dsh-loop-engine 0.1.5-rc1 → 0.1.5-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +88 -5
  2. package/README.zh.md +74 -0
  3. package/lib/client.js +158 -0
  4. package/lib/index.js +1020 -1486
  5. package/lib/invariant.js +6 -3
  6. package/lib/types/client/turn-status.d.ts +43 -0
  7. package/lib/types/driver-core/agents-md-skill-provider.d.ts +72 -0
  8. package/lib/types/driver-core/hosted-loop-factory.d.ts +119 -0
  9. package/lib/types/driver-core/ownership.d.ts +6 -6
  10. package/lib/types/driver-core/permission-knobs.d.ts +1 -1
  11. package/lib/types/driver-core/prompt.d.ts +1 -1
  12. package/lib/types/driver-core/skill-inject.d.ts +2 -2
  13. package/lib/types/engine-claude/agent.d.ts +22 -0
  14. package/lib/types/engine-claude/loop.d.ts +7 -56
  15. package/lib/types/engine-claude/mapping.d.ts +28 -3
  16. package/lib/types/engine-codex/agent.d.ts +46 -3
  17. package/lib/types/engine-codex/appserver/client.d.ts +16 -0
  18. package/lib/types/engine-codex/loop.d.ts +7 -56
  19. package/lib/types/engine-codex/permission.d.ts +100 -6
  20. package/lib/types/engine-codex/skills.d.ts +7 -8
  21. package/lib/types/engine-kimi/acp/client.d.ts +33 -3
  22. package/lib/types/engine-kimi/acp/mapping.d.ts +36 -7
  23. package/lib/types/engine-kimi/acp/types.d.ts +41 -2
  24. package/lib/types/engine-kimi/agent.d.ts +65 -8
  25. package/lib/types/engine-kimi/loop.d.ts +7 -56
  26. package/lib/types/engine-kimi/skills.d.ts +9 -14
  27. package/lib/types/engine-pi/agent.d.ts +23 -4
  28. package/lib/types/engine-pi/loop.d.ts +7 -56
  29. package/lib/types/engine-pi/permission.d.ts +16 -12
  30. package/lib/types/engine-pi/skills.d.ts +6 -14
  31. package/lib/types/patch-manager.d.ts +7 -0
  32. package/lib/types/provider-route.d.ts +16 -7
  33. package/lib/types/settings.d.ts +4 -1
  34. package/lib/types/skills.d.ts +6 -14
  35. package/package.json +1 -1
package/lib/invariant.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  var LOOP_ENGINE_IDS = ["in-process", "claude-code", "codex", "pi", "kimi"];
4
4
  var LOOP_ENGINE_SETTINGS_SCHEMA = z.object({
5
- engine: z.union([z.const("in-process"), z.const("claude-code"), z.const("codex"), z.const("pi"), z.const("kimi")]).default("in-process"),
5
+ engine: z.union(LOOP_ENGINE_IDS.map((id) => z.const(id))).default("in-process"),
6
6
  showInComposer: z.boolean().default(true)
7
7
  });
8
8
 
@@ -23,9 +23,12 @@ function renderManagedBlock(engine) {
23
23
  ].join("\n");
24
24
  }
25
25
  var BEGIN_MARKER_RE = /^# -- dsh-loop-engine managed block: (\S+) --$/m;
