Skip to content

Commit 51058d2

Browse files
fix(web): correct notifier agent UI state from full agent API (#79)
- trust profile_complete for configured notifier bots - load delivery_mode from GET /api/v1/agents/{id} runtime_options
1 parent 87acb69 commit 51058d2

3 files changed

Lines changed: 63 additions & 21 deletions

File tree

web/static/app.js

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2703,9 +2703,7 @@ function App() {
27032703
setAgentProgress(null);
27042704
setAgentModels([]);
27052705
try {
2706-
const resp = await fetch(`api/v1/agents/${encodeURIComponent(item.id)}/profile`);
2707-
const profile = resp.ok ? await resp.json() : item.agent_profile;
2708-
const draft = ensureNotifierPullSubscriptionDraft(agentToDraft({ ...item, agent_profile: profile }));
2706+
const draft = await agentDraftFromItem(item);
27092707
setAgentDraft(draft);
27102708
setShowAgentModal(true);
27112709
loadAgentModels(draft, { silent: true });
@@ -2721,12 +2719,9 @@ function App() {
27212719
setAgentPageError("");
27222720
setAgentPageModels([]);
27232721
try {
2724-
const resp = await fetch(`api/v1/agents/${encodeURIComponent(item.id)}/profile`);
2725-
const profile = resp.ok ? await resp.json() : item.agent_profile;
2726-
const base = ensureNotifierPullSubscriptionDraft(agentToDraft({ ...item, agent_profile: profile }));
2727-
const rk = normalizeRuntimeKind(item.runtime_kind || base.runtime_kind);
2728-
setAgentPageDraft({ ...base, runtime_kind: rk || base.runtime_kind });
2729-
loadAgentPageModels({ ...base, runtime_kind: rk || base.runtime_kind }, { silent: true });
2722+
const draft = await agentDraftFromItem(item);
2723+
setAgentPageDraft(draft);
2724+
loadAgentPageModels(draft, { silent: true });
27302725
} catch (err) {
27312726
setAgentPageError(err.message || t("agentActionFailed"));
27322727
const base = ensureNotifierPullSubscriptionDraft(agentToDraft(item));
@@ -2878,7 +2873,7 @@ function App() {
28782873
if (saved.id === "u-manager") {
28792874
await refreshManagerProfile();
28802875
}
2881-
setAgentPageDraft(agentToDraft(saved));
2876+
setAgentPageDraft(await agentDraftFromItem(saved));
28822877
} catch (err) {
28832878
setAgentPageError(err.message || t("agentActionFailed"));
28842879
} finally {
@@ -5159,10 +5154,7 @@ function AgentDetailPane({
51595154
const isManager = item.role === "manager" || item.id === "u-manager";
51605155
const running = isAgentRunning(item);
51615156
const draftBelongsToItem = Boolean(draft) && String(draft?.agent_id ?? "").trim() === String(item?.id ?? "").trim();
5162-
const incomplete =
5163-
draftBelongsToItem && isNotifierRuntimeDraftOnAgentPage(draft, item)
5164-
? !notifierFormIsComplete(draft, item)
5165-
: isAgentIncomplete(item);
5157+
const incomplete = isAgentIncomplete(item, draftBelongsToItem ? draft : undefined);
51665158
const restartNeeded = isAgentRestartNeeded(item);
51675159
const busyPrefix = `${item.id}:`;
51685160
const provider = item.provider || item.agent_profile?.provider;
@@ -5203,9 +5195,7 @@ function AgentDetailPane({
52035195
`
52045196
: null}
52055197
<button className="btn btn-secondary-gray btn-sm preview-action-button" disabled=${busyKey.startsWith(busyPrefix)} onClick=${() => onOpenDM(item)}>${t("openDM")}</button>
5206-
${isManager
5207-
? html`<button className="btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger" disabled=${busyKey.startsWith(busyPrefix) || incomplete} onClick=${() => onRecreate(item)}>${t("agentRecreate")}</button>`
5208-
: html`<button className="btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger" disabled=${busyKey.startsWith(busyPrefix) || incomplete} onClick=${() => onRecreate(item)}>${t("agentRecreate")}</button>`}
5198+
<button className="btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger" disabled=${busyKey.startsWith(busyPrefix) || incomplete} onClick=${() => onRecreate(item)}>${t("agentRecreate")}</button>
52095199
${!isManager
52105200
? html`<button className="btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger" disabled=${busyKey.startsWith(busyPrefix)} onClick=${() => onDelete(item)}>${t("agentDelete")}</button>`
52115201
: null}
@@ -6583,6 +6573,40 @@ function inferNotifierRuntimeKindIfUnset(agent, profile) {
65836573
return "notifier";
65846574
}
65856575

6576+
/** Loads full agent record (runtime_options) plus redacted profile view for draft editing. */
6577+
async function fetchAgentWithProfile(item) {
6578+
const id = String(item?.id ?? "").trim();
6579+
if (!id) {
6580+
return { agent: item || {}, profile: item?.agent_profile };
6581+
}
6582+
let agent = item || {};
6583+
try {
6584+
const agentResp = await fetch(`api/v1/agents/${encodeURIComponent(id)}`);
6585+
if (agentResp.ok) {
6586+
agent = { ...agent, ...(await agentResp.json()) };
6587+
}
6588+
} catch {
6589+
// keep channel bot list item
6590+
}
6591+
let profile = agent?.agent_profile;
6592+
try {
6593+
const profileResp = await fetch(`api/v1/agents/${encodeURIComponent(id)}/profile`);
6594+
if (profileResp.ok) {
6595+
profile = await profileResp.json();
6596+
}
6597+
} catch {
6598+
// keep profile from agent record
6599+
}
6600+
return { agent, profile };
6601+
}
6602+
6603+
async function agentDraftFromItem(item) {
6604+
const { agent, profile } = await fetchAgentWithProfile(item);
6605+
const base = agentToDraft({ ...agent, agent_profile: profile });
6606+
const rk = normalizeRuntimeKind(agent?.runtime_kind || item?.runtime_kind || base.runtime_kind);
6607+
return ensureNotifierPullSubscriptionDraft({ ...base, runtime_kind: rk || base.runtime_kind });
6608+
}
6609+
65866610
function agentToDraft(agent) {
65876611
const profile = agent?.agent_profile || agent || {};
65886612
const inferred = inferNotifierRuntimeKindIfUnset(agent, profile);
@@ -6799,8 +6823,15 @@ function isAgentRunning(item) {
67996823
return status === "running" || status === "online";
68006824
}
68016825

6802-
function isAgentIncomplete(item) {
6803-
const draft = agentToDraft(item);
6826+
function isAgentProfileMarkedComplete(item) {
6827+
return item?.profile_complete === true || item?.agent_profile?.profile_complete === true;
6828+
}
6829+
6830+
function isAgentIncomplete(item, draftOverride) {
6831+
if (isAgentProfileMarkedComplete(item)) {
6832+
return false;
6833+
}
6834+
const draft = draftOverride ?? agentToDraft(item);
68046835
if (isNotifierRuntimeDraftOnAgentPage(draft, item)) {
68056836
return !notifierFormIsComplete(draft, item);
68066837
}

web/static/app_action_card.test.cjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,15 @@ assert(
119119
source.includes('pickDefaultAgentTemplate(hubTemplates, runtimeKind, bootstrapConfig)'),
120120
'changing the runtime must re-pick a compatible default template when needed',
121121
);
122+
assert(
123+
source.includes('function isAgentProfileMarkedComplete(item)') &&
124+
source.includes('if (isAgentProfileMarkedComplete(item))') &&
125+
source.includes('return false;'),
126+
'notifier agents with profile_complete from the bots API must not show as incomplete in the UI',
127+
);
128+
assert(
129+
source.includes('async function fetchAgentWithProfile(item)') &&
130+
source.includes('fetch(`api/v1/agents/${encodeURIComponent(id)}`)') &&
131+
source.includes('async function agentDraftFromItem(item)'),
132+
'notifier agent drafts must load runtime_options from the full agent API, not profile-only',
133+
);

web/static/app_agent_profile_actions.test.cjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ assert(
2323
'agent detail pane must keep DM while gating start/stop/recreate behind SHOW_AGENT_LIFECYCLE_ACTIONS',
2424
);
2525
assert(
26-
agentDetailPaneSource.includes('isManager') &&
27-
agentDetailPaneSource.includes('btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger') &&
26+
agentDetailPaneSource.includes('btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger') &&
2827
!agentDetailPaneSource.includes('btn btn-secondary-gray btn-sm preview-action-button" disabled=${busyKey.startsWith(busyPrefix) || incomplete} onClick=${() => onRecreate(item)}') &&
2928
agentDetailPaneSource.includes('<button className="btn btn-outline-danger btn-sm preview-action-button preview-action-button-danger" disabled=${busyKey.startsWith(busyPrefix) || incomplete} onClick=${() => onRecreate(item)}>${t("agentRecreate")}</button>'),
3029
'agent detail pane must expose recreate for both manager and worker with the same red danger styling',

0 commit comments

Comments
 (0)