dsh-loop-engine 1.0.0-rc6 → 1.0.0-rc8

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 (36) hide show
  1. package/README.md +43 -3
  2. package/README.zh.md +4 -3
  3. package/lib/client.js +82 -19
  4. package/lib/index.js +2249 -460
  5. package/lib/invariant.js +4 -3
  6. package/lib/types/client/LoopEngineBadge.d.ts +1 -1
  7. package/lib/types/client/LoopEngineComposerSelect.d.ts +1 -1
  8. package/lib/types/client/LoopEngineSection.d.ts +1 -1
  9. package/lib/types/client/index.d.ts +1 -1
  10. package/lib/types/client/locales.d.ts +4 -0
  11. package/lib/types/client/store.d.ts +2 -1
  12. package/lib/types/engine-claude/agent.d.ts +2 -0
  13. package/lib/types/engine-claude/loop.d.ts +24 -2
  14. package/lib/types/engine-claude/mapping.d.ts +3 -3
  15. package/lib/types/engine-codex/agent.d.ts +2 -0
  16. package/lib/types/engine-codex/appserver/mapping.d.ts +4 -4
  17. package/lib/types/engine-codex/loop.d.ts +24 -2
  18. package/lib/types/engine-kimi/acp/client.d.ts +76 -0
  19. package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
  20. package/lib/types/engine-kimi/acp/types.d.ts +95 -0
  21. package/lib/types/engine-kimi/agent.d.ts +123 -0
  22. package/lib/types/engine-kimi/commands.d.ts +40 -0
  23. package/lib/types/engine-kimi/loop.d.ts +108 -0
  24. package/lib/types/engine-kimi/mapping.d.ts +71 -0
  25. package/lib/types/engine-kimi/permission.d.ts +28 -0
  26. package/lib/types/engine-kimi/process.d.ts +61 -0
  27. package/lib/types/engine-kimi/skills.d.ts +57 -0
  28. package/lib/types/engine-kimi/types.d.ts +23 -0
  29. package/lib/types/engine-pi/agent.d.ts +2 -0
  30. package/lib/types/engine-pi/loop.d.ts +24 -2
  31. package/lib/types/index.d.ts +20 -2
  32. package/lib/types/patch-manager.d.ts +9 -0
  33. package/lib/types/preset.d.ts +73 -0
  34. package/lib/types/provider-route.d.ts +35 -0
  35. package/lib/types/settings.d.ts +6 -6
  36. package/package.json +24 -24
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Kimi Code loop engine module: hosts the AgentFactory that drives every
3
+ * session through a persistent `kimi acp` child (Agent Client Protocol over
4
+ * stdio), speaking one stateless `session/new` + `session/prompt` per dsh step,
5
+ * with the durable session log as the sole source of model context.
6
+ * dsh-loop-engine constructs this factory when the Kimi engine is selected; this
7
+ * module is a library, not a Cordis plugin entry. Kimi has no host approval
8
+ * callback, so tool approvals surfaced by ACP (`session/request_permission`) are
9
+ * answered from the session's dsh approval knobs (an `ask` policy degrades to
10
+ * denial); the whole child is spawned through the dsh subprocess seam — the only
11
+ * available privilege boundary — and the sandbox stance follows the session's
12
+ * durable permission knobs as the subprocess provider resolves them (default
13
+ * read-only).
14
+ *
15
+ * @module dsh-loop-engine/engine-kimi
16
+ */
17
+ import { Service } from '@deepseek-ai/cordis';
18
+ import type { Context } from '@deepseek-ai/cordis';
19
+ import z from '@deepseek-ai/schemastery';
20
+ import type { AgentFactory, AgentHandle, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
21
+ import type { KimiSpawnCapability } from './process.ts';
22
+ import type { ResolvedConfig } from './types.ts';
23
+ /** Grace in milliseconds for Kimi process-tree termination. */
24
+ export declare const KIMI_DISPOSE_GRACE_MS = 3000;
25
+ /** Deployment-owned configuration for the Kimi loop plugin. */
26
+ export interface Config {
27
+ /** Model alias for the `kimi` child (`-m`); Kimi native config owns the model when omitted. */
28
+ model?: string;
29
+ /** Explicit environment entries passed to the `kimi` child. */
30
+ env?: Record<string, string>;
31
+ /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
32
+ bin?: string;
33
+ }
34
+ /** Schema of the Kimi loop plugin configuration. */
35
+ export declare const Config: z<Config>;
36
+ /** Host-face ctx key for the Kimi loop service. */
37
+ declare module '@deepseek-ai/cordis' {
38
+ interface Context {
39
+ agentLoopKimi: KimiLoop;
40
+ }
41
+ }
42
+ /**
43
+ * Concrete AgentFactory and driver service of the Kimi loop. Creation and
44
+ * resume follow the registry factory contract and the shared publication
45
+ * transaction: prepare, run setup, then publish through both registries,
46
+ * announce, and emit `agent/session-start`.
47
+ */
48
+ export declare class KimiLoop extends Service implements AgentFactory {
49
+ /** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
50
+ static inject: string[];
51
+ /** Validated configuration owned by the loop plugin. */
52
+ readonly config: ResolvedConfig;
53
+ private readonly ownership;
54
+ /** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
55
+ private readonly runtime;
56
+ /** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
57
+ readonly spawn: KimiSpawnCapability;
58
+ constructor(ctx: Context, config: Config);
59
+ /**
60
+ * Construct the driver, scope, and one memoized reverse teardown for a new
61
+ * agent. The teardown is registered with the factory and the owner fiber
62
+ * BEFORE publication, so a mid-setup unload rolls everything back; `signal`
63
+ * fuses caller cancellation with lifecycle teardown for setup awaits.
64
+ */
65
+ private prepare;
66
+ /** Prepare one Agent around an acquired Session, run setup, and publish it. */
67
+ private setupAndPublish;
68
+ /**
69
+ * Create an agent and session under one caller-supplied identity, owned by
70
+ * the accessing fiber. When a persistence backend is mounted, the session's
71
+ * durable identity is stored before publication.
72
+ * @param ownerCtx - caller context that structurally owns the lifecycle.
73
+ * @param options - identities, session seed/metadata, loop options, setup, and cancellation.
74
+ * @returns the published handle.
75
+ */
76
+ createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
77
+ /**
78
+ * Take a fresh session's write ownership when persistence is mounted.
79
+ * Nothing is appended here: the constructor seed (which never re-emits
80
+ * through `session/event`) is stored by {@link appendUnstoredSuffix} at the
81
+ * publication commit point, so a failed or cancelled setup closes an
82
+ * unmaterialized handle and leaves no stored residue — the same id can be
83
+ * created again.
84
+ * @param session - the unpublished session to store.
85
+ * @param signal - optional cancellation forwarded to the backend create.
86
+ * @returns the owned handle and stored cursor, or `undefined` without a backend.
87
+ */
88
+ private createStoredSession;
89
+ /**
90
+ * Durably store the session events appended since the last stored cursor.
91
+ * Pre-publication appends (constructor seed markers, setup-window events)
92
+ * never re-emit through `session/event`, so publication must flush them
93
+ * through the handle before live events start routing into it.
94
+ * @param stored - the session's owned handle and stored cursor, if any.
95
+ * @param session - the unpublished session whose suffix is stored.
96
+ */
97
+ private appendUnstoredSuffix;
98
+ /**
99
+ * Resume an owned agent from the configured persistence service.
100
+ * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
101
+ * @param options - persisted identity, loop options, setup, and cancellation.
102
+ * @returns the published handle.
103
+ */
104
+ resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
105
+ /** Resume through an explicit persistence service. */
106
+ private resumeWith;
107
+ }
108
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Mapping from Kimi Code `--output-format stream-json` records to dsh
3
+ * session-log events. Kimi emits one JSON object per stdout line, discriminated
4
+ * by a `role` field (verified against the 0.28.1 CLI):
5
+ * - `{ "role": "assistant", "content": "<text>" }`
6
+ * - `{ "role": "assistant", "tool_calls": [{ "type": "function",
7
+ * "id": "call_…", "function": { "name": "Bash", "arguments": "<json>" } }] }`
8
+ * - `{ "role": "tool", "tool_call_id": "call_…", "content": "<text>" }`
9
+ * - `{ "role": "meta", "type": "session.resume_hint", … }` (discarded)
10
+ * Thinking and tool progress go to stderr, never the JSONL, so this module has no
11
+ * usage or reasoning projection. The module is pure: it parses a raw stdout
12
+ * buffer and returns plain record projections the agent folds into the log.
13
+ *
14
+ * @module dsh-loop-engine/engine-kimi/mapping
15
+ */
16
+ import type { ToolResultMessage } from '@deepseek-ai/dsh-llm';
17
+ /** One normalized Kimi tool-call record. */
18
+ export interface KimiToolCall {
19
+ /** The tool invocation id (`call_…`), correlated with the later `tool` record. */
20
+ readonly id: string;
21
+ /** The tool name. */
22
+ readonly name: string;
23
+ /** The raw arguments JSON string, passed verbatim. */
24
+ readonly arguments: string;
25
+ }
26
+ /** One parsed Kimi stream-json record (partial, tolerant of unknown fields). */
27
+ export interface KimiRecord {
28
+ readonly role?: unknown;
29
+ readonly type?: unknown;
30
+ readonly content?: unknown;
31
+ readonly tool_calls?: unknown;
32
+ readonly tool_call_id?: unknown;
33
+ readonly is_error?: unknown;
34
+ readonly isError?: unknown;
35
+ readonly error?: unknown;
36
+ }
37
+ /** Record role discriminant used by the step loop. */
38
+ export type KimiRecordRole = 'assistant' | 'tool' | 'meta' | 'unknown';
39
+ /**
40
+ * Parse a `kimi --output-format stream-json` stdout buffer into records.
41
+ * Lines are bare-`\n` delimited; blank lines and non-JSON lines are dropped.
42
+ * @param stdout - the child's full stdout text.
43
+ * @returns the parsed records, in order.
44
+ */
45
+ export declare function parseKimiRecords(stdout: string): KimiRecord[];
46
+ /** Classify one record's role. */
47
+ export declare function roleOf(record: KimiRecord): KimiRecordRole;
48
+ /**
49
+ * The joined text of one record's `content` field. Handles both the string
50
+ * form (`"content": "<text>"`) and an array form (`[{ "type": "text", "text": … }]`)
51
+ * defensively; any other shape renders empty.
52
+ * @param record - the parsed record.
53
+ * @returns the joined body text.
54
+ */
55
+ export declare function recordContentText(record: KimiRecord): string;
56
+ /**
57
+ * Normalize an assistant record's `tool_calls` array. Each entry keeps its id,
58
+ * name, and a verbatim argument string (the wire already serializes arguments
59
+ * as JSON text; absent args render `{}`).
60
+ * @param record - the parsed assistant record.
61
+ * @returns the normalized tool calls, in order.
62
+ */
63
+ export declare function assistantToolCalls(record: KimiRecord): readonly KimiToolCall[];
64
+ /**
65
+ * Surface a `tool` record as a durable tool-result message, or `undefined` when
66
+ * the record is not a correlatable tool result (missing `role: tool` or no id).
67
+ * @param record - the parsed record.
68
+ * @returns the dsh tool-result message, or `undefined`.
69
+ */
70
+ export declare function toolResultMessage(record: KimiRecord): ToolResultMessage | undefined;
71
+ //# sourceMappingURL=mapping.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Tool-approval mapping for the `kimi acp` driver.
3
+ *
4
+ * Kimi's ACP adapter surfaces tool/permission decisions as the reverse-RPC
5
+ * `session/request_permission`, which the client must answer. The dsh harness
6
+ * has no interactive approval callback in the unattended runtime, so the fold
7
+ * mirrors the codex/Pi bridges: an `ask` approval policy degrades to a denial
8
+ * (the only safe answer when no human is present), and anything else
9
+ * (`never`, or knob-less) auto-approves the tool. A full-access sandbox mode
10
+ * also auto-approves; a `workspace-write` stance is still auto-approved here
11
+ * because Kimi's own permission gating (`--permission`/session policy) is what
12
+ * bounds the tool — the ACP approval is the host's gate, and the dsh
13
+ * `approval/policy` knob is its signal.
14
+ *
15
+ * @module dsh-loop-engine/engine-kimi/permission
16
+ */
17
+ import type { PermissionEvent } from '../driver-core/permission-knobs.ts';
18
+ /**
19
+ * Whether the driver should answer `session/request_permission` with approval for
20
+ * one query. An `ask` policy denies (fail-closed — no human is present in the
21
+ * unattended runtime); anything else (`never`, or knob-less) auto-approves. The
22
+ * sandbox stance is not consulted: Kimi's own tool policy bounds what a tool does,
23
+ * and the ACP approval is the host's gate, signalled by `approval/policy`.
24
+ * @param events - the durable session log.
25
+ * @returns whether tool requests are approved.
26
+ */
27
+ export declare function resolveToolApproval(events: readonly PermissionEvent[]): boolean;
28
+ //# sourceMappingURL=permission.d.ts.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Kimi CLI process projection. The driver locates the `kimi` executable, builds
3
+ * the `-p --output-format stream-json` argv for one step, and projects the dsh
4
+ * subprocess seam handle onto a minimal transport the agent can read to
5
+ * completion. Kimi has no host permission callback and no `--tools` pruning
6
+ * flag in `-p` mode, so the whole child is spawned through the seam — the only
7
+ * available privilege boundary (the seam's OS sandbox, default read-only) — and
8
+ * every step is a fresh one-shot child because `-p` is inherently stateless.
9
+ *
10
+ * @module dsh-loop-engine/engine-kimi/process
11
+ */
12
+ import type { Readable, Writable } from 'node:stream';
13
+ import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
14
+ /** The exact argv/cwd/env the driver requests for one `kimi -p` child. */
15
+ export interface KimiSpawnSpec {
16
+ /** The program plus its flags; `argv[0]` is the Kimi CLI executable. */
17
+ readonly argv: readonly string[];
18
+ readonly cwd: string;
19
+ readonly env: Record<string, string>;
20
+ /** Cancellation: the seam escalates process-tree termination when it fires. */
21
+ readonly signal?: AbortSignal;
22
+ }
23
+ /** A spawned Kimi process as the agent's transport needs it. */
24
+ export interface KimiProcess {
25
+ /** Child stdin (JSON-RPC request frames). */
26
+ readonly stdin: Writable;
27
+ /** Child stdout (the `session/update` notification frames). */
28
+ readonly stdout: Readable;
29
+ /** Child stderr (diagnostics; drained and dropped). */
30
+ readonly stderr: Readable;
31
+ /** Resolves when the child closes. */
32
+ readonly done: Promise<unknown>;
33
+ /** Request process-tree termination. */
34
+ terminate(): void;
35
+ }
36
+ /** Spawns one Kimi child over the given spec (the driver's spawn capability). */
37
+ export type KimiSpawnCapability = (spec: KimiSpawnSpec) => KimiProcess;
38
+ /** Resolve the Kimi home directory from `KIMI_CODE_HOME` or the current user's home. */
39
+ export declare function kimiHomeDir(): string;
40
+ /**
41
+ * Resolve the Kimi CLI executable. An explicit config path wins; otherwise this
42
+ * probes the standard `<kimi home>/bin/kimi[.exe]` location and falls back to
43
+ * `'kimi'` (resolved through PATH by the spawner).
44
+ * @param configBin - an operator-pinned absolute path (or `'kimi'`), if any.
45
+ * @returns the executable to spawn as `argv[0]`.
46
+ */
47
+ export declare function kimiBinResolver(configBin?: string): string;
48
+ /**
49
+ * Build the persistent `kimi acp` argv. The ACP child stays alive across steps
50
+ * and is spoken to over JSON-RPC on stdio — the prompt is a request body, not an
51
+ * argv positional — so there is no command-line length ceiling and no model flag
52
+ * (Kimi owns model selection natively via its own config).
53
+ * @param bin - the Kimi executable.
54
+ * @returns the argv, `argv[0]` being the executable.
55
+ */
56
+ export declare function kimiAcpArgv(bin: string): string[];
57
+ /** Project the driver's spawn request onto the dsh subprocess seam. */
58
+ export declare function kimiSubprocessSpec(spec: KimiSpawnSpec, graceMs: number): SubprocessSpawnSpec;
59
+ /** Project a dsh subprocess handle onto the Kimi process transport. */
60
+ export declare function fromSubprocess(handle: SubprocessHandle): KimiProcess;
61
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Kimi Code skill provider: exposes the Kimi CLI's instruction files and skills
3
+ * as DSH skills.
4
+ *
5
+ * Kimi reads per-directory `AGENTS.md` files from the session cwd up to the git
6
+ * root, and installs skills from `skills/` directories — the user-level
7
+ * `$KIMI_CODE_HOME/skills/` (default `~/.kimi-code/skills/`) and the
8
+ * project-level `.kimi-code/skills/` (walking up to the git root). Each
9
+ * context-file set is surfaced as one user-invocable `agents-md` skill whose
10
+ * body is the concatenated file contents; every found `SKILL.md` catalog entry
11
+ * is surfaced under its own name, so the dsh skill-injection seam (`/name`
12
+ * gestures) can carry them into the prompt.
13
+ *
14
+ * The generic `~/.agents/skills/` and `.agents/skills/` roots are deliberately
15
+ * not scanned here: dsh's own `skill-filesystem` provider already exposes them
16
+ * through the same registry in the web profile. Kimi built-in Skills are
17
+ * shipped inside the CLI and cannot be read from a stable on-disk location, so
18
+ * the filesystem subset above is authoritative for the web menu. Note the
19
+ * shared {@link parseSkillFile} mirrors the agents-skill frontmatter
20
+ * (`name`/`description`/`whenToUse`/`disable-model-invocation`); Kimi's own
21
+ * `disableModelInvocation`/`type` fields are not translated, so a `type: flow`
22
+ * skill is surfaced as model-invocable.
23
+ *
24
+ * @module dsh-loop-engine/engine-kimi/skills
25
+ */
26
+ import type { SkillCandidate, SkillDefinition, SkillLookupOptions, SkillProvider, SkillProviderControl } from '../skills.ts';
27
+ /**
28
+ * Resolve the Kimi config directory, honoring the `KIMI_CODE_HOME` environment
29
+ * override and falling back to `~/.kimi-code`.
30
+ * @returns the absolute Kimi config directory.
31
+ */
32
+ export declare function kimiAgentDir(): string;
33
+ /**
34
+ * Skill provider that discovers context files and skills from Kimi's standard
35
+ * locations:
36
+ * - project `AGENTS.md` files between the cwd and the git root — surfaced as
37
+ * one `agents-md` skill;
38
+ * - project `.kimi-code/skills/` and user `~/.kimi-code/skills/` — each
39
+ * `SKILL.md` entry surfaced under its own name.
40
+ */
41
+ export declare class KimiSkillProvider implements SkillProvider {
42
+ private readonly control;
43
+ readonly name = "kimi";
44
+ constructor(control: SkillProviderControl);
45
+ list(options: SkillLookupOptions): Promise<readonly SkillCandidate[]>;
46
+ get(candidate: SkillCandidate, _options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
47
+ /** One merged `agents-md` candidate for a ranked file set. */
48
+ private agentsCandidate;
49
+ /** Collect every skill in one skills directory, both kimi layouts. */
50
+ private collectSkillsDir;
51
+ /** One parsed skill as a ranked candidate. */
52
+ private skillCandidate;
53
+ /** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
54
+ private tryParse;
55
+ }
56
+ export default KimiSkillProvider;
57
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Public types of the Kimi Code loop driver. Types only — no runtime code.
3
+ *
4
+ * Kimi Code (`kimi`) has no host approval callback ("runs with the permissions
5
+ * of the user"), and its non-interactive `-p` surface already auto-approves tool
6
+ * calls. It also exposes no `--tools` pruning flag in `-p` mode, so there is no
7
+ * per-driver permission lever to pin. The driver therefore spawns the whole
8
+ * `kimi -p` child through the dsh subprocess seam — the only available privilege
9
+ * boundary — and the sandbox stance follows the session's durable permission
10
+ * knobs as the subprocess provider resolves them (default read-only).
11
+ *
12
+ * @module dsh-loop-engine/engine-kimi/types
13
+ */
14
+ /** Driver configuration after defaults and load-time validation. */
15
+ export interface ResolvedConfig {
16
+ /** Model alias the `kimi` child is launched with (`--model`); Kimi native config owns the model when omitted. */
17
+ readonly model: string | undefined;
18
+ /** Explicit environment entries layered over the credential-scrubbed parent environment. */
19
+ readonly env: Record<string, string>;
20
+ /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
21
+ readonly bin: string;
22
+ }
23
+ //# sourceMappingURL=types.d.ts.map
@@ -17,6 +17,8 @@ import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
17
17
  import type { Context } from '@deepseek-ai/cordis';
18
18
  import type { ResolvedConfig } from './types.ts';
19
19
  import { type PiSpawnCapability } from './rpc/client.ts';
20
+ /** Provider route label used for logged header snapshots and message provenance. */
21
+ export declare const PROVIDER = "pi";
20
22
  /** Drives one session through turn and step boundaries on Pi. */
21
23
  export declare class PiAgent implements Agent {
22
24
  private loopCtx;
@@ -77,12 +77,34 @@ export declare class PiLoop extends Service implements AgentFactory {
77
77
  private setupAndPublish;
78
78
  /**
79
79
  * Create an agent and session under one caller-supplied identity, owned by
80
- * the accessing fiber.
80
+ * the accessing fiber. When a persistence backend is mounted, the session's
81
+ * durable identity is stored before publication.
81
82
  * @param ownerCtx - caller context that structurally owns the lifecycle.
82
83
  * @param options - identities, session seed/metadata, loop options, setup, and cancellation.
83
84
  * @returns the published handle.
84
85
  */
85
86
  createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
87
+ /**
88
+ * Take a fresh session's write ownership when persistence is mounted.
89
+ * Nothing is appended here: the constructor seed (which never re-emits
90
+ * through `session/event`) is stored by {@link appendUnstoredSuffix} at the
91
+ * publication commit point, so a failed or cancelled setup closes an
92
+ * unmaterialized handle and leaves no stored residue — the same id can be
93
+ * created again.
94
+ * @param session - the unpublished session to store.
95
+ * @param signal - optional cancellation forwarded to the backend create.
96
+ * @returns the owned handle and stored cursor, or `undefined` without a backend.
97
+ */
98
+ private createStoredSession;
99
+ /**
100
+ * Durably store the session events appended since the last stored cursor.
101
+ * Pre-publication appends (constructor seed markers, setup-window events)
102
+ * never re-emit through `session/event`, so publication must flush them
103
+ * through the handle before live events start routing into it.
104
+ * @param stored - the session's owned handle and stored cursor, if any.
105
+ * @param session - the unpublished session whose suffix is stored.
106
+ */
107
+ private appendUnstoredSuffix;
86
108
  /**
87
109
  * Resume an owned agent from the configured persistence service.
88
110
  * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
@@ -90,7 +112,7 @@ export declare class PiLoop extends Service implements AgentFactory {
90
112
  * @returns the published handle.
91
113
  */
92
114
  resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
93
- /** Resume through an explicit persistence handle. */
115
+ /** Resume through an explicit persistence service. */
94
116
  private resumeWith;
95
117
  }
96
118
  //# sourceMappingURL=loop.d.ts.map
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Web-switchable agent loop engine, node half.
3
3
  *
4
- * Hosts the non-default agent-loop engines (Claude Code, Codex) and
5
- * bridges them with the harness's single AgentFactory slot. The engine is
4
+ * Hosts the non-default agent-loop engines (Claude Code, Codex, Pi, Kimi Code)
5
+ * and bridges them with the harness's single AgentFactory slot. The engine is
6
6
  * selected by the `agent-loop-engine` settings section; the selection is
7
7
  * realized by a managed block in the profile's `cordis.patch.yml` that
8
8
  * disables the base bundle's `agent-loop` row — exactly one AgentFactory may
@@ -17,6 +17,22 @@
17
17
  * The settings section is seeded from the block so the UI mirrors the file,
18
18
  * and a committed settings change writes the block (only when it differs).
19
19
  *
20
+ * A hosted engine also takes over the session's command and skill surface:
21
+ * the block disables dsh's `command-goal` row, and the plugin authors a
22
+ * stripped copy of the `standard` agent preset into the user preset root
23
+ * (see `preset.ts`) and steers the `agent-presets` roster default to it, so
24
+ * new sessions get the engine's commands and skills instead of the dsh-native
25
+ * ones an external engine cannot honor. Switching back to `in-process`
26
+ * restores the previous default.
27
+ *
28
+ * While a hosted engine is mounted the plugin also serves its provider route
29
+ * label (`claude-code` / `codex` / `pi` / `kimi`) from the llm registry with
30
+ * a model-less placeholder adapter (see `provider-route.ts`): the engine logs
31
+ * that label into each session's request/header, and the web host refuses a
32
+ * turn whose session selection names a provider no adapter serves — without
33
+ * the placeholder the second prompt of every hosted session would fail with
34
+ * `model-unavailable`.
35
+ *
20
36
  * @module dsh-loop-engine
21
37
  */
22
38
  import { Context } from '@deepseek-ai/cordis';
@@ -50,6 +66,8 @@ export interface Config extends ClaudeCodeConfig {
50
66
  piProvider?: string;
51
67
  /** Thinking/reasoning level for the Pi RPC child, appended to its `--model`. */
52
68
  piThinking?: string;
69
+ /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
70
+ kimiBin?: string;
53
71
  }
54
72
  /**
55
73
  * Schema of the loop engine composition entry.
@@ -12,8 +12,17 @@
12
12
  * # -- dsh-loop-engine managed block: claude-code --
13
13
  * - id: agent-loop
14
14
  * disabled: true
15
+ * - id: command-goal
16
+ * disabled: true
15
17
  * # -- /dsh-loop-engine managed block --
16
18
  *
19
+ * The `command-goal` row goes down with the loop: a hosted engine owns the
20
+ * session's command surface, and dsh's `/goal` would otherwise collide with
21
+ * an engine's own goal command (Kimi) or dangle over a goal service nothing
22
+ * drives (the other engines). The remaining dsh-native commands (`/export`,
23
+ * `/feedback`, `/permission`) are engine-agnostic session/settings controls
24
+ * that keep working under a hosted engine, so they stay.
25
+ *
17
26
  * `in-process` renders an absent block (the base bundle's `agent-loop` row
18
27
  * stays active and supplies the factory), so switching back removes the span
19
28
  * entirely. Any other engine renders the same disable block, and the begin
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Hosted-engine agent preset: a managed copy of the deployment's `standard`
3
+ * preset with the dsh-native command and skill rows stripped.
4
+ *
5
+ * A hosted engine (Claude Code, Codex, Pi, Kimi) owns its session's command
6
+ * and skill surface: the engine's own slash commands and skill providers are
7
+ * registered globally by the plugin, and the dsh-native equivalents would only
8
+ * duplicate or mislead — dsh `/plan` is advisory prompt text an external
9
+ * engine never assembles, dsh `/compact` cannot shrink a context the engine's
10
+ * child process holds, and dsh skills would sit next to the engine's own
11
+ * catalog. Those rows live inside the agent-preset composition, which a
12
+ * profile patch cannot reach, so the plugin authors a stripped preset into the
13
+ * user preset root (`$DSH_HOME/.agent-presets/<id>`) and steers the roster's
14
+ * default at runtime (see the plugin's apply).
15
+ *
16
+ * The preset is REGENERATED from the current `standard` composition on every
17
+ * boot that needs it: text on disk is never authoritative, so a harness
18
+ * upgrade that changes `standard` flows through. The file is plain YAML the
19
+ * loader already accepts — the strip is a line transform that preserves
20
+ * everything it does not drop byte for byte, comments included.
21
+ *
22
+ * @module dsh-loop-engine/preset
23
+ */
24
+ /** Preset id the plugin authors into the user preset root. */
25
+ export declare const HOSTED_PRESET_ID = "loop-engine";
26
+ /** Harness-home-relative directory of locally authored presets (mirrors `USER_PRESET_DIR` in `dsh-agent-presets`). */
27
+ export declare const USER_PRESET_DIR = ".agent-presets";
28
+ /** The composition file that makes a directory a preset. */
29
+ export declare const COMPOSITION_FILE = "agent.cordis.yml";
30
+ /** The display-metadata file beside a preset's composition. */
31
+ export declare const METADATA_FILE = "preset.yml";
32
+ /** Source preset the hosted preset derives from. */
33
+ export declare const SOURCE_PRESET_ID = "standard";
34
+ /**
35
+ * Top-level rows stripped from the source preset for hosted engines:
36
+ * - `skill-filesystem` / `tool-skill`: the dsh skill surface — each engine
37
+ * registers its own skill provider globally;
38
+ * - `tool-goal`: the model-facing goal tool — the managed block already
39
+ * disables dsh's `/goal` command for hosted engines;
40
+ * - `planning`: dsh plan mode — its only model-visible effect is a system
41
+ * prompt section an external engine never assembles;
42
+ * - `compaction`: dsh `/compact` and auto-compaction — a hosted engine owns
43
+ * its context and its own `/compact` (Claude, Kimi).
44
+ */
45
+ export declare const STRIPPED_ROWS: readonly ["skill-filesystem", "tool-skill", "tool-goal", "planning", "compaction"];
46
+ /**
47
+ * Remove top-level entries by id from a preset composition, preserving every
48
+ * other byte. Each entry owns the comment/blank run directly above its opener
49
+ * — that run is the entry's section heading and drops with it — except the
50
+ * run above the FIRST entry, which is the file header and stays. Entries
51
+ * without an `id` opener are always kept: the transform touches only what it
52
+ * can name.
53
+ * @param text - the source composition.
54
+ * @param ids - top-level row ids to strip.
55
+ * @returns the stripped composition.
56
+ */
57
+ export declare function stripPresetRows(text: string, ids?: readonly string[]): string;
58
+ /** Minimal read seam over the host's preset roster (`AgentPresets.read`). */
59
+ export interface PresetCompositionSource {
60
+ /** Read one preset's composition text; throws when the id is unknown. */
61
+ read(id: string): Promise<string>;
62
+ }
63
+ /**
64
+ * Regenerate the hosted-engine preset under the dsh home's user preset root
65
+ * from the roster's `standard` preset. Idempotent: an up-to-date directory is
66
+ * untouched, so no standing mount sees a spurious file-stamp change.
67
+ * @param dshHome - the resolved harness home.
68
+ * @param source - the roster's composition reader.
69
+ * @returns whether any file was written.
70
+ * @throws when the source preset cannot be read or the writes fail.
71
+ */
72
+ export declare function ensureHostedPreset(dshHome: string, source: PresetCompositionSource): Promise<boolean>;
73
+ //# sourceMappingURL=preset.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Hosted-engine provider route placeholders.
3
+ *
4
+ * Every hosted engine logs its sessions' request/header with its own provider
5
+ * label (`claude-code`, `codex`, `pi`, `kimi`) rather than a model endpoint the
6
+ * harness llm registry serves — the engine owns its model natively. The web
7
+ * host derives a session's model selection from that header and refuses a turn
8
+ * whose provider no registered adapter serves, so without a placeholder route
9
+ * the SECOND prompt of every hosted session fails with `model-unavailable`.
10
+ * The placeholder serves the label while advertising no models; catalog groups
11
+ * that advertise nothing are dropped, so the model picker is unchanged.
12
+ *
13
+ * @module dsh-loop-engine/provider-route
14
+ */
15
+ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
16
+ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
17
+ import type { LoopEngineId } from './settings.ts';
18
+ /** Provider route label each hosted engine logs into its sessions' request/header. */
19
+ export declare const HOSTED_PROVIDER_ROUTES: Readonly<Record<Exclude<LoopEngineId, 'in-process'>, string>>;
20
+ /**
21
+ * Placeholder adapter serving one hosted engine's provider route label. It
22
+ * inherits the empty catalog and default metadata (the engine's model is not a
23
+ * harness-selectable endpoint), and {@link stream} fails loud: a call reaching
24
+ * it means a real model query was routed to an engine that owns its model
25
+ * natively — a wiring bug, not a request to serve.
26
+ */
27
+ export declare class HostedEngineRouteAdapter extends LlmAdapter {
28
+ private readonly label;
29
+ /**
30
+ * @param label - the provider route label this placeholder serves.
31
+ */
32
+ constructor(label: string);
33
+ stream(_options: GenerateOptions): AsyncIterable<StreamChunk>;
34
+ }
35
+ //# sourceMappingURL=provider-route.d.ts.map
@@ -2,10 +2,10 @@
2
2
  * Shared loop-engine identity, namespace, and schema.
3
3
  *
4
4
  * The namespace literal lives in the zero-import `./namespace.ts` so both
5
- * halves agree on the section name: the node half brands it through
6
- * `settingsNamespace()` (a runtime value), while the browser half imports the
7
- * same literal without pulling the host-side `dsh-settings` service into the
8
- * client bundle (cross-plugin value imports go through cordis services, and
5
+ * halves agree on the section name: the node half brands it as a
6
+ * `SettingsNamespace`, while the browser half imports the same literal
7
+ * without pulling the host-side `dsh-settings` service into the client
8
+ * bundle (cross-plugin value imports go through cordis services, and
9
9
  * `settings-scope.ts` follows the same discipline).
10
10
  *
11
11
  * @module dsh-loop-engine/settings
@@ -14,7 +14,7 @@ import z from '@deepseek-ai/schemastery';
14
14
  import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
15
15
  export { LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL } from './namespace.ts';
16
16
  /** The installed engine driving new Agent turns. */
17
- export declare const LOOP_ENGINE_IDS: readonly ["in-process", "claude-code", "codex", "pi"];
17
+ export declare const LOOP_ENGINE_IDS: readonly ["in-process", "claude-code", "codex", "pi", "kimi"];
18
18
  /** Installed agent loop engine id. */
19
19
  export type LoopEngineId = (typeof LOOP_ENGINE_IDS)[number];
20
20
  /** Stored and composed loop engine selection. */
@@ -26,6 +26,6 @@ export interface LoopEngineSettings {
26
26
  }
27
27
  /** Schema of the loop engine settings section. */
28
28
  export declare const LOOP_ENGINE_SETTINGS_SCHEMA: z<LoopEngineSettings>;
29
- /** Brand the shared literal through the settings API on the node side. */
29
+ /** The shared literal branded as a settings namespace on the node side. */
30
30
  export declare function loopEngineSettingsNamespace(): SettingsNamespace;
31
31
  //# sourceMappingURL=settings.d.ts.map