26
- function currentEngineOf(text) {
26
+ function managedBlockEngineOf(text) {
27
27
  const engine = BEGIN_MARKER_RE.exec(text)?.[1];
28
- return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : "in-process";
28
+ return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : void 0;
29
+ }
30
+ function currentEngineOf(text) {
31
+ return managedBlockEngineOf(text) ?? "in-process";
29
32
  }
30
33
  function managedSpan(text) {
31
34
  const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Per-engine styling for the chat turn-status line (the "深度求索中..." row).
3
+ *
4
+ * That row is rendered from inside the harness's ChatView and its words belong
5
+ * to ui-chat: the row is not a slot, and ui-chat owns the `chat` locale
6
+ * namespace (a second `locale.register` for the same namespace throws), so a
7
+ * plugin cannot change the text. What it CAN do is restyle the element, and
8
+ * that is all this module does — it paints an engine-specific glyph and color
9
+ * onto the row while a hosted engine is selected, and leaves the stock look
10
+ * alone otherwise.
11
+ *
12
+ * Three facts about the harness markup make that safe and specific:
13
+ * - the row carries exactly one class whose `[hash]_turnStatus` suffix is
14
+ * stable across rebuilds (the hash in front is not), so
15
+ * `[class$="_turnStatus"]` matches it and not the sibling clock;
16
+ * - its gradient paints through the `--dsw-static-deepseek-*` custom
17
+ * properties, so recoloring is a variable override rather than a fight
18
+ * over `background` and `background-clip`;
19
+ * - the glyph rides a `::before` pseudo-element, so the row's real text node
20
+ * — and the status it announces — is untouched.
21
+ *
22
+ * This sheet deliberately keeps the row animating even when the OS reports
23
+ * `prefers-reduced-motion: reduce` (see the re-asserted sweep below): the
24
+ * deployment's Windows images ship with client-area animation off, which
25
+ * otherwise freezes every indicator here — the sweep and the glyph alike.
26
+ * In-process sessions keep the stock reduced-motion behaviour.
27
+ *
28
+ * @module dsh-loop-engine/client/turn-status
29
+ */
30
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
31
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
32
+ import type { LoopEngineState } from './store.ts';
33
+ /**
34
+ * Install the per-engine turn-status styling for the lifetime of `ctx`.
35
+ *
36
+ * Keyed off the shared controller store, so it follows a live engine switch
37
+ * exactly as the settings section and header chip do; the store is read rather
38
+ * than the settings scope so all three surfaces cannot disagree.
39
+ * @param ctx - the client root context.
40
+ * @param store - the loop-engine controller's snapshot source.
41
+ */
42
+ export declare function installTurnStatusStyles(ctx: ClientContext, store: SnapshotStore<LoopEngineState>): void;
43
+ //# sourceMappingURL=turn-status.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Data-driven skill provider for the hosted engines whose discovery surface is
3
+ * "per-directory instruction files plus a skills catalog".
4
+ *
5
+ * Codex, Pi, and Kimi Code each expose the same two things through the dsh
6
+ * skill-injection seam: an `agents-md` candidate merging the per-directory
7
+ * instruction files found between the session cwd and the git root, and one
8
+ * candidate per `SKILL.md` catalog entry. Only the *locations*, *names*, and
9
+ * *precedence ranks* differ — never the algorithm — so those are the entire
10
+ * configuration surface here, and each engine module supplies one
11
+ * {@link AgentsMdProviderSpec} and a thin subclass.
12
+ *
13
+ * `.agents/skills` roots are deliberately absent from every engine's spec:
14
+ * dsh's own `skill-filesystem` provider already serves them through the same
15
+ * registry in the web profile.
16
+ *
17
+ * @module dsh-loop-engine/driver-core/agents-md-skill-provider
18
+ */
19
+ import { type ContextFilePolicy } from './context-files.ts';
20
+ import type { SkillCandidate, SkillDefinition, SkillLookupOptions, SkillProvider, SkillProviderControl } from '../skills.ts';
21
+ /** A skills catalog: a per-ancestor project directory plus one user-level directory. */
22
+ export interface SkillCatalogSpec {
23
+ /** Directory path, relative to each ancestor of the session cwd. */
24
+ readonly project: readonly string[];
25
+ /** Rank of project catalog entries — project instructions beat project skills. */
26
+ readonly projectRank: number;
27
+ /** Directory name under the user install root. */
28
+ readonly userDir: string;
29
+ /** Rank of user catalog entries — project files win duplicate names. */
30
+ readonly userRank: number;
31
+ }
32
+ /** Everything that varies between the codex, pi, and kimi discovery surfaces. */
33
+ export interface AgentsMdProviderSpec {
34
+ /** Provider identity registered against the host skills service. */
35
+ readonly name: string;
36
+ /** Description of the merged `agents-md` candidate. */
37
+ readonly agentsMdDescription: string;
38
+ /** Per-directory instruction files this engine reads, and its override. */
39
+ readonly contextPolicy: ContextFilePolicy;
40
+ /** The engine's user-level install root. */
41
+ readonly userDir: () => string;
42
+ /** Rank of the project `agents-md` candidate. */
43
+ readonly projectRank: number;
44
+ /** User-level instruction file inside {@link userDir}, when the engine reads one. */
45
+ readonly userContext?: {
46
+ readonly file: string;
47
+ readonly rank: number;
48
+ };
49
+ /** Skill catalogs, when the engine has any. */
50
+ readonly skills?: SkillCatalogSpec;
51
+ }
52
+ /**
53
+ * Skill provider that discovers an engine's per-directory instruction files and
54
+ * `SKILL.md` catalogs from the locations its spec names.
55
+ */
56
+ export declare class AgentsMdSkillProvider implements SkillProvider {
57
+ private readonly spec;
58
+ private readonly control;
59
+ readonly name: string;
60
+ constructor(spec: AgentsMdProviderSpec, control: SkillProviderControl);
61
+ list(options: SkillLookupOptions): Promise<readonly SkillCandidate[]>;
62
+ get(candidate: SkillCandidate, _options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
63
+ /** One merged `agents-md` candidate for a ranked file set. */
64
+ private agentsCandidate;
65
+ /** Collect every skill in one skills directory, both nesting layouts. */
66
+ private collectSkillsDir;
67
+ /** One parsed skill as a ranked candidate. */
68
+ private skillCandidate;
69
+ /** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
70
+ private tryParse;
71
+ }
72
+ //# sourceMappingURL=agents-md-skill-provider.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Shared AgentFactory transaction machinery for the hosted loop engines.
3
+ *
4
+ * All four engines (Claude Code, Codex, Pi, Kimi Code) implement the harness's
5
+ * single AgentFactory slot the same way: prepare a driver, scope, and one
6
+ * memoized reverse teardown for a session; run the caller's setup under a fused
7
+ * abort signal; publish through both registries and announce; and on resume,
8
+ * own a session's write handle across the cold read, crash repair, and
9
+ * re-publication. None of that touches an engine protocol — the whole
10
+ * engine-specific surface is one call, {@link HostedLoopFactory.buildAgent}.
11
+ *
12
+ * This body mirrors the default in-process `agent-loop` factory; depending on
13
+ * the `dsh-agent-loop` package is forbidden (it would claim the slot this
14
+ * plugin exists to hand over), so the machinery is replicated here once instead
15
+ * of four times.
16
+ *
17
+ * Subclasses supply:
18
+ * - the cordis service label, which is also the prefix of every effect label
19
+ * (`<label>.transactions()`, `<label>.setFactory()`, `<label>.lifecycle(id)`,
20
+ * `<label>.resume-load(id)`) — the lifecycle label is asserted by tests, so
21
+ * it must stay `<label>.lifecycle(...)`;
22
+ * - their own `static inject` (Codex needs no `subprocess`);
23
+ * - {@link HostedLoopFactory.buildAgent}.
24
+ *
25
+ * @module dsh-loop-engine/driver-core/hosted-loop-factory
26
+ */
27
+ import { Service } from '@deepseek-ai/cordis';
28
+ import type { Context } from '@deepseek-ai/cordis';
29
+ import type { Agent, AgentFactory, AgentHandle, AgentOptions, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
30
+ import { SessionId } from '@deepseek-ai/dsh-session';
31
+ import type { Session } from '@deepseek-ai/dsh-session';
32
+ import type { Scope } from '@deepseek-ai/dsh-scope';
33
+ /**
34
+ * What the transaction machinery needs of a driver beyond the harness `Agent`
35
+ * contract: a teardown entry point that also unwinds the driver's own scope.
36
+ * The concrete engines declare these; the base only spells them out so the
37
+ * shared body can call them generically.
38
+ */
39
+ export interface HostedAgent extends Agent {
40
+ /** Stop the machine with the given cause, without waiting for it to settle. */
41
+ cancel(cause: Parameters<Agent['cancel']>[0]): void;
42
+ /** Resolves when the machine has no work in flight. */
43
+ whenIdle(): Promise<void>;
44
+ /** The driver's own resource scope, unwound after the machine settles. */
45
+ readonly scope: Scope;
46
+ }
47
+ /**
48
+ * Concrete AgentFactory base for one hosted engine. Creation and resume follow
49
+ * the registry factory contract and the shared publication transaction:
50
+ * prepare, run setup, then publish through both registries, announce, and emit
51
+ * `agent/session-start`.
52
+ */
53
+ export declare abstract class HostedLoopFactory<TConfig, TAgent extends HostedAgent> extends Service implements AgentFactory {
54
+ /** Validated configuration owned by the loop plugin. */
55
+ readonly config: TConfig;
56
+ private readonly ownership;
57
+ /** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
58
+ protected readonly runtime: {
59
+ ctx: Context;
60
+ };
61
+ /** Cordis service name, also the prefix of every effect label. */
62
+ private readonly label;
63
+ constructor(ctx: Context, label: string, config: TConfig);
64
+ /**
65
+ * Construct this engine's driver for one prepared session. Called once per
66
+ * create or resume, after the session exists and before setup runs; the
67
+ * hook is the engine's entire protocol surface.
68
+ */
69
+ protected abstract buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): TAgent;
70
+ /**
71
+ * Construct the driver, scope, and one memoized reverse teardown for a new
72
+ * agent. The teardown is registered with the factory and the owner fiber
73
+ * BEFORE publication, so a mid-setup unload rolls everything back; `signal`
74
+ * fuses caller cancellation with lifecycle teardown for setup awaits.
75
+ */
76
+ private prepare;
77
+ /** Prepare one Agent around an acquired Session, run setup, and publish it. */
78
+ private setupAndPublish;
79
+ /**
80
+ * Create an agent and session under one caller-supplied identity, owned by
81
+ * the accessing fiber. When a persistence backend is mounted, the session's
82
+ * durable identity is stored before publication.
83
+ * @param ownerCtx - caller context that structurally owns the lifecycle.
84
+ * @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
85
+ * @returns the published handle.
86
+ */
87
+ createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
88
+ /**
89
+ * Take a fresh session's write ownership when persistence is mounted.
90
+ * Nothing is appended here: the constructor seed (which never re-emits
91
+ * through `session/event`) is stored by {@link appendUnstoredSuffix} at the
92
+ * publication commit point, so a failed or cancelled setup closes an
93
+ * unmaterialized handle and leaves no stored residue — the same id can be
94
+ * created again.
95
+ * @param session - the unpublished session to store.
96
+ * @param signal - optional cancellation forwarded to the backend create.
97
+ * @returns the owned handle and stored cursor, or `undefined` without a backend.
98
+ */
99
+ private createStoredSession;
100
+ /**
101
+ * Durably store the session events appended since the last stored cursor.
102
+ * Pre-publication appends (constructor seed markers, setup-window events)
103
+ * never re-emit through `session/event`, so publication must flush them
104
+ * through the handle before live events start routing into it.
105
+ * @param stored - the session's owned handle and stored cursor, if any.
106
+ * @param session - the unpublished session whose suffix is stored.
107
+ */
108
+ private appendUnstoredSuffix;
109
+ /**
110
+ * Resume an owned agent from the configured persistence service.
111
+ * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
112
+ * @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
113
+ * @returns the published handle.
114
+ */
115
+ resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
116
+ /** Resume through an explicit persistence service. */
117
+ private resumeWith;
118
+ }
119
+ //# sourceMappingURL=hosted-loop-factory.d.ts.map
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Shared factory ownership and abort-race machinery for the hosted engines.
3
- * Both the Claude Code and Codex loop drivers run the same lifecycle: exactly
4
- * one factory owns the AgentFactory slot, every live agent's teardown is
5
- * tracked until it settles, and setup awaits are raced against a fused abort
6
- * signal. These helpers are engine-free — they only touch the fiber state,
7
- * the session id type, and an AbortController — so the two loop modules share
8
- * them verbatim.
3
+ * All four loop drivers (Claude Code, Codex, Pi, Kimi Code) run the same
4
+ * lifecycle: exactly one factory owns the AgentFactory slot, every live
5
+ * agent's teardown is tracked until it settles, and setup awaits are raced
6
+ * against a fused abort signal. These helpers are engine-free — they only
7
+ * touch the fiber state, the session id type, and an AbortController — so the
8
+ * loop modules share them verbatim.
9
9
  *
10
10
  * @module dsh-loop-engine/driver-core/ownership
11
11
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Reading the dsh session's durable permission knobs from the session log.
3
- * Both the Claude Code and Codex drivers fold the same `sandbox/mode` and
3
+ * Every hosted driver folds the same `sandbox/mode` and
4
4
  * `approval/policy` events (pinned at creation, re-recorded on every switch)
5
5
  * into per-query permission decisions; the knob readers are engine-free.
6
6
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Serialization of the durable session history into the prompt text of one
3
- * hosted-engine query. Both the Claude Code and Codex drivers build their
3
+ * hosted-engine query. Every hosted driver builds its
4
4
  * per-step input from the durable session log: the transcript is the log's
5
5
  * exact projection, so a later replay of the same log derives the identical
6
6
  * prompt (Model-visible ⟺ logged bridge).
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Skill-injection helpers shared by the hosted engine drivers. Both the Claude
3
- * Code and Codex agents replicate the dsh `/name` skill gesture scan and the
2
+ * Skill-injection helpers shared by the hosted engine drivers. Each hosted
3
+ * engine's agent replicates the dsh `/name` skill gesture scan and the
4
4
  * XML `<skill_content>` rendering that the in-process engine's dsh-tool-skill
5
5
  * handler would otherwise provide — their agent contexts do not descend from
6
6
  * the agent-preset chain. These helpers are pure: they take user messages or a
@@ -33,6 +33,17 @@ export declare class ClaudeCodeAgent implements Agent {
33
33
  private requestHeaderLogged;
34
34
  /** Agent-lifecycle-local counter naming each streamed attempt. */
35
35
  private streamAttempts;
36
+ /**
37
+ * Tool results logged into the currently open step. A result means the
38
+ * segment that requested the call is finished, so the next assistant content
39
+ * opens the next step (see {@link beginSegment}).
40
+ */
41
+ private stepSettledTools;
42
+ /**
43
+ * Whether the current query has rotated into a second step. The query-total
44
+ * usage record is only meaningful while one step holds the whole query.
45
+ */
46
+ private rotated;
36
47
  constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig);
37
48
  get status(): AgentStatus;
38
49
  /** Commit a phase and publish its externally visible status transition. */
@@ -96,6 +107,17 @@ export declare class ClaudeCodeAgent implements Agent {
96
107
  private queryPermission;
97
108
  /** Open one turn before claiming its first proposed step. */
98
109
  private turn;
110
+ /**
111
+ * Rotate to the next step when the segment that ran a tool has finished, so
112
+ * each assistant segment lands in its own step.
113
+ *
114
+ * Called as new assistant content begins. A step holding a settled tool
115
+ * result means the previous segment is complete, and the content about to be
116
+ * written belongs to the next one. Rotating here (rather than when a call is
117
+ * announced) keeps calls announced together — one model turn — in one step.
118
+ * @param phase - the running phase carrying the open step's position.
119
+ */
120
+ private beginSegment;
99
121
  /** Model label recorded in the request header for one lifecycle. */
100
122
  private modelLabel;
101
123
  /** Append the request header snapshot once per loop instance. */
@@ -7,11 +7,13 @@
7
7
  *
8
8
  * @module dsh-loop-engine/engine-claude
9
9
  */
10
- import { Service } from '@deepseek-ai/cordis';
11
10
  import type { Context } from '@deepseek-ai/cordis';
12
11
  import z from '@deepseek-ai/schemastery';
13
- import type { AgentFactory, AgentHandle, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
12
+ import type { AgentOptions } from '@deepseek-ai/dsh-agent';
13
+ import type { Session, SessionId } from '@deepseek-ai/dsh-session';
14
+ import { ClaudeCodeAgent } from './agent.ts';
14
15
  import type { ClaudeCodePermissionMode, ResolvedConfig } from './types.ts';
16
+ import { HostedLoopFactory } from '../driver-core/hosted-loop-factory.ts';
15
17
  /** Deployment-selectable non-interactive Claude Code permission modes. */
16
18
  export declare const CLAUDE_CODE_PERMISSION_MODES: readonly ClaudeCodePermissionMode[];
17
19
  /** Deployment-owned configuration for the Claude Code loop plugin. */
@@ -50,62 +52,11 @@ declare module '@deepseek-ai/cordis' {
50
52
  * transaction: prepare, run setup, then publish through both registries,
51
53
  * announce, and emit `agent/session-start`.
52
54
  */
53
- export declare class ClaudeCodeLoop extends Service implements AgentFactory {
55
+ export declare class ClaudeCodeLoop extends HostedLoopFactory<ResolvedConfig, ClaudeCodeAgent> {
54
56
  /** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
55
57
  static inject: string[];
56
- /** Validated configuration owned by the loop plugin. */
57
- readonly config: ResolvedConfig;
58
- private readonly ownership;
59
- /** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
60
- private readonly runtime;
61
58
  constructor(ctx: Context, config: Config);
62
- /**
63
- * Construct the driver, scope, and one memoized reverse teardown for a new
64
- * agent. The teardown is registered with the factory and the owner fiber
65
- * BEFORE publication, so a mid-setup unload rolls everything back; `signal`
66
- * fuses caller cancellation with lifecycle teardown for setup awaits.
67
- */
68
- private prepare;
69
- /** Prepare one Agent around an acquired Session, run setup, and publish it. */
70
- private setupAndPublish;
71
- /**
72
- * Create an agent and session under one caller-supplied identity, owned by
73
- * the accessing fiber. When a persistence backend is mounted, the session's
74
- * durable identity is stored before publication.
75
- * @param ownerCtx - caller context that structurally owns the lifecycle.
76
- * @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
77
- * @returns the published handle.
78
- */
79
- createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
80
- /**
81
- * Take a fresh session's write ownership when persistence is mounted.
82
- * Nothing is appended here: the constructor seed (which never re-emits
83
- * through `session/event`) is stored by {@link appendUnstoredSuffix} at the
84
- * publication commit point, so a failed or cancelled setup closes an
85
- * unmaterialized handle and leaves no stored residue — the same id can be
86
- * created again.
87
- * @param session - the unpublished session to store.
88
- * @param signal - optional cancellation forwarded to the backend create.
89
- * @returns the owned handle and stored cursor, or `undefined` without a backend.
90
- */
91
- private createStoredSession;
92
- /**
93
- * Durably store the session events appended since the last stored cursor.
94
- * Pre-publication appends (constructor seed markers, setup-window events)
95
- * never re-emit through `session/event`, so publication must flush them
96
- * through the handle before live events start routing into it.
97
- * @param stored - the session's owned handle and stored cursor, if any.
98
- * @param session - the unpublished session whose suffix is stored.
99
- */
100
- private appendUnstoredSuffix;
101
- /**
102
- * Resume an owned agent from the configured persistence service.
103
- * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
104
- * @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
105
- * @returns the published handle.
106
- */
107
- resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
108
- /** Resume through an explicit persistence service. */
109
- private resumeWith;
59
+ /** Construct the Claude Code driver for one prepared session. */
60
+ protected buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): ClaudeCodeAgent;
110
61
  }
111
62
  //# sourceMappingURL=loop.d.ts.map
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module dsh-loop-engine/engine-claude/mapping
8
8
  */
9
- import type { BetaMessage, BetaRawMessageStreamEvent, BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs';
9
+ import type { BetaMessage, BetaRawMessageStreamEvent } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs';
10
10
  import type { MessageParam } from '@anthropic-ai/sdk/resources';
11
11
  import { ToolCallId, type ContentBlock, type StreamChunk, type TokenUsage, type ToolResultMessage } from '@deepseek-ai/dsh-llm';
12
12
  /** One tool invocation surfaced from a Claude Code assistant message. */
@@ -54,13 +54,38 @@ export declare function mapAssistantMessage(message: BetaMessage): MappedAssista
54
54
  * @returns the mapped tool-result messages, in block order.
55
55
  */
56
56
  export declare function mapToolResults(message: MessageParam): ToolResultMessage[];
57
+ /**
58
+ * The counters {@link mapUsage} reads. The SDK reports these on an assistant
59
+ * message's `usage`, on a stream `message_delta`'s narrower usage, and on the
60
+ * query `result`'s totals, so the parameter is the common shape rather than any
61
+ * one of those types.
62
+ */
63
+ export interface ReportedUsage {
64
+ readonly input_tokens: number | null;
65
+ readonly output_tokens: number | null;
66
+ readonly cache_read_input_tokens?: number | null;
67
+ readonly cache_creation_input_tokens?: number | null;
68
+ }
57
69
  /**
58
70
  * Translate SDK token accounting into the dsh token-usage shape. Cache
59
71
  * breakpoints are optional; absent or null SDK counters stay absent.
60
- * @param usage - SDK-reported usage for one assistant message.
72
+ * @param usage - SDK-reported usage for one assistant message, stream delta, or query result.
61
73
  * @returns dsh token accounting, omitting absent optional counters.
62
74
  */
63
- export declare function mapUsage(usage: BetaUsage): TokenUsage;
75
+ export declare function mapUsage(usage: ReportedUsage): TokenUsage;
76
+ /**
77
+ * Keep a usage sample only when it accounts for something.
78
+ *
79
+ * The SDK zero-fills `usage` on streamed assistant messages it cannot attribute
80
+ * (gateway-fronted models report the real counters on the stream's
81
+ * `message_delta` and the query's `result` instead). A sample with every bucket
82
+ * at zero is that placeholder, not a measurement: keeping it would both show an
83
+ * empty usage row and — because the token meter trusts a present sample over
84
+ * the stream — hide the real one.
85
+ * @param usage - a mapped usage sample, or undefined.
86
+ * @returns the sample when it is non-empty, otherwise undefined.
87
+ */
88
+ export declare function meaningfulUsage(usage: TokenUsage | undefined): TokenUsage | undefined;
64
89
  /** Per-block-index tool-call identity captured at `content_block_start`, reused by `input_json_delta`. */
65
90
  export interface StreamToolCall {
66
91
  readonly callId: ToolCallId;
@@ -5,9 +5,11 @@
5
5
  * the source of truth and the thread input is a pure serialization of it.
6
6
  * The app-server streams token-level deltas via `item/agentMessage/delta` and
7
7
  * `item/reasoning/summaryTextDelta`, so the visible partial paints
8
- * progressively as the model generates — not all at once at the end. It offers
9
- * no interactive approval callback, so permissions are folded declaratively
10
- * into each thread's `sandboxMode`/`approvalPolicy`.
8
+ * progressively as the model generates — not all at once at the end. Native
9
+ * approval requests (the `item/…/requestApproval` family) are server-initiated
10
+ * requests, not notifications: the client answers them through the dsh approval
11
+ * seam (`agent/requestPermission` → `ctx.approval`), while the thread still
12
+ * starts with a declarative `sandboxMode`/`approvalPolicy` stance.
11
13
  *
12
14
  * @module dsh-loop-engine/engine-codex/agent
13
15
  */
@@ -38,11 +40,52 @@ export declare class CodexAgent implements Agent {
38
40
  private requestHeaderLogged;
39
41
  /** Agent-lifecycle-local counter naming each streamed attempt. */
40
42
  private streamAttempts;
43
+ /**
44
+ * Tool results logged into the currently open step. A result means the
45
+ * segment that requested the call is finished, so the next assistant content
46
+ * opens the next step (see {@link beginSegment}).
47
+ */
48
+ private stepSettledTools;
49
+ /**
50
+ * Rotate to the next step when the segment that ran a tool has finished, so
51
+ * each assistant segment lands in its own step.
52
+ *
53
+ * Called as new assistant content begins. A step holding a settled tool
54
+ * result means the previous segment is complete, and the content about to be
55
+ * written belongs to the next one. Rotating here (rather than when a call is
56
+ * announced) keeps calls announced together — one model turn — in one step.
57
+ * @param phase - the running phase carrying the open step's position.
58
+ */
59
+ private beginSegment;
41
60
  /** Lazily created app-server client, reused across steps and released on scope teardown. */
42
61
  private appServer;
43
62
  constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig);
44
63
  /** Return the cached app-server client, spawning one on first use or after a dead process. */
45
64
  private appServerClient;
65
+ /**
66
+ * Answer one server-initiated interaction. Approvals go through the dsh
67
+ * approval seam; a `request_user_input` question goes to the user-questions
68
+ * seam (both fail closed when their seam is absent), and an MCP elicitation is
69
+ * declined outright. Anything else is a protocol error.
70
+ * @param method - the server request method.
71
+ * @param params - the server request params.
72
+ * @returns the JSON-RPC outcome to send back.
73
+ */
74
+ private answerRequest;
75
+ /** Resolve one native Codex approval request through the dsh approval seam. */
76
+ private answerApproval;
77
+ /**
78
+ * Put one `request_user_input` question to the human through the dsh
79
+ * user-questions seam. The seam itself fails closed (`NO_PROVIDER`) when no
80
+ * answerer is composed, so a refusal degrades to "no answers given" and the
81
+ * turn continues; asking is never silently skipped, and the degradation is
82
+ * logged rather than swallowed.
83
+ * @param params - the request params carrying the questions.
84
+ * @returns the response payload (or the empty answer when nobody answered).
85
+ */
86
+ private answerUserInput;
87
+ /** Ask the dsh approval seam; fail closed to a denial when it is absent. */
88
+ private requestApproval;
46
89
  get status(): AgentStatus;
47
90
  /** Commit a phase and publish its externally visible status transition. */
48
91
  private setPhase;
@@ -8,6 +8,17 @@
8
8
  import type { InitializeResult, ThreadResumeParams, ThreadStartParams, ThreadStartResult, TurnInterruptParams, TurnStartParams, TurnStartResult } from './types.ts';
9
9
  /** Callback for receiving server notifications. */
10
10
  export type NotificationHandler = (method: string, params: unknown) => void;
11
+ /** A JSON-RPC reply the client writes back for one inbound server request. */
12
+ export type RequestOutcome = {
13
+ readonly result: unknown;
14
+ } | {
15
+ readonly error: {
16
+ readonly code: number;
17
+ readonly message: string;
18
+ };
19
+ };
20
+ /** Callback answering one server-initiated JSON-RPC request. */
21
+ export type RequestHandler = (method: string, params: unknown, id: number | string) => RequestOutcome | Promise<RequestOutcome>;
11
22
  /** Callback for receiving raw stderr lines from the server process. */
12
23
  export type StderrHandler = (line: string) => void;
13
24
  /** JSON-RPC client for the codex app-server. */
@@ -17,6 +28,7 @@ export declare class AppServerClient {
17
28
  private reqId;
18
29
  private pending;
19
30
  private notificationHandler;
31
+ private requestHandler;
20
32
  private stderrHandler;
21
33
  private disposed;
22
34
  /** Whether this client was disposed or its server process exited. */
@@ -27,6 +39,8 @@ export declare class AppServerClient {
27
39
  static create(): Promise<AppServerClient>;
28
40
  /** Set the notification handler for streaming events. */
29
41
  onNotification(handler: NotificationHandler): void;
42
+ /** Set the handler for server-initiated requests (e.g. approvals). */
43
+ onRequest(handler: RequestHandler): void;
30
44
  /** Set the stderr handler for server log lines. */
31
45
  onStderr(handler: StderrHandler): void;
32
46
  /** Send the initialize handshake. */
@@ -45,5 +59,7 @@ export declare class AppServerClient {
45
59
  private request;
46
60
  /** Handle one line of stdout from the server. */
47
61
  private handleLine;
62
+ /** Resolve one inbound server request and write the JSON-RPC reply to stdin. */
63
+ private answerRequest;
48
64
  }
49
65
  //# sourceMappingURL=client.d.ts.map