Skip to content

Commit df19912

Browse files
mxmzbclaude
andcommitted
fix: inspector syncs state on direct agent.setState()
When using selfManagedAgents, calling agent.setState() directly only fires onStateChanged — the inspector subscribed to onStateSnapshotEvent (pipeline-only) but not onStateChanged, leaving the "Current State" panel permanently stale. Add onStateChanged handler mirroring the existing onMessagesChanged pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dd526ef commit df19912

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

packages/web-inspector/src/__tests__/web-inspector.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,51 @@ describe("WebInspectorElement", () => {
206206
inspectorHandle.persistState();
207207
expect(localStorage.getItem("cpk:inspector:state")).toBeTruthy();
208208
});
209+
210+
it("syncs agent state on direct setState (onStateChanged without pipeline events)", async () => {
211+
// Simulates a selfManagedAgent where agent.setState() is called directly
212+
// from UI code, bypassing the AG-UI event pipeline. Before the fix,
213+
// only onStateSnapshotEvent updated the inspector — onStateChanged was
214+
// not subscribed to, so direct setState() left the inspector stale.
215+
const { agent, controller } = createMockAgent("counter", {
216+
state: { counter: 0 },
217+
});
218+
const { core, emitAgentsChanged } = createMockCore({ counter: agent });
219+
220+
const inspector = new WebInspectorElement();
221+
document.body.appendChild(inspector);
222+
inspector.core = core as unknown as WebInspectorElement["core"];
223+
224+
emitAgentsChanged();
225+
await inspector.updateComplete;
226+
227+
const inspectorHandle = inspector as unknown as InspectorInternals;
228+
229+
// Initial state should be captured on subscription
230+
expect(inspectorHandle.agentStates.get("counter")).toEqual({ counter: 0 });
231+
232+
// Simulate agent.setState({ counter: 1 }) — mutate the agent's state
233+
// property and fire onStateChanged (exactly what AbstractAgent.setState does)
234+
(agent as unknown as { state: unknown }).state = { counter: 1 };
235+
controller.emit("onStateChanged", {
236+
state: { counter: 1 },
237+
messages: [],
238+
agent,
239+
});
240+
await inspector.updateComplete;
241+
242+
// Inspector should reflect the updated state
243+
expect(inspectorHandle.agentStates.get("counter")).toEqual({ counter: 1 });
244+
245+
// Simulate a second setState
246+
(agent as unknown as { state: unknown }).state = { counter: 5 };
247+
controller.emit("onStateChanged", {
248+
state: { counter: 5 },
249+
messages: [],
250+
agent,
251+
});
252+
await inspector.updateComplete;
253+
254+
expect(inspectorHandle.agentStates.get("counter")).toEqual({ counter: 5 });
255+
});
209256
});

packages/web-inspector/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ export class WebInspectorElement extends LitElement {
496496
onMessagesChanged: () => {
497497
this.syncAgentMessages(agent);
498498
},
499+
onStateChanged: () => {
500+
this.syncAgentState(agent);
501+
},
499502
onRawEvent: ({ event }) => {
500503
this.recordAgentEvent(agentId, "RAW_EVENT", event);
501504
},

0 commit comments

Comments
 (0)