revert: use session.append
This commit is contained in:
@@ -313,11 +313,7 @@ export class ReactLoopAgent implements Agent {
|
|||||||
this.abort = controller
|
this.abort = controller
|
||||||
this.acceptsNextStep = true
|
this.acceptsNextStep = true
|
||||||
const signal = controller.signal
|
const signal = controller.signal
|
||||||
// The log is the turn-number authority: out-of-band zero-step turns
|
const turn = this.lastTurn + 1
|
||||||
// (command lifecycle on an idle log) advance it behind this cached
|
|
||||||
// counter, so re-derive the successor at open instead of trusting it.
|
|
||||||
const loggedLast = this.session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 0
|
|
||||||
const turn = Math.max(this.lastTurn, loggedLast) + 1
|
|
||||||
let step = 0
|
let step = 0
|
||||||
let opened = false
|
let opened = false
|
||||||
let reason: TurnEndReason = { kind: 'completed' }
|
let reason: TurnEndReason = { kind: 'completed' }
|
||||||
|
|||||||
@@ -119,11 +119,6 @@ class CommandLayer implements ScopeLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module '@deepseek-ai/dsh-session' {
|
declare module '@deepseek-ai/dsh-session' {
|
||||||
interface TurnTriggerMap {
|
|
||||||
/** Zero-step turn opened only to durably record a command lifecycle event on an idle log. */
|
|
||||||
command: { kind: 'command' }
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SessionEventMap {
|
interface SessionEventMap {
|
||||||
/**
|
/**
|
||||||
* A resolved slash command entered its handler. Log-only (never model
|
* A resolved slash command entered its handler. Log-only (never model
|
||||||
@@ -142,11 +137,6 @@ declare module '@deepseek-ai/dsh-session' {
|
|||||||
*/
|
*/
|
||||||
'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string }
|
'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OutOfBandSessionEventMap {
|
|
||||||
'command/run': true
|
|
||||||
'command/done': true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'cordis' {
|
declare module 'cordis' {
|
||||||
@@ -286,9 +276,6 @@ function normalizeResult(command: string, value: unknown): CommandResult {
|
|||||||
* globals for that agent.
|
* globals for that agent.
|
||||||
*/
|
*/
|
||||||
export class CommandService extends Service {
|
export class CommandService extends Service {
|
||||||
/** The executor writes lifecycle events through the session store. */
|
|
||||||
static inject = ['sessions']
|
|
||||||
|
|
||||||
private readonly layers = new ScopedLayers(
|
private readonly layers = new ScopedLayers(
|
||||||
scope => new CommandLayer(scope),
|
scope => new CommandLayer(scope),
|
||||||
() => { this.notifyChange() },
|
() => { this.notifyChange() },
|
||||||
@@ -298,12 +285,6 @@ export class CommandService extends Service {
|
|||||||
private commandSeq = 0
|
private commandSeq = 0
|
||||||
/** Instance token keeping minted ids unique across process restarts over one resumed log. */
|
/** Instance token keeping minted ids unique across process restarts over one resumed log. */
|
||||||
private readonly instanceToken = crypto.randomUUID().slice(0, 8)
|
private readonly instanceToken = crypto.randomUUID().slice(0, 8)
|
||||||
/**
|
|
||||||
* Per-session lifecycle-append chains: `appendOutOfBand` rejects a second
|
|
||||||
* concurrent out-of-band append, so this service serializes its own writes
|
|
||||||
* (the session-title tail-queue pattern).
|
|
||||||
*/
|
|
||||||
private readonly logTails = new WeakMap<Session, Promise<void>>()
|
|
||||||
|
|
||||||
constructor(ctx: Context) {
|
constructor(ctx: Context) {
|
||||||
super(ctx, 'commands')
|
super(ctx, 'commands')
|
||||||
@@ -348,13 +329,15 @@ export class CommandService extends Service {
|
|||||||
/**
|
/**
|
||||||
* Parse and execute a known command without sending it to the model.
|
* Parse and execute a known command without sending it to the model.
|
||||||
*
|
*
|
||||||
* A resolved command's lifecycle is durably logged: `command/run` is
|
* A resolved command's lifecycle is logged: `command/run` is appended
|
||||||
* appended before the handler is invoked and `command/done` after
|
* before the handler is invoked and `command/done` after settlement (a
|
||||||
* settlement (a thrown or aborted handler settles as `kind: 'error'`).
|
* thrown or aborted handler settles as `kind: 'error'`). Both are direct
|
||||||
* Admission misses (syntax or unknown name) log nothing — they never
|
* log-only appends — no turn wraps them, and persistence drains them at
|
||||||
* entered a handler. A `command/run` append failure fails the execution
|
* ordinary checkpoints. Admission misses (syntax or unknown name) log
|
||||||
* loud; a `command/done` append failure on the handler-failure path is
|
* nothing — they never entered a handler. A `command/run` append failure
|
||||||
* contained so the handler's own error stays the reported failure.
|
* fails the execution loud; a `command/done` append failure on the
|
||||||
|
* handler-failure path is contained so the handler's own error stays the
|
||||||
|
* reported failure.
|
||||||
*
|
*
|
||||||
* @param agent - exact receiving agent.
|
* @param agent - exact receiving agent.
|
||||||
* @param line - complete slash-command line.
|
* @param line - complete slash-command line.
|
||||||
@@ -373,7 +356,7 @@ export class CommandService extends Service {
|
|||||||
if (command === undefined) return undefined
|
if (command === undefined) return undefined
|
||||||
if (signal.aborted) throw abortError(signal)
|
if (signal.aborted) throw abortError(signal)
|
||||||
const commandId = this.mintCommandId()
|
const commandId = this.mintCommandId()
|
||||||
await this.appendLifecycle(agent.session, 'command/run', {
|
this.appendLifecycle(agent.session, 'command/run', {
|
||||||
commandId, name: parsed.name, args: parsed.rawInput, source: { kind: 'user' },
|
commandId, name: parsed.name, args: parsed.rawInput, source: { kind: 'user' },
|
||||||
})
|
})
|
||||||
const invocation = Object.freeze({ agent, rawInput: parsed.rawInput, signal })
|
const invocation = Object.freeze({ agent, rawInput: parsed.rawInput, signal })
|
||||||
@@ -383,7 +366,7 @@ export class CommandService extends Service {
|
|||||||
result = normalizeResult(parsed.name, await withAbort(Promise.resolve(output), signal))
|
result = normalizeResult(parsed.name, await withAbort(Promise.resolve(output), signal))
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
try {
|
try {
|
||||||
await this.appendLifecycle(agent.session, 'command/done', {
|
this.appendLifecycle(agent.session, 'command/done', {
|
||||||
commandId, kind: 'error',
|
commandId, kind: 'error',
|
||||||
text: error instanceof Error ? error.message : renderThrown(error),
|
text: error instanceof Error ? error.message : renderThrown(error),
|
||||||
})
|
})
|
||||||
@@ -392,7 +375,7 @@ export class CommandService extends Service {
|
|||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
await this.appendLifecycle(agent.session, 'command/done', {
|
this.appendLifecycle(agent.session, 'command/done', {
|
||||||
commandId, kind: result.kind,
|
commandId, kind: result.kind,
|
||||||
...result.text === undefined ? {} : { text: result.text },
|
...result.text === undefined ? {} : { text: result.text },
|
||||||
})
|
})
|
||||||
@@ -406,19 +389,21 @@ export class CommandService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append one lifecycle event, serialized per session: `appendOutOfBand`
|
* Append one log-only lifecycle event directly: no turn is opened for it and
|
||||||
* rejects concurrent out-of-band appends, and two commands may overlap on
|
* no flush is forced — persistence observes the eager `session/event` path
|
||||||
* one session.
|
* and drains at ordinary checkpoints and teardown, like every other
|
||||||
|
* standalone plugin event.
|
||||||
*/
|
*/
|
||||||
private appendLifecycle<T extends 'command/run' | 'command/done'>(
|
private appendLifecycle<T extends 'command/run' | 'command/done'>(
|
||||||
session: Session,
|
session: Session,
|
||||||
type: T,
|
type: T,
|
||||||
data: SessionEventMap[T],
|
data: SessionEventMap[T],
|
||||||
): Promise<SessionEvent<T>> {
|
): SessionEvent<T> {
|
||||||
const tail = this.logTails.get(session) ?? Promise.resolve()
|
// Both admitted types are log-only (non-surface), but TypeScript does not
|
||||||
const run = tail.then(() => this.ctx.sessions.appendOutOfBand(session, type, data, { kind: 'command' }))
|
// reduce Session.append's conditional rest parameter through a generic
|
||||||
this.logTails.set(session, run.then(() => undefined, () => undefined))
|
// type parameter. Preserve the proven two-argument call shape.
|
||||||
return run
|
const appendLogOnly = session.append.bind(session) as (eventType: T, eventData: SessionEventMap[T]) => SessionEvent<T>
|
||||||
|
return appendLogOnly(type, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolve global definitions followed by exact scoped shadows. */
|
/** Resolve global definitions followed by exact scoped shadows. */
|
||||||
|
|||||||
Reference in New Issue
Block a user