feat(web): merge compact status and summary cards
This commit is contained in:
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user