fix(tui): render instruction updates as compact notices (#41900)

This commit is contained in:
Kit Langton 2026-08-11 22:21:24 -04:00 committed by GitHub
parent c83933d1d4
commit caae28e0d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 31 additions and 14 deletions

View file

@ -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

View file

@ -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 },
}),

View file

@ -91,7 +91,10 @@ export interface System extends Schema.Schema.Type<typeof System> {}
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<typeof Skill> {}

View file

@ -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 },
})

View file

@ -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 ""
}

View file

@ -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()