feat(web): merge compact status and summary cards

This commit is contained in:
Yichen Jiang
2026-08-08 14:11:17 +08:00
parent cc407a2672
commit 1db327ea6c
47 changed files with 725 additions and 121 deletions
+27 -5
View File
@@ -47,7 +47,12 @@ export interface CommandInvocation {
/** Expected command outcome rendered directly by the dispatching UI. */
export type CommandResult =
| { readonly kind: 'success'; readonly text?: string }
| {
readonly kind: 'success'
readonly text?: string
/** Earlier authoritative domain event that owns a richer presentation. */
readonly sourceEventSeq?: number
}
| { readonly kind: 'error'; readonly text: string }
/**
@@ -140,9 +145,15 @@ declare module '@deepseek-ai/dsh-session' {
/**
* The paired command settled. `kind`/`text` carry the handler's verbatim
* outcome (a thrown/aborted handler settles as `kind: 'error'` with the
* rendered failure); presentation stays client-computed at render time.
* rendered failure). A successful command may identify the earlier
* authoritative domain event for a richer client-computed presentation.
*/
'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string }
'command/done': {
commandId: CommandId
kind: 'success' | 'error'
text?: string
sourceEventSeq?: number
}
}
}
@@ -262,12 +273,20 @@ function normalizeResult(command: string, value: unknown): CommandResult {
if (typeof value !== 'object' || value === null || !('kind' in value)) {
throw new TypeError(`command "${command}" handler must return a CommandResult`)
}
const result = value as { kind?: unknown; text?: unknown }
const result = value as { kind?: unknown; text?: unknown; sourceEventSeq?: unknown }
if (result.kind === 'success') {
if (result.text !== undefined && typeof result.text !== 'string') {
throw new TypeError(`command "${command}" success text must be a string when supplied`)
}
return Object.freeze(result.text === undefined ? { kind: 'success' } : { kind: 'success', text: result.text })
if (result.sourceEventSeq !== undefined
&& (!Number.isSafeInteger(result.sourceEventSeq) || (result.sourceEventSeq as number) < 0)) {
throw new TypeError(`command "${command}" success sourceEventSeq must be a non-negative safe integer when supplied`)
}
return Object.freeze({
kind: 'success',
...result.text === undefined ? {} : { text: result.text },
...result.sourceEventSeq === undefined ? {} : { sourceEventSeq: result.sourceEventSeq as number },
})
}
if (result.kind === 'error') {
if (typeof result.text !== 'string' || result.text.trim().length === 0) {
@@ -389,6 +408,9 @@ export class CommandService extends Service {
this.appendLifecycle(agent.session, 'command/done', {
commandId, kind: result.kind,
...result.text === undefined ? {} : { text: result.text },
...result.kind === 'success' && result.sourceEventSeq !== undefined
? { sourceEventSeq: result.sourceEventSeq }
: {},
})
return Object.freeze({ commandId, result })
}
+10
View File
@@ -34,6 +34,16 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
if (runIds.get(session)?.has(event.data.commandId) !== true) {
fail(`command/done ${JSON.stringify(event.data.commandId)} pairs no prior command/run in this log`)
}
const source = event.data.sourceEventSeq
const sourceEvent = source === undefined ? undefined : session.events[source]
if (source !== undefined
&& (event.data.kind !== 'success'
|| !Number.isSafeInteger(source) || source < 0 || source >= event.seq
|| sourceEvent?.seq !== source
|| sourceEvent.type === 'command/run'
|| sourceEvent.type === 'command/done')) {
fail(`command/done ${JSON.stringify(event.data.commandId)} has invalid sourceEventSeq ${String(source)}`)
}
}
for (const session of ctx.sessions.list()) {
for (const event of session.events) validateEvent(session, event)