From caae28e0d42a14497e160476d55d9fd2c0b5e55f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 11 Aug 2026 22:21:24 -0400 Subject: [PATCH] fix(tui): render instruction updates as compact notices (#41900) --- .../client/src/promise/generated/types.ts | 4 ++++ packages/core/src/session/message-updater.ts | 1 + packages/schema/src/session-message.ts | 3 +++ packages/tui/src/context/data.tsx | 15 ++++++-------- packages/tui/src/routes/session/index.tsx | 2 +- packages/tui/test/cli/tui/data.test.tsx | 20 +++++++++++++++---- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index a23aa7d7a44..fffe17e4134 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -65,6 +65,7 @@ export type SessionMessageSystem = { time: { created: number } type: "system" text: string + description?: string } export type SessionMessageSkill = { @@ -2611,6 +2612,7 @@ export type SessionImportInput = { readonly time: { readonly created: number } readonly type: "system" readonly text: string + readonly description?: string } | { readonly id: string @@ -2877,6 +2879,7 @@ export type SessionImportInput = { readonly time: { readonly created: number } readonly type: "system" readonly text: string + readonly description?: string } | { readonly id: string @@ -3143,6 +3146,7 @@ export type SessionImportInput = { readonly time: { readonly created: number } readonly type: "system" readonly text: string + readonly description?: string } | { readonly id: string diff --git a/packages/core/src/session/message-updater.ts b/packages/core/src/session/message-updater.ts index a892e1c8c42..29617feb077 100644 --- a/packages/core/src/session/message-updater.ts +++ b/packages/core/src/session/message-updater.ts @@ -125,6 +125,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) { id: SessionMessage.ID.fromEvent(event.id), type: "system", text: event.data.text, + description: `Instructions updated: ${Object.keys(event.data.delta).join(", ")}`, metadata: event.metadata, time: { created: event.created }, }), diff --git a/packages/schema/src/session-message.ts b/packages/schema/src/session-message.ts index 1a42e8422c0..0de8d68a04c 100644 --- a/packages/schema/src/session-message.ts +++ b/packages/schema/src/session-message.ts @@ -91,7 +91,10 @@ export interface System extends Schema.Schema.Type {} export const System = Schema.Struct({ ...Base, type: Schema.tag("system"), + /** The model-facing update text, frozen at emit time. */ text: Schema.String, + /** A short human-readable summary for transcript display. */ + description: Schema.String.pipe(optional), }).annotate({ identifier: "Session.Message.System" }) export interface Skill extends Schema.Schema.Type {} diff --git a/packages/tui/src/context/data.tsx b/packages/tui/src/context/data.tsx index 7b078ea3a94..a4399d5d0a1 100644 --- a/packages/tui/src/context/data.tsx +++ b/packages/tui/src/context/data.tsx @@ -523,19 +523,16 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ }) break case "session.instructions.updated": - const instructions = event.metadata?.instructions - if ( - typeof instructions === "object" && - instructions !== null && - "initial" in instructions && - instructions.initial === true - ) - break + // Mirror the projector: the initial baseline and empty-rendering deltas carry no text + // and produce no transcript message. + const updateText = event.data.text + if (updateText === undefined) break message.update(event.data.sessionID, (draft, index) => { message.append(draft, index, { id: messageIDFromEvent(event.id), type: "system", - text: `Instructions updated: ${Object.keys(event.data.delta).join(", ")}`, + text: updateText, + description: `Instructions updated: ${Object.keys(event.data.delta).join(", ")}`, metadata: event.metadata, time: { created: event.created }, }) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index cf75052517a..bedc64e1fd1 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1695,7 +1695,7 @@ function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) { const state = () => stringValue(metadata()?.state) const actor = () => (source() === "shell" ? "Shell" : Locale.titlecase(stringValue(metadata()?.agent) ?? "Subagent")) const text = () => { - if (props.message.type === "system") return props.message.text + if (props.message.type === "system") return props.message.description ?? "Instructions updated" if (props.message.type === "synthetic") return props.message.description ?? "" return "" } diff --git a/packages/tui/test/cli/tui/data.test.tsx b/packages/tui/test/cli/tui/data.test.tsx index f54905b29e7..5d466edb1d4 100644 --- a/packages/tui/test/cli/tui/data.test.tsx +++ b/packages/tui/test/cli/tui/data.test.tsx @@ -2901,14 +2901,26 @@ test("skips initial instruction state and projects later updates with their mess delta: { "core/date": "1".repeat(64) }, }, }) + emitEvent(events, { + id: "evt_instructions_3", + created: 2, + type: "session.instructions.updated", + durable: durable("session-1", 2, 2), + data: { + sessionID: "session-1", + delta: { "core/date": "2".repeat(64) }, + text: "The current date has changed.", + }, + }) - await wait(() => sync.session.message.list("session-1")?.some((message) => message.time.created === 1)) + await wait(() => sync.session.message.list("session-1")?.some((message) => message.time.created === 2)) expect(sync.session.message.list("session-1")).toHaveLength(1) expect(sync.session.message.list("session-1")?.[0]).toMatchObject({ - id: SessionMessage.ID.fromEvent(Event.ID.make("evt_instructions_2")), + id: SessionMessage.ID.fromEvent(Event.ID.make("evt_instructions_3")), type: "system", - text: "Instructions updated: core/date", - time: { created: 1 }, + text: "The current date has changed.", + description: "Instructions updated: core/date", + time: { created: 2 }, }) } finally { app.renderer.destroy()