Merge remote-tracking branch 'origin/master' into codex/session-scoped-sandbox-roots
# Conflicts: # .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml # .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md # .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md # examples/acp-agent/README.md # packages/examples/agent-spine-demo/package.json # packages/fs/fs-sandbox/src/index.ts # packages/fs/tool-fs-search/tests/tools.spec.ts # packages/support/acp-snapshot/README.md # packages/support/acp-snapshot/src/suite.ts # pnpm-lock.yaml # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -372,6 +372,20 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'planMode',
|
||||
summary: '`ctx.planMode`: owns logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool.',
|
||||
methods: [
|
||||
{
|
||||
signature: 'get(agent: Agent): { active: boolean; pending?: boolean }',
|
||||
jsDoc: '/**\n * Read the logged plan state and any selected state awaiting a boundary.\n *\n * @param agent The agent to read.\n * @returns Current logged state plus a pending selection, when present.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'set(agent: Agent, active: boolean): void',
|
||||
jsDoc: '/**\n * Select whether plan mode should be active from the next turn boundary.\n * Repeated selection of the current or already-pending state is a no-op.\n *\n * @param agent The agent to switch.\n * @param active Whether plan mode should be active.\n */',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sandbox',
|
||||
summary: 'Abstract process-sandbox service.',
|
||||
@@ -426,6 +440,10 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
signature: 'listSessions(): Promise<SessionRecord[]>',
|
||||
jsDoc: '/**\n * List the complete logical corpus using live-preferred records.\n * @returns deterministic newest-first cloned session records.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'async readTitle(sessionId: SessionId): Promise<SessionTitleSnapshot | undefined>',
|
||||
jsDoc: '/**\n * Fold the latest log-backed title from one live-preferred logical session.\n * @param sessionId - live or persisted session id to read.\n * @returns latest title snapshot, or `undefined` when the log has no title event.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'async listEvents(sessionId: SessionId): Promise<SessionEventRecord[]>',
|
||||
jsDoc: '/**\n * List lightweight raw-log event records for one logical session.\n * @param sessionId - live-preferred session id to read.\n * @returns event records in ascending seq order.\n */',
|
||||
@@ -468,6 +486,10 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
signature: 'async flush(session: Session): Promise<void>',
|
||||
jsDoc: '/**\n * Dispatch the awaited `session/flush` durability checkpoint for `session`,\n * with the carrier captured at {@link enter}. THE flush entry point: the\n * store owns the carrier, so callers (the loop\'s turn-end checkpoint, idle\n * injection, teardown drains) must come through here rather than dispatch a\n * raw `ctx.parallel(\'session/flush\', …)` — one owner, one spelling, and the\n * scoped-dispatch invariant can pin it.\n * @param session - the session whose buffered events must reach durable storage.\n * @returns resolves when every flush listener has settled; after all settle,\n * rejects with the first registered listener failure if any listener failed.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'async appendOutOfBand<T extends OutOfBandSessionEventType>( session: Session, type: T, data: SessionEventMap[T], trigger: TurnTrigger, ): Promise<SessionEvent<T>>',
|
||||
jsDoc: '/**\n * Append one plugin-declared log-only event without borrowing the agent\n * loop\'s lifecycle. An open turn receives the event directly and remains\n * responsible for its ordinary checkpoint. A closed log receives one\n * zero-step turn around the event, followed by an awaited flush.\n *\n * Once the synthetic `turn/start` commits, this method always attempts its\n * matching `turn/end` and flush, including when the target append fails.\n * Detachment requested by an event or flush listener is deferred until that\n * sequence settles, so publication cannot switch from a live scoped session\n * to an unobserved bare `Session` halfway through the update.\n *\n * @param session - exact live session that owns the target log.\n * @param type - event type opted into {@link OutOfBandSessionEventMap} by its owner.\n * @param data - typed JSON payload for the target event.\n * @param trigger - plugin-owned turn trigger used only when the log is closed.\n * @returns the accepted target event with its assigned sequence and timestamp.\n * @throws when the session is detached, another out-of-band append is active,\n * event acceptance fails, the synthetic turn cannot close, or flushing fails.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'get(id: SessionId): Session | undefined',
|
||||
jsDoc: '/**\n * Look up a live session.\n * @param id - the session id to look up.\n * @returns the session, or undefined when no live session has that id.\n */',
|
||||
@@ -482,6 +504,24 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sessionTitle',
|
||||
summary: 'Log-backed title fold plus asynchronous fallback generation.',
|
||||
methods: [
|
||||
{
|
||||
signature: 'get(session: Session): SessionTitleSnapshot | undefined',
|
||||
jsDoc: '/**\n * Read the latest folded title from one live or replayed session.\n * @param session - session whose log is the title source of truth.\n * @returns latest title snapshot, or `undefined` before eligible input.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnapshot | undefined>',
|
||||
jsDoc: '/**\n * Explicitly retry the registered provider, or materialize the built-in\n * fallback when no provider is registered.\n * @param session - exact live session to refresh.\n * @param signal - optional caller cancellation; an in-progress fallback append may finish durably before rejection.\n * @returns latest accepted title, or `undefined` when no eligible text exists.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'register(provider: SessionTitleProvider): () => Promise<void>',
|
||||
jsDoc: '/**\n * Register the sole optional title provider. Disposal aborts its pending and\n * active work before another provider may register.\n * @param provider - provider identity, cadence, and generation function.\n * @returns exact Cordis effect disposer, which settles after active calls quiesce.\n */',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'skills',
|
||||
summary: 'Registry of skill providers.',
|
||||
@@ -658,7 +698,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
},
|
||||
{
|
||||
signature: 'async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>',
|
||||
jsDoc: '/**\n * Execute through pre-policy, guards, around-dispatch, post-policy, and final\n * notification. Tool and listener failures resolve as materialized error\n * results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is\n * the same lossless, frozen snapshot final observers receive.\n * @param exec - the typed same-process call input. The registry assigns its\n * correlation token before policy begins.\n * @returns the materialized final result.\n */',
|
||||
jsDoc: '/**\n * Execute through pre-policy, guards, around-dispatch, post-policy, and final\n * notification. Tool and listener failures resolve as materialized error\n * results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is\n * the same lossless, frozen snapshot final observers receive. Cancellation\n * arriving after entry and before final result materialization skips a\n * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a\n * successful started outcome with `ABORTED`; already-started work is still\n * drained and may retain a tool-owned structured error.\n * @param exec - the typed same-process call input. The registry assigns its\n * correlation token before policy begins.\n * @returns the materialized final result.\n */',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -722,9 +762,9 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
{
|
||||
name: 'agent/cancel-requested',
|
||||
mode: 'emit',
|
||||
signature: '\'agent/cancel-requested\'(this: Scoped<Agent>, agent: Agent, reason: string): void',
|
||||
jsDoc: '/**\n * Effective broad cancellation was requested, before queued/steering work\n * is cleared or the active step is aborted. This observe-only notification\n * cannot veto cancellation; listener failures are contained.\n * @param agent - the agent whose current work is being cancelled.\n * @param reason - resolved cancellation reason, including the default.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode emit\n */',
|
||||
summary: 'Effective broad cancellation was requested, before queued/steering work is cleared or the active step is aborted.',
|
||||
signature: '\'agent/cancel-requested\'(this: Scoped<Agent>, agent: Agent, cause: AgentCancelCause): void',
|
||||
jsDoc: '/**\n * Effective broad cancellation was requested, before queued/steering work\n * is cleared or the active turn is aborted. This observe-only notification\n * cannot veto cancellation; listener failures are contained.\n * @param agent - the agent whose current work is being cancelled.\n * @param cause - resolved typed cancellation cause, including the default.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode emit\n */',
|
||||
summary: 'Effective broad cancellation was requested, before queued/steering work is cleared or the active turn is aborted.',
|
||||
},
|
||||
{
|
||||
name: 'agent/created',
|
||||
@@ -764,8 +804,8 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
{
|
||||
name: 'agent/prompt-submit',
|
||||
mode: 'waterfall',
|
||||
signature: '\'agent/prompt-submit\'(this: Scoped<Agent>, agent: Agent, content: ContentBlock[], source: MessageSource, next: () => Promise<PromptDecision>): Promise<PromptDecision>',
|
||||
jsDoc: '/**\n * Allow, rewrite, or block one claimed prompt before it becomes a user\n * message. Call `next()` for the unchanged default.\n * @param agent - the agent whose turn claimed the message.\n * @param content - the claimed message\'s blocks, as queued.\n * @param source - the message\'s resolved source.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
signature: '\'agent/prompt-submit\'(this: Scoped<Agent>, agent: Agent, content: ContentBlock[], source: MessageSource, signal: AbortSignal, next: () => Promise<PromptDecision>): Promise<PromptDecision>',
|
||||
jsDoc: '/**\n * Allow, rewrite, or block one claimed prompt before it becomes a user\n * message. Call `next()` for the unchanged default. The signal controls only\n * this turn; listeners may cooperate with it but must not retain it to\n * control another turn.\n * @param agent - the agent whose turn claimed the message.\n * @param content - the claimed message\'s blocks, as queued.\n * @param source - the message\'s resolved source.\n * @param signal - the current turn\'s explicit abort signal.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
summary: 'Allow, rewrite, or block one claimed prompt before it becomes a user message.',
|
||||
},
|
||||
{
|
||||
@@ -778,8 +818,8 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
{
|
||||
name: 'agent/request',
|
||||
mode: 'waterfall',
|
||||
signature: '\'agent/request\'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, config: LlmCallConfig, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>',
|
||||
jsDoc: '/**\n * Replace the frozen call configuration. Model-visible content must use\n * logged channels; this seam cannot mutate messages. Injection here joins\n * the next request because the current step boundary is already fixed.\n * @param agent - the agent making the model call.\n * @param turn - the open turn number.\n * @param step - the step whose request this is.\n * @param config - the config the loop would use (frozen); return a replacement to switch.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
signature: '\'agent/request\'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, config: LlmCallConfig, signal: AbortSignal, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>',
|
||||
jsDoc: '/**\n * Replace the frozen call configuration. Model-visible content must use\n * logged channels; this seam cannot mutate messages. Injection here joins\n * the next request because the current step boundary is already fixed.\n * @param agent - the agent making the model call.\n * @param turn - the open turn number.\n * @param step - the step whose request this is.\n * @param config - the config the loop would use (frozen); return a replacement to switch.\n * @param signal - the current turn\'s explicit abort signal; ambient\n * initiator identity does not imply liveness or cancellation authority.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
summary: 'Replace the frozen call configuration.',
|
||||
},
|
||||
{
|
||||
@@ -793,7 +833,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
name: 'agent/session-prefix',
|
||||
mode: 'waterfall',
|
||||
signature: '\'agent/session-prefix\'(this: Scoped<Agent>, agent: Agent, prefix: Message[], signal: AbortSignal, next: () => Promise<Message[]>): Promise<Message[]>',
|
||||
jsDoc: '/**\n * Compose request-only messages placed before derived history. The frozen\n * result is computed once per loop instance, logged on its anchoring request\n * header, and reused so the provider prefix remains stable. Interrupted\n * composition is discarded. Composition precedes the first `agent/pre-step`\n * and request boundary, so listener appends join the current request.\n * Changing context belongs in history; contributors should prepend to\n * `await next()` to preserve registration order.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @param agent - the agent whose session prefix is being composed.\n * @param prefix - the frozen seed; return an extended replacement.\n * @param signal - aborts composition when the step is torn down.\n * @mode waterfall\n */',
|
||||
jsDoc: '/**\n * Compose request-only messages placed before derived history. The frozen\n * result is computed once per loop instance, logged on its anchoring request\n * header, and reused so the provider prefix remains stable. Interrupted\n * composition is discarded. Composition precedes the first `agent/pre-step`\n * and request boundary, so listener appends join the current request.\n * Changing context belongs in history; contributors should prepend to\n * `await next()` to preserve registration order.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @param agent - the agent whose session prefix is being composed.\n * @param prefix - the frozen seed; return an extended replacement.\n * @param signal - the current turn\'s explicit abort signal.\n * @mode waterfall\n */',
|
||||
summary: 'Compose request-only messages placed before derived history.',
|
||||
},
|
||||
{
|
||||
@@ -813,22 +853,22 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
{
|
||||
name: 'agent/step-result',
|
||||
mode: 'waterfall',
|
||||
signature: '\'agent/step-result\'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, message: Message, next: () => Promise<Message>): Promise<Message>',
|
||||
jsDoc: '/**\n * Waterfall: post-process the assembled assistant {@link Message} before\n * tool dispatch (validation, content rewriting, …).\n * @param agent - the agent that received the step\'s response.\n * @param turn - the open turn number.\n * @param step - the step that produced the message.\n * @param message - the assistant message as assembled from the stream.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
signature: '\'agent/step-result\'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, message: Message, signal: AbortSignal, next: () => Promise<Message>): Promise<Message>',
|
||||
jsDoc: '/**\n * Waterfall: post-process the assembled assistant {@link Message} before\n * tool dispatch (validation, content rewriting, …).\n * @param agent - the agent that received the step\'s response.\n * @param turn - the open turn number.\n * @param step - the step that produced the message.\n * @param message - the assistant message as assembled from the stream.\n * @param signal - the current turn\'s explicit abort signal.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
summary: 'Waterfall: post-process the assembled assistant Message before tool dispatch (validation, content rewriting, …).',
|
||||
},
|
||||
{
|
||||
name: 'agent/turn-continuation',
|
||||
mode: 'waterfall',
|
||||
signature: '\'agent/turn-continuation\'(this: Scoped<Agent>, agent: Agent, turn: number, defaultDecision: ContinuationDecision, next: () => Promise<ContinuationDecision>): Promise<ContinuationDecision>',
|
||||
jsDoc: '/**\n * Override whether the turn continues. The default continues after tool\n * calls or steering and stops otherwise; a continue reason becomes steering.\n * @param agent - the agent deciding whether to run another step.\n * @param turn - the turn being continued or stopped.\n * @param defaultDecision - what the loop would do absent an override.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
signature: '\'agent/turn-continuation\'(this: Scoped<Agent>, agent: Agent, turn: number, defaultDecision: ContinuationDecision, signal: AbortSignal, next: () => Promise<ContinuationDecision>): Promise<ContinuationDecision>',
|
||||
jsDoc: '/**\n * Override whether the turn continues. The default continues after tool\n * calls or steering and stops otherwise; a continue reason becomes steering.\n * @param agent - the agent deciding whether to run another step.\n * @param turn - the turn being continued or stopped.\n * @param defaultDecision - what the loop would do absent an override.\n * @param signal - the current turn\'s explicit abort signal.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode waterfall\n */',
|
||||
summary: 'Override whether the turn continues.',
|
||||
},
|
||||
{
|
||||
name: 'agent/turn-stop',
|
||||
mode: 'serial',
|
||||
signature: '\'agent/turn-stop\'(this: Scoped<Agent>, agent: Agent, turn: number): ContinuationStop | undefined',
|
||||
jsDoc: '/**\n * Monotonic terminal-stop checkpoint after continuation and steering are\n * folded; a stop remains authoritative through turn close and flush:\n * steering queued in that window is discarded, while ordinary sends survive.\n * @param agent - the agent whose composed continuation outcome may be stopped.\n * @param turn - the turn at its terminal-stop checkpoint.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode serial\n */',
|
||||
signature: '\'agent/turn-stop\'(this: Scoped<Agent>, agent: Agent, turn: number, signal: AbortSignal): Promise<ContinuationStop | undefined> | ContinuationStop | undefined',
|
||||
jsDoc: '/**\n * Monotonic terminal-stop checkpoint after continuation and steering are\n * folded; a stop remains authoritative through turn close and flush:\n * steering queued in that window is discarded, while ordinary sends survive.\n * @param agent - the agent whose composed continuation outcome may be stopped.\n * @param turn - the turn at its terminal-stop checkpoint.\n * @param signal - the current turn\'s explicit abort signal.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.\n * @mode serial\n */',
|
||||
summary: 'Monotonic terminal-stop checkpoint after continuation and steering are folded; a stop remains authoritative through turn close and flush: steering queued in that window is discarded, while ordinary sends survive.',
|
||||
},
|
||||
{
|
||||
@@ -877,7 +917,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
name: 'llm/stream',
|
||||
mode: 'waterfall',
|
||||
signature: '\'llm/stream\'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable<StreamChunk>): AsyncIterable<StreamChunk>',
|
||||
jsDoc: '/**\n * Waterfall around every streaming model call (retry, replay, routing).\n * Bound to the {@link LlmService}; call `next()` to reach the resolved\n * adapter\'s stream, or yield your own chunks to short-circuit.\n * @param options - the full request. A LOOP-built request arrives\n * deep-frozen (mutation throws): its content is a pure function of the\n * session log (the reconstructability Agent Note), so listeners read it, never\n * rewrite it. A hand-built one-shot (compaction summarize) is the\n * caller\'s own object and stays mutable here.\n * @mode waterfall\n */',
|
||||
jsDoc: '/**\n * Waterfall around every streaming model call (retry, replay, routing).\n * Bound to the {@link LlmService}; call `next()` to reach the resolved\n * adapter\'s stream, or yield your own chunks to short-circuit.\n * @param options - the full request. A LOOP-built request carries the\n * process-local {@link markAgentLoopRequest} identity and arrives deep-frozen\n * (mutation throws): its content is a pure function of the session log (the\n * reconstructability Agent Note), so listeners read it, never rewrite it.\n * Hand-built calls own their mutability policy and do not carry that marker.\n * @mode waterfall\n */',
|
||||
summary: 'Waterfall around every streaming model call (retry, replay, routing).',
|
||||
},
|
||||
{
|
||||
@@ -940,7 +980,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
name: 'system-prompt/assemble',
|
||||
mode: 'waterfall',
|
||||
signature: '\'system-prompt/assemble\'(this: Scoped<SystemPrompt>, assembly: PromptAssembly, context: AssembleContext, next: () => Promise<PromptAssembly>): Promise<PromptAssembly>',
|
||||
jsDoc: '/**\n * Expert waterfall over the assembled sections, tools, and variables.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners\n * receive only that scope\'s assemblies. The returned value is authoritative.\n * @param assembly - the mutable assembly built from registered providers.\n * @param context - the caller\'s per-assembly context.\n * @mode waterfall\n */',
|
||||
jsDoc: '/**\n * Expert waterfall over the assembled sections, tools, and variables.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners\n * receive only that scope\'s assemblies. The returned value is authoritative.\n * A supplied signal controls only this explicit assembly request and must not\n * be retained to control later turns.\n * @param assembly - the mutable assembly built from registered providers.\n * @param context - the caller\'s per-assembly context.\n * @mode waterfall\n */',
|
||||
summary: 'Expert waterfall over the assembled sections, tools, and variables.',
|
||||
},
|
||||
{
|
||||
@@ -960,22 +1000,22 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
{
|
||||
name: 'tools/execute',
|
||||
mode: 'waterfall',
|
||||
signature: '\'tools/execute\'(this: Scoped<ToolRegistry>, exec: ToolExecution, next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>',
|
||||
jsDoc: '/**\n * Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns\n * a normalized result; wrappers may change only `exec.signal`, while call\n * identity remains immutable.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent\'s calls.\n * @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal).\n * @mode waterfall\n */',
|
||||
signature: '\'tools/execute\'(this: Scoped<ToolRegistry>, exec: ToolDispatchExecution, next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>',
|
||||
jsDoc: '/**\n * Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns\n * a normalized result; wrappers may change only `exec.signal`, while call\n * identity remains immutable. The registry re-fuses the original caller\n * signal before the body, so replacement cannot detach caller cancellation;\n * wrappers must still restore their signal and reach quiescence.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent\'s calls.\n * @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal).\n * @mode waterfall\n */',
|
||||
summary: 'Around-dispatch waterfall for timeout, retry, or metrics.',
|
||||
},
|
||||
{
|
||||
name: 'tools/post-execute',
|
||||
mode: 'waterfall',
|
||||
signature: '\'tools/post-execute\'(this: Scoped<ToolRegistry>, exec: ToolExecution, result: Readonly<ToolExecutionResult>, next: () => Promise<PostToolDecision>): Promise<PostToolDecision>',
|
||||
jsDoc: '/**\n * Accept, replace, enrich, or block a normalized dispatch result. `next()`\n * accepts it unchanged; thrown tools still reach this seam as errors.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent\'s calls.\n * @param exec - the call that just ran (name, parsed arguments, caller agent).\n * @param result - the dispatch outcome a listener may accept, replace, or block.\n * @mode waterfall\n */',
|
||||
jsDoc: '/**\n * Accept, replace, enrich, or block a normalized dispatch result. `next()`\n * accepts it unchanged; thrown tools still reach this seam as errors. Async\n * listeners must observe `exec.signal`; after they settle, caller\n * cancellation replaces only a successful accepted outcome with the code\n * selected by whether the tool body was invoked.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent\'s calls.\n * @param exec - the call that just ran (name, parsed arguments, caller agent).\n * @param result - the dispatch outcome a listener may accept, replace, or block.\n * @mode waterfall\n */',
|
||||
summary: 'Accept, replace, enrich, or block a normalized dispatch result.',
|
||||
},
|
||||
{
|
||||
name: 'tools/pre-execute',
|
||||
mode: 'waterfall',
|
||||
signature: '\'tools/pre-execute\'(this: Scoped<ToolRegistry>, exec: ToolExecution, next: () => Promise<PreToolDecision>): Promise<PreToolDecision>',
|
||||
jsDoc: '/**\n * Allow, deny, or ask before dispatch. `next()` delegates to allow; missing\n * approval support turns `ask` into denial.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent\'s calls.\n * @param exec - the pending call (name, parsed arguments, caller agent).\n * @mode waterfall\n */',
|
||||
jsDoc: '/**\n * Allow, deny, or ask before dispatch. `next()` delegates to allow; missing\n * approval support turns `ask` into denial. Async gates must observe\n * `exec.signal`; the registry rechecks cancellation after they settle but\n * never abandons their promise.\n * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent\'s calls.\n * @param exec - the pending call (name, parsed arguments, caller agent).\n * @mode waterfall\n */',
|
||||
summary: 'Allow, deny, or ask before dispatch.',
|
||||
},
|
||||
{
|
||||
@@ -1033,7 +1073,11 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
{
|
||||
name: 'Agent',
|
||||
declaration: 'export interface Agent {\n readonly id: SessionId;\n readonly options: AgentOptions;\n readonly session: Session;\n readonly status: AgentStatus;\n readonly ctx: Context;\n send(content: ContentBlock[], options?: SendOptions): void;\n steer(content: ContentBlock[], options?: SendOptions): void;\n inject(content: ContentBlock[], options?: InjectOptions): void;\n cancel(reason?: string): void;\n whenIdle(): Promise<void>;\n}',
|
||||
declaration: 'export interface Agent {\n readonly id: SessionId;\n readonly options: AgentOptions;\n readonly session: Session;\n readonly status: AgentStatus;\n readonly ctx: Context;\n send(content: ContentBlock[], options?: SendOptions): void;\n steer(content: ContentBlock[], options?: SendOptions): void;\n inject(content: ContentBlock[], options?: InjectOptions): void;\n cancel(cause?: AgentCancelCause): void;\n whenIdle(): Promise<void>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AgentCancelCause',
|
||||
declaration: 'export type AgentCancelCause = {\n readonly kind: \'user\';\n} | {\n readonly kind: \'parent\';\n};',
|
||||
},
|
||||
{
|
||||
name: 'AgentFactory',
|
||||
@@ -1073,7 +1117,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'AskUserQuestionItem',
|
||||
declaration: 'export interface AskUserQuestionItem {\n id: string;\n question: string;\n header?: string;\n options?: AskUserQuestionOption[];\n multiSelect?: boolean;\n}',
|
||||
declaration: 'export interface AskUserQuestionItem {\n id: string;\n question: string;\n detail?: string;\n header?: string;\n options?: AskUserQuestionOption[];\n multiSelect?: boolean;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AskUserQuestionOption',
|
||||
@@ -1085,7 +1129,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'AssembleContext',
|
||||
declaration: 'export interface AssembleContext {\n scope?: ScopeKey;\n}',
|
||||
declaration: 'export interface AssembleContext {\n scope?: ScopeKey;\n signal?: AbortSignal;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AssembledSection',
|
||||
@@ -1309,7 +1353,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'GenerateOptions',
|
||||
declaration: 'export interface GenerateOptions {\n provider: string;\n model: string;\n messages: Message[];\n system?: string;\n tools?: ToolSchema[];\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<\'SessionId\'>;\n}',
|
||||
declaration: 'export interface GenerateOptions {\n provider: string;\n model: string;\n messages: Message[];\n system?: string;\n tools?: ToolSchema[];\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<\'SessionId\'>;\n purpose?: \'compaction\';\n}',
|
||||
},
|
||||
{
|
||||
name: 'GenericCallView',
|
||||
@@ -1399,6 +1443,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'MessageSourceMap',
|
||||
declaration: 'export interface MessageSourceMap {\n user: {\n kind: \'user\';\n };\n plugin: {\n kind: \'plugin\';\n plugin: string;\n };\n}',
|
||||
},
|
||||
{
|
||||
name: 'OutOfBandSessionEventMap',
|
||||
declaration: 'export interface OutOfBandSessionEventMap {\n}',
|
||||
},
|
||||
{
|
||||
name: 'OutOfBandSessionEventType',
|
||||
declaration: 'export type OutOfBandSessionEventType = Exclude<Extract<SessionEventType, keyof OutOfBandSessionEventMap>, SurfaceEventType>;',
|
||||
},
|
||||
{
|
||||
name: 'PresetOption',
|
||||
declaration: 'export interface PresetOption {\n value: string;\n name: string;\n description?: string;\n}',
|
||||
@@ -1531,6 +1583,46 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'SessionRecord',
|
||||
declaration: 'export interface SessionRecord {\n header: SessionHeader;\n live: boolean;\n persisted: boolean;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleAutomaticMode',
|
||||
declaration: 'export type SessionTitleAutomaticMode = \'first-message\' | \'all-user-messages\';',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleEventData',
|
||||
declaration: 'export interface SessionTitleEventData {\n readonly title: string;\n readonly messageSeqs: number[];\n readonly source: SessionTitleSource;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleModelProvenance',
|
||||
declaration: 'export interface SessionTitleModelProvenance {\n readonly provider: string;\n readonly model: string;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleProvider',
|
||||
declaration: 'export interface SessionTitleProvider {\n readonly id: SessionTitleProviderId;\n readonly automatic: SessionTitleAutomaticMode;\n generate(request: SessionTitleProviderRequest): Promise<SessionTitleProviderResult>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleProviderId',
|
||||
declaration: 'export type SessionTitleProviderId = Branded<\'SessionTitleProviderId\'>;',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleProviderRequest',
|
||||
declaration: 'export interface SessionTitleProviderRequest {\n readonly session: Session;\n readonly messages: readonly SessionTitleUserMessage[];\n readonly route?: SessionTitleModelProvenance;\n readonly signal: AbortSignal;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleProviderResult',
|
||||
declaration: 'export interface SessionTitleProviderResult {\n readonly title: string;\n readonly messageSeqs: readonly number[];\n readonly model?: SessionTitleModelProvenance;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleSnapshot',
|
||||
declaration: 'export interface SessionTitleSnapshot extends SessionTitleEventData {\n readonly eventSeq: number;\n readonly updatedAt: number;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleSource',
|
||||
declaration: 'export type SessionTitleSource = {\n readonly kind: \'fallback\';\n} | {\n readonly kind: \'provider\';\n readonly provider: SessionTitleProviderId;\n readonly model?: SessionTitleModelProvenance;\n};',
|
||||
},
|
||||
{
|
||||
name: 'SessionTitleUserMessage',
|
||||
declaration: 'export interface SessionTitleUserMessage {\n readonly seq: number;\n readonly text: string;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SkillCandidate',
|
||||
declaration: 'export interface SkillCandidate extends SkillSummary {\n readonly rank: number;\n readonly locator: unknown;\n readonly path?: string;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}',
|
||||
@@ -1729,7 +1821,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'ToolExecutionInput',
|
||||
declaration: 'export interface ToolExecutionInput {\n readonly callId: CallId;\n readonly name: string;\n readonly arguments: unknown;\n readonly agent?: Agent;\n readonly parent?: ToolExecutionToken;\n signal?: AbortSignal;\n}',
|
||||
declaration: 'export interface ToolExecutionInput {\n readonly callId: CallId;\n readonly name: string;\n readonly arguments: unknown;\n readonly agent?: Agent;\n readonly parent?: ToolExecutionToken;\n readonly signal: AbortSignal;\n}',
|
||||
},
|
||||
{
|
||||
name: 'ToolExecutionMode',
|
||||
@@ -1781,7 +1873,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'TurnEndReasonMap',
|
||||
declaration: 'export interface TurnEndReasonMap {\n completed: {\n kind: \'completed\';\n };\n aborted: {\n kind: \'aborted\';\n reason?: string;\n };\n error: {\n kind: \'error\';\n step: number;\n } & ({\n failure: LlmFailure;\n message?: never;\n code?: never;\n } | {\n message: string;\n code?: string;\n failure?: never;\n });\n disposed: {\n kind: \'disposed\';\n };\n \'max-tokens\': {\n kind: \'max-tokens\';\n };\n rejected: {\n kind: \'rejected\';\n reason: string;\n };\n interrupted: {\n kind: \'interrupted\';\n };\n}',
|
||||
declaration: 'export interface TurnEndReasonMap {\n completed: {\n kind: \'completed\';\n };\n aborted: {\n kind: \'aborted\';\n };\n error: {\n kind: \'error\';\n step: number;\n } & ({\n failure: LlmFailure;\n message?: never;\n code?: never;\n } | {\n message: string;\n code?: string;\n failure?: never;\n });\n disposed: {\n kind: \'disposed\';\n };\n \'max-tokens\': {\n kind: \'max-tokens\';\n };\n rejected: {\n kind: \'rejected\';\n reason: string;\n };\n interrupted: {\n kind: \'interrupted\';\n };\n}',
|
||||
},
|
||||
{
|
||||
name: 'TurnTrigger',
|
||||
|
||||
@@ -6,6 +6,8 @@ import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import type { ToolDefinition, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
|
||||
import * as tool from '../src/index.ts'
|
||||
|
||||
const testToolSignal = new AbortController().signal
|
||||
|
||||
/**
|
||||
* Shared spec helpers: a real `SystemPrompt` + `ToolRegistry` + timer +
|
||||
* tool-cordis tree (only the model is absent — the code strings below stand in
|
||||
@@ -27,7 +29,7 @@ let callCounter = 0
|
||||
|
||||
/** Execute a registered tool through the real registry pipeline. */
|
||||
export function call(ctx: Context, name: string, args: unknown): Promise<ToolExecutionResult> {
|
||||
return ctx.tools.execute({ callId: CallId(`call-${++callCounter}`), name, arguments: args })
|
||||
return ctx.tools.execute({ signal: testToolSignal, callId: CallId(`call-${++callCounter}`), name, arguments: args })
|
||||
}
|
||||
|
||||
/** Concatenated text blocks of one tool result. */
|
||||
|
||||
Reference in New Issue
Block a user