dsh-loop-engine 1.0.0-rc10

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 (63) hide show
  1. package/README.md +121 -0
  2. package/README.zh.md +38 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/client.js +37506 -0
  5. package/lib/index.js +6412 -0
  6. package/lib/invariant.js +108 -0
  7. package/lib/types/client/LoopEngineBadge.d.ts +34 -0
  8. package/lib/types/client/LoopEngineComposerSelect.d.ts +40 -0
  9. package/lib/types/client/LoopEngineSection.d.ts +34 -0
  10. package/lib/types/client/index.d.ts +29 -0
  11. package/lib/types/client/locales.d.ts +46 -0
  12. package/lib/types/client/store.d.ts +58 -0
  13. package/lib/types/commands.d.ts +69 -0
  14. package/lib/types/driver-core/context-files.d.ts +62 -0
  15. package/lib/types/driver-core/ownership.d.ts +40 -0
  16. package/lib/types/driver-core/permission-knobs.d.ts +26 -0
  17. package/lib/types/driver-core/prompt.d.ts +23 -0
  18. package/lib/types/driver-core/skill-inject.d.ts +59 -0
  19. package/lib/types/engine-claude/agent.d.ts +104 -0
  20. package/lib/types/engine-claude/loop.d.ts +111 -0
  21. package/lib/types/engine-claude/mapping.d.ts +83 -0
  22. package/lib/types/engine-claude/permission.d.ts +41 -0
  23. package/lib/types/engine-claude/process.d.ts +59 -0
  24. package/lib/types/engine-claude/sdk.d.ts +57 -0
  25. package/lib/types/engine-claude/types.d.ts +18 -0
  26. package/lib/types/engine-codex/agent.d.ts +111 -0
  27. package/lib/types/engine-codex/appserver/client.d.ts +49 -0
  28. package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
  29. package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
  30. package/lib/types/engine-codex/appserver/types.d.ts +215 -0
  31. package/lib/types/engine-codex/loop.d.ts +114 -0
  32. package/lib/types/engine-codex/permission.d.ts +32 -0
  33. package/lib/types/engine-codex/skills.d.ts +29 -0
  34. package/lib/types/engine-codex/types.d.ts +19 -0
  35. package/lib/types/engine-kimi/acp/client.d.ts +76 -0
  36. package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
  37. package/lib/types/engine-kimi/acp/types.d.ts +95 -0
  38. package/lib/types/engine-kimi/agent.d.ts +123 -0
  39. package/lib/types/engine-kimi/commands.d.ts +40 -0
  40. package/lib/types/engine-kimi/loop.d.ts +108 -0
  41. package/lib/types/engine-kimi/mapping.d.ts +71 -0
  42. package/lib/types/engine-kimi/permission.d.ts +28 -0
  43. package/lib/types/engine-kimi/process.d.ts +61 -0
  44. package/lib/types/engine-kimi/skills.d.ts +57 -0
  45. package/lib/types/engine-kimi/types.d.ts +23 -0
  46. package/lib/types/engine-pi/agent.d.ts +135 -0
  47. package/lib/types/engine-pi/loop.d.ts +123 -0
  48. package/lib/types/engine-pi/permission.d.ts +43 -0
  49. package/lib/types/engine-pi/probe.d.ts +23 -0
  50. package/lib/types/engine-pi/rpc/client.d.ts +105 -0
  51. package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
  52. package/lib/types/engine-pi/rpc/types.d.ts +235 -0
  53. package/lib/types/engine-pi/skills.d.ts +55 -0
  54. package/lib/types/engine-pi/types.d.ts +27 -0
  55. package/lib/types/index.d.ts +114 -0
  56. package/lib/types/invariant.d.ts +23 -0
  57. package/lib/types/namespace.d.ts +9 -0
  58. package/lib/types/patch-manager.d.ts +59 -0
  59. package/lib/types/preset.d.ts +73 -0
  60. package/lib/types/provider-route.d.ts +49 -0
  61. package/lib/types/settings.d.ts +31 -0
  62. package/lib/types/skills.d.ts +93 -0
  63. package/package.json +103 -0
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Pi RPC protocol type definitions. A minimal subset of the upstream
3
+ * `pi --mode rpc` protocol, covering only what the driver needs: the commands
4
+ * it sends (`new_session`, `prompt`, `abort`, `get_session_stats`), the
5
+ * response envelope, and the streaming events it maps into the durable dsh
6
+ * session log. Types only — no runtime code.
7
+ *
8
+ * The protocol is strict LF (`\n`) JSONL: records are delimited only by a bare
9
+ * `\n` (a trailing `\r` is tolerated), and Unicode separators such as U+2028 /
10
+ * U+2029 are ordinary characters inside JSON strings — so a generic line reader
11
+ * that treats them as newlines is not compliant.
12
+ *
13
+ * @module dsh-loop-engine/engine-pi/rpc/types
14
+ */
15
+ /** Optional per-command correlation id; echoed back on the response. */
16
+ export interface PiCommandCorrelation {
17
+ readonly id?: number;
18
+ }
19
+ /** Start a fresh Pi session (the driver issues one per dsh step). */
20
+ export interface PiNewSessionCommand extends PiCommandCorrelation {
21
+ readonly type: 'new_session';
22
+ readonly parentSession?: string;
23
+ }
24
+ /** Send a user prompt to the agent and begin streaming events. */
25
+ export interface PiPromptCommand extends PiCommandCorrelation {
26
+ readonly type: 'prompt';
27
+ readonly message: string;
28
+ readonly images?: readonly PiImage[];
29
+ readonly streamingBehavior?: 'steer' | 'followUp';
30
+ }
31
+ /** Abort the current agent operation. */
32
+ export interface PiAbortCommand extends PiCommandCorrelation {
33
+ readonly type: 'abort';
34
+ }
35
+ /** Query session stats (usage/cost fallback when a message carries none). */
36
+ export interface PiGetSessionStatsCommand extends PiCommandCorrelation {
37
+ readonly type: 'get_session_stats';
38
+ }
39
+ /** Every command the client can send. */
40
+ export type PiCommand = PiNewSessionCommand | PiPromptCommand | PiAbortCommand | PiGetSessionStatsCommand;
41
+ /** Message attachment (images) accepted by `prompt`. */
42
+ export interface PiImage {
43
+ readonly type: 'image';
44
+ readonly data: string;
45
+ readonly mimeType: string;
46
+ }
47
+ /** Successful or failed command response. */
48
+ export interface PiResponse {
49
+ readonly type: 'response';
50
+ readonly command?: string;
51
+ readonly success: boolean;
52
+ readonly error?: string;
53
+ readonly id?: number;
54
+ readonly data?: unknown;
55
+ }
56
+ /** Session statistics returned by `get_session_stats`. */
57
+ export interface PiSessionStats {
58
+ readonly tokens?: {
59
+ readonly input?: number;
60
+ readonly output?: number;
61
+ readonly cacheRead?: number;
62
+ readonly cacheWrite?: number;
63
+ readonly total?: number;
64
+ };
65
+ readonly contextUsage?: {
66
+ readonly tokens?: number | null;
67
+ readonly contextWindow?: number;
68
+ readonly percent?: number | null;
69
+ };
70
+ }
71
+ /** Provider-reported token usage attached to messages and updates. */
72
+ export interface PiUsage {
73
+ readonly input?: number;
74
+ readonly output?: number;
75
+ readonly cacheRead?: number;
76
+ readonly cacheWrite?: number;
77
+ readonly totalTokens?: number;
78
+ }
79
+ /** A content block of a Pi message. */
80
+ export type PiContent = {
81
+ readonly type: 'text';
82
+ readonly text: string;
83
+ } | {
84
+ readonly type: 'thinking';
85
+ readonly thinking: string;
86
+ } | {
87
+ readonly type: 'toolCall';
88
+ readonly id: string;
89
+ readonly name: string;
90
+ readonly arguments: unknown;
91
+ };
92
+ /** One role-tagged Pi message. */
93
+ export interface PiMessage {
94
+ readonly role: 'user' | 'assistant' | 'toolResult' | 'system';
95
+ readonly content: string | readonly PiContent[];
96
+ readonly usage?: PiUsage;
97
+ readonly isError?: boolean;
98
+ readonly toolCallId?: string;
99
+ readonly toolName?: string;
100
+ readonly timestamp?: number;
101
+ readonly id?: string;
102
+ }
103
+ /** A tool result as carried by `turn_end.toolResults`. */
104
+ export interface PiToolResult {
105
+ readonly role: 'toolResult';
106
+ readonly toolCallId: string;
107
+ readonly toolName: string;
108
+ readonly content: readonly PiContent[];
109
+ readonly isError?: boolean;
110
+ readonly usage?: PiUsage;
111
+ }
112
+ /** The `assistantMessageEvent` delta union of `message_update`. */
113
+ export type PiAssistantMessageEvent = {
114
+ readonly type: 'text_start';
115
+ readonly contentIndex: number;
116
+ } | {
117
+ readonly type: 'text_delta';
118
+ readonly contentIndex: number;
119
+ readonly delta: string;
120
+ } | {
121
+ readonly type: 'text_end';
122
+ readonly contentIndex: number;
123
+ readonly content?: string;
124
+ } | {
125
+ readonly type: 'thinking_start';
126
+ readonly contentIndex: number;
127
+ } | {
128
+ readonly type: 'thinking_delta';
129
+ readonly contentIndex: number;
130
+ readonly delta: string;
131
+ } | {
132
+ readonly type: 'thinking_end';
133
+ readonly contentIndex: number;
134
+ readonly thinking?: string;
135
+ } | {
136
+ readonly type: 'toolcall_start';
137
+ readonly contentIndex: number;
138
+ readonly id: string;
139
+ readonly toolName: string;
140
+ } | {
141
+ readonly type: 'toolcall_delta';
142
+ readonly contentIndex: number;
143
+ readonly delta: string;
144
+ } | {
145
+ readonly type: 'toolcall_end';
146
+ readonly contentIndex: number;
147
+ readonly toolCall: {
148
+ readonly id: string;
149
+ readonly name: string;
150
+ readonly arguments: unknown;
151
+ };
152
+ };
153
+ /** An `extension_ui_request` (dialog or fire-and-forget). */
154
+ export interface PiExtensionUiRequest {
155
+ readonly type: 'extension_ui_request';
156
+ readonly id: string;
157
+ readonly method: 'select' | 'confirm' | 'input' | 'editor' | 'notify' | 'setStatus' | 'setWidget' | 'setTitle' | 'set_editor_text';
158
+ readonly title?: string;
159
+ readonly options?: readonly string[];
160
+ readonly message?: string;
161
+ readonly [key: string]: unknown;
162
+ }
163
+ /** A tool-execution event (start / update / end). */
164
+ export type PiToolExecutionEvent = {
165
+ readonly type: 'tool_execution_start';
166
+ readonly toolCallId: string;
167
+ readonly toolName: string;
168
+ readonly args: unknown;
169
+ } | {
170
+ readonly type: 'tool_execution_update';
171
+ readonly toolCallId: string;
172
+ readonly toolName: string;
173
+ readonly args: unknown;
174
+ readonly partialResult: unknown;
175
+ } | {
176
+ readonly type: 'tool_execution_end';
177
+ readonly toolCallId: string;
178
+ readonly toolName: string;
179
+ readonly result: unknown;
180
+ readonly isError: boolean;
181
+ };
182
+ /** Every agent event the driver consumes or ignores. */
183
+ export type PiEvent = {
184
+ readonly type: 'response';
185
+ } & PiResponse | {
186
+ readonly type: 'agent_start';
187
+ } | {
188
+ readonly type: 'agent_end';
189
+ readonly messages?: readonly PiMessage[];
190
+ readonly willRetry?: boolean;
191
+ } | {
192
+ readonly type: 'agent_settled';
193
+ } | {
194
+ readonly type: 'turn_start';
195
+ } | {
196
+ readonly type: 'turn_end';
197
+ readonly message?: PiMessage;
198
+ readonly toolResults?: readonly PiToolResult[];
199
+ } | {
200
+ readonly type: 'message_start';
201
+ readonly message: PiMessage;
202
+ } | {
203
+ readonly type: 'message_update';
204
+ readonly usage?: PiUsage;
205
+ readonly assistantMessageEvent: PiAssistantMessageEvent;
206
+ } | {
207
+ readonly type: 'message_end';
208
+ readonly message: PiMessage;
209
+ } | PiToolExecutionEvent | {
210
+ readonly type: 'compaction_start';
211
+ readonly reason?: string;
212
+ } | {
213
+ readonly type: 'compaction_end';
214
+ readonly reason?: string;
215
+ readonly aborted?: boolean;
216
+ readonly willRetry?: boolean;
217
+ readonly result?: unknown;
218
+ } | {
219
+ readonly type: 'auto_retry_start';
220
+ readonly attempt?: number;
221
+ } | {
222
+ readonly type: 'auto_retry_end';
223
+ readonly success?: boolean;
224
+ readonly attempt?: number;
225
+ readonly finalError?: string;
226
+ } | {
227
+ readonly type: 'queue_update';
228
+ readonly steering?: readonly string[];
229
+ readonly followUp?: readonly string[];
230
+ } | {
231
+ readonly type: 'bash_execution_update';
232
+ readonly id?: string;
233
+ readonly delta?: string;
234
+ } | PiExtensionUiRequest;
235
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Pi skill provider: exposes the Pi CLI's instruction files and skills as DSH
3
+ * skills.
4
+ *
5
+ * Pi reads per-directory context files (`AGENTS.md`, or `CLAUDE.md`,
6
+ * preferring `AGENTS.override.md` where one exists) from the session cwd up to
7
+ * the git root, plus a global `AGENTS.md` under the pi config directory
8
+ * (`PI_CODING_AGENT_DIR` or `~/.pi/agent`), and installs skills from
9
+ * `skills/` directories (`~/.pi/agent/skills/` and project `.pi/skills/`
10
+ * walking up). Each context-file set is surfaced as one user-invocable
11
+ * `agents-md` skill whose body is the concatenated file contents; every found
12
+ * `SKILL.md` catalog entry is surfaced under its own name, so the dsh
13
+ * skill-injection seam (`/name` gestures) can carry them into the prompt.
14
+ *
15
+ * `.agents/skills` roots are deliberately not scanned here: dsh's own
16
+ * `skill-filesystem` provider already exposes them through the same registry
17
+ * in the web profile. Pi settings/CLI/package skills are only discoverable
18
+ * through a running `pi --mode rpc` probe, which the engine does not perform
19
+ * at composition time — the filesystem subset above is authoritative for the
20
+ * web menu.
21
+ *
22
+ * @module dsh-loop-engine/engine-pi/skills
23
+ */
24
+ import type { SkillCandidate, SkillDefinition, SkillLookupOptions, SkillProvider, SkillProviderControl } from '../skills.ts';
25
+ /**
26
+ * Resolve the pi config directory, honoring the `PI_CODING_AGENT_DIR`
27
+ * environment override and falling back to `~/.pi/agent`.
28
+ * @returns the absolute pi config directory.
29
+ */
30
+ export declare function piAgentDir(): string;
31
+ /**
32
+ * Skill provider that discovers context files and skills from pi's standard
33
+ * locations:
34
+ * - project context files between the cwd and the git root (plus
35
+ * `~/.pi/agent/AGENTS.md`) — surfaced as one `agents-md` skill;
36
+ * - project `.pi/skills/` and user `~/.pi/agent/skills/` — each `SKILL.md`
37
+ * entry surfaced under its own name.
38
+ */
39
+ export declare class PiSkillProvider implements SkillProvider {
40
+ private readonly control;
41
+ readonly name = "pi";
42
+ constructor(control: SkillProviderControl);
43
+ list(options: SkillLookupOptions): Promise<readonly SkillCandidate[]>;
44
+ get(candidate: SkillCandidate, _options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
45
+ /** One merged `agents-md` candidate for a ranked file set. */
46
+ private agentsCandidate;
47
+ /** Collect every skill in one skills directory, both pi layouts. */
48
+ private collectSkillsDir;
49
+ /** One parsed skill as a ranked candidate. */
50
+ private skillCandidate;
51
+ /** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
52
+ private tryParse;
53
+ }
54
+ export default PiSkillProvider;
55
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Public types of the Pi loop driver. Types only — no runtime code.
3
+ *
4
+ * Pi carries no native permission system ("runs with the permissions of the
5
+ * user"), so the declarative stance this driver resolves is a sandbox mode plus
6
+ * the tool set the process is allowed to use; the rest of the driver then
7
+ * either wraps the whole `pi --mode rpc` child in the dsh subprocess sandbox or
8
+ * prunes its `--tools` accordingly.
9
+ *
10
+ * @module dsh-loop-engine/engine-pi/types
11
+ */
12
+ /** Pi sandbox stances the driver can resolve, mapped from the dsh session knobs. */
13
+ export type PiSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
14
+ /** Driver configuration after defaults and load-time validation. */
15
+ export interface ResolvedConfig {
16
+ /** Pinned sandbox mode; `undefined` follows the session's dsh permission knobs per query. */
17
+ readonly sandboxMode: PiSandboxMode | undefined;
18
+ /** LLM provider the `pi` RPC process is launched with (`--provider`). */
19
+ readonly provider: string | undefined;
20
+ /** Model pattern the `pi` RPC process is launched with (`--model`). */
21
+ readonly model: string | undefined;
22
+ /** Thinking/reasoning level for the model (`--model <id>:<level>` or set at runtime). */
23
+ readonly thinkingLevel: string | undefined;
24
+ /** Explicit environment entries layered over the credential-scrubbed parent environment. */
25
+ readonly env: Record<string, string>;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Web-switchable agent loop engine, node half.
3
+ *
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
+ * selected by the `agent-loop-engine` settings section; the selection is
7
+ * realized by a managed block in the profile's `cordis.patch.yml` that
8
+ * disables the base bundle's `agent-loop` row — exactly one AgentFactory may
9
+ * register, so a non-default engine owns the slot by disabling the base loop
10
+ * first, and `in-process` leaves the base row active (this plugin does NOT
11
+ * register its own factory then).
12
+ *
13
+ * The managed block is the ground truth the factory decision reads at boot:
14
+ * apply() reads the file synchronously, so a committed engine change takes
15
+ * effect on the next recomposition (restart); the config-only HMR watcher
16
+ * re-applies the patch file but cannot re-register an AgentFactory mid-run.
17
+ * The settings section is seeded from the block so the UI mirrors the file,
18
+ * and a committed settings change writes the block (only when it differs).
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
+ *
36
+ * @module dsh-loop-engine
37
+ */
38
+ import { Context } from '@deepseek-ai/cordis';
39
+ import z from '@deepseek-ai/schemastery';
40
+ import { type Config as ClaudeCodeConfig } from './engine-claude/loop.ts';
41
+ import type { CodexApprovalPolicy, CodexSandboxMode } from './engine-codex/types.ts';
42
+ import { type LoopEngineId } from './settings.ts';
43
+ export declare const name = "loop-engine";
44
+ /**
45
+ * Services the plugin's own fiber requires. The plugin declares none of its
46
+ * own: the optional host services it reads (`commands`, `skills`) are resolved
47
+ * lazily via `ctx.get` and may be absent, and the hosted engine factories
48
+ * (Claude Code / Codex) declare their own `inject` when the plugin mounts them
49
+ * as children. Empty keeps the plugin from demanding a service that a minimal
50
+ * profile does not provide.
51
+ */
52
+ export declare const inject: never[];
53
+ /** Composition entry for the loop engine selection and the hosted engine drivers. */
54
+ export interface Config extends ClaudeCodeConfig {
55
+ /** Profile whose `cordis.patch.yml` carries the managed block; defaults to `web`. */
56
+ profile?: string;
57
+ /** Patch file name inside the profile; defaults to `cordis.patch.yml`. */
58
+ patchFilename?: string;
59
+ /** Explicit absolute path to the patch file, overriding profile + filename. */
60
+ patchPath?: string;
61
+ /** Pinned Codex sandbox mode; falls back to the session's dsh permission knobs. */
62
+ sandboxMode?: CodexSandboxMode;
63
+ /** Pinned Codex approval policy; falls back to the session's dsh permission knobs. */
64
+ approvalPolicy?: CodexApprovalPolicy;
65
+ /** LLM provider for the Pi RPC child (`--provider`). */
66
+ piProvider?: string;
67
+ /** Thinking/reasoning level for the Pi RPC child, appended to its `--model`. */
68
+ piThinking?: string;
69
+ /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
70
+ kimiBin?: string;
71
+ }
72
+ /**
73
+ * Schema of the loop engine composition entry.
74
+ *
75
+ * A schemastery object validates each field only when it is present and lets
76
+ * an absent key fall through as `undefined`, so omitted knobs are accepted —
77
+ * matching the permissive interface and read path (`resolvePatchPath` defaults
78
+ * the patch path; each engine driver resolves only the knobs it owns and
79
+ * omitted deployment tunables fall back to the session). The composition entry
80
+ * is an engine-agnostic superset: the selectable knobs belong to whichever
81
+ * engine the settings pick at runtime, so both engines' knobs may coexist and
82
+ * only the selected one is consumed.
83
+ */
84
+ export declare const Config: z<Config>;
85
+ /** Resolve the managed patch file from configuration, defaulting to the web profile. */
86
+ export declare function resolvePatchPath(config: Config): string;
87
+ /** Atomically replace the patch file (same-directory temp + rename). */
88
+ export declare function writePatchFile(path: string, text: string): Promise<void>;
89
+ /**
90
+ * Synchronously atomically replace the patch file. The engine-selection
91
+ * onChange is a synchronous hook with no await, and the write MUST land before
92
+ * the caller is told the switch committed — otherwise a user who restarts
93
+ * `dsh web` immediately reads the stale file and the previous engine boots.
94
+ * @param path - the profile's patch file.
95
+ * @param text - the next file content.
96
+ */
97
+ export declare function writePatchFileSync(path: string, text: string): void;
98
+ /**
99
+ * Rewrite the managed block for a target engine, preserving the rest of the
100
+ * file byte for byte. Only writes when the file actually differs.
101
+ * @param path - the profile's patch file.
102
+ * @param engine - the target engine.
103
+ * @returns whether a write occurred.
104
+ */
105
+ export declare function syncManagedBlock(path: string, engine: LoopEngineId): Promise<boolean>;
106
+ /**
107
+ * Apply the plugin: seed the settings section from the managed block, host
108
+ * the non-default engine factory when the block says so, and translate
109
+ * committed engine changes into managed-block writes.
110
+ * @param ctx - the composing context.
111
+ * @param config - composition entry for the managed patch file.
112
+ */
113
+ export declare function apply(ctx: Context, config: Config): void;
114
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Package-owned invariant companion for the loop engine selection.
3
+ *
4
+ * The plugin's owned relationship is the patch-manager round trip: rendering
5
+ * a managed block for an engine and reading it back must produce the same
6
+ * engine, and the `in-process` engine must render an absent block (so the base
7
+ * bundle's `agent-loop` row stays mounted). The companion asserts both against
8
+ * the pure transform, binding the writer's inverse to the reader directly.
9
+ *
10
+ * @module dsh-loop-engine/invariant
11
+ */
12
+ import type { Context } from '@deepseek-ai/cordis';
13
+ /** Cordis companion plugin name. */
14
+ export declare const name = "loop-engine-invariant";
15
+ /** Services required before the companion can register. */
16
+ export declare const inject: string[];
17
+ /**
18
+ * Register the loop-engine invariant contribution.
19
+ * @param ctx - Cordis context carrying the invariant service.
20
+ * @returns the installed registration's disposer after setup succeeds.
21
+ */
22
+ export declare const apply: (ctx: Context) => Promise<() => void>;
23
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Loop engine namespace literal: the one string both halves agree on, in a
3
+ * module with no runtime imports so the browser bundle can import it without
4
+ * dragging `dsh-settings` (a host-side service) into the client artifact.
5
+ * @module dsh-loop-engine/namespace
6
+ */
7
+ /** Settings namespace carrying the deployment's selected agent loop engine. */
8
+ export declare const LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
9
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Managed-block editing for a profile's `cordis.patch.yml`.
3
+ *
4
+ * The plugin owns one contiguous block inside the user's patch file, delimited
5
+ * by a begin/end marker pair, and rewrites only that span on engine switches
6
+ * — everything else the user wrote (other patches, their comments) survives
7
+ * byte for byte. The block's content is the loader patch that takes the loop
8
+ * engine over: it disables the base bundle's `agent-loop` row so this plugin's
9
+ * factory (hosted by dsh-loop-engine) can register without colliding, because
10
+ * the harness admits exactly one AgentFactory:
11
+ *
12
+ * # -- dsh-loop-engine managed block: claude-code --
13
+ * - id: agent-loop
14
+ * disabled: true
15
+ * - id: command-goal
16
+ * disabled: true
17
+ * # -- /dsh-loop-engine managed block --
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
+ *
26
+ * `in-process` renders an absent block (the base bundle's `agent-loop` row
27
+ * stays active and supplies the factory), so switching back removes the span
28
+ * entirely. Any other engine renders the same disable block, and the begin
29
+ * marker carries the specific engine id (`# -- dsh-loop-engine managed block:
30
+ * claude-code --`) so `currentEngineOf` can read which non-default engine owns
31
+ * the slot from the file alone. All functions here are pure string transforms —
32
+ * file I/O and durability live in the plugin's apply.
33
+ *
34
+ * @module dsh-loop-engine/patch-manager
35
+ */
36
+ import type { LoopEngineId } from './settings.ts';
37
+ /** Begin marker of the plugin-managed span inside a profile patch file. */
38
+ export declare const MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block: ";
39
+ /** End marker of the plugin-managed span inside a profile patch file. */
40
+ export declare const MANAGED_BLOCK_END = "# -- /dsh-loop-engine managed block --";
41
+ /** Render the managed block for one engine; `in-process` returns the empty span. */
42
+ export declare function renderManagedBlock(engine: LoopEngineId): string;
43
+ /** Whether a patch-file text contains the managed block span. */
44
+ export declare function hasManagedBlock(text: string): boolean;
45
+ /** Derive the current engine from a patch-file text by the managed block's begin marker. */
46
+ export declare function currentEngineOf(text: string): LoopEngineId;
47
+ /**
48
+ * Produce the next patch-file text for a target engine, preserving every byte
49
+ * outside the managed span. Appends the span when absent; replaces or removes
50
+ * it when present. The managed block is a root-level collection, so a leftover
51
+ * seed `[]` is dropped when adding it, and a removal that leaves no entries is
52
+ * re-seeded back to `[]` — either way the file stays a single valid top-level
53
+ * array the harness can boot.
54
+ * @param text - current patch-file text.
55
+ * @param engine - target engine.
56
+ * @returns the rewritten patch-file text.
57
+ */
58
+ export declare function applyManagedBlock(text: string, engine: LoopEngineId): string;
59
+ //# sourceMappingURL=patch-manager.d.ts.map
@@ -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,49 @@
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, LlmModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
16
+ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
17
+ import type { LoopEngineId } from './settings.ts';
18
+ import type { PiModelEntry } from './engine-pi/probe.ts';
19
+ /** Provider route label each hosted engine logs into its sessions' request/header. */
20
+ export declare const HOSTED_PROVIDER_ROUTES: Readonly<Record<Exclude<LoopEngineId, 'in-process'>, string>>;
21
+ /** Injectable catalog source a hosted engine route can advertise over the placeholder. */
22
+ export interface HostedEngineRouteAdapterOptions {
23
+ /**
24
+ * Optional model catalog generator. When present, `listModels` advertises
25
+ * these entries under this route's provider label; when absent, the catalog
26
+ * stays empty (the default, "engine owns its models" behavior).
27
+ */
28
+ readonly listModels?: () => readonly PiModelEntry[];
29
+ }
30
+ /**
31
+ * Placeholder adapter serving one hosted engine's provider route label. It
32
+ * inherits the empty catalog and default metadata (the engine's model is not a
33
+ * harness-selectable endpoint), and {@link stream} fails loud: a call reaching
34
+ * it means a real model query was routed to an engine that owns its model
35
+ * natively — a wiring bug, not a request to serve.
36
+ */
37
+ export declare class HostedEngineRouteAdapter extends LlmAdapter {
38
+ private readonly label;
39
+ private readonly options;
40
+ /**
41
+ * @param label - the provider route label this placeholder serves.
42
+ * @param options - optional catalog source; omit for an empty catalog.
43
+ */
44
+ constructor(label: string, options?: HostedEngineRouteAdapterOptions);
45
+ /** Advertise the injected Pi models (if any) under this route's provider label. */
46
+ listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
47
+ stream(_options: GenerateOptions): AsyncIterable<StreamChunk>;
48
+ }
49
+ //# sourceMappingURL=provider-route.d.ts.map