dsh-loop-engine 0.1.5-rc1
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.
- package/README.md +125 -0
- package/README.zh.md +53 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +675 -0
- package/lib/index.js +6821 -0
- package/lib/invariant.js +108 -0
- package/lib/types/client/LoopEngineBadge.d.ts +34 -0
- package/lib/types/client/LoopEngineComposerSelect.d.ts +40 -0
- package/lib/types/client/LoopEngineSection.d.ts +34 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/locales.d.ts +46 -0
- package/lib/types/client/store.d.ts +58 -0
- package/lib/types/commands.d.ts +69 -0
- package/lib/types/driver-core/assistant-stream.d.ts +68 -0
- package/lib/types/driver-core/context-files.d.ts +62 -0
- package/lib/types/driver-core/inbox.d.ts +104 -0
- package/lib/types/driver-core/ownership.d.ts +40 -0
- package/lib/types/driver-core/permission-knobs.d.ts +26 -0
- package/lib/types/driver-core/prompt.d.ts +23 -0
- package/lib/types/driver-core/skill-inject.d.ts +59 -0
- package/lib/types/engine-claude/agent.d.ts +106 -0
- package/lib/types/engine-claude/loop.d.ts +111 -0
- package/lib/types/engine-claude/mapping.d.ts +83 -0
- package/lib/types/engine-claude/permission.d.ts +41 -0
- package/lib/types/engine-claude/process.d.ts +59 -0
- package/lib/types/engine-claude/sdk.d.ts +57 -0
- package/lib/types/engine-claude/types.d.ts +18 -0
- package/lib/types/engine-codex/agent.d.ts +113 -0
- package/lib/types/engine-codex/appserver/client.d.ts +49 -0
- package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
- package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
- package/lib/types/engine-codex/appserver/types.d.ts +215 -0
- package/lib/types/engine-codex/loop.d.ts +114 -0
- package/lib/types/engine-codex/permission.d.ts +32 -0
- package/lib/types/engine-codex/skills.d.ts +29 -0
- package/lib/types/engine-codex/types.d.ts +19 -0
- package/lib/types/engine-kimi/acp/client.d.ts +76 -0
- package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
- package/lib/types/engine-kimi/acp/types.d.ts +95 -0
- package/lib/types/engine-kimi/agent.d.ts +130 -0
- package/lib/types/engine-kimi/commands.d.ts +40 -0
- package/lib/types/engine-kimi/loop.d.ts +108 -0
- package/lib/types/engine-kimi/mapping.d.ts +71 -0
- package/lib/types/engine-kimi/permission.d.ts +28 -0
- package/lib/types/engine-kimi/process.d.ts +61 -0
- package/lib/types/engine-kimi/skills.d.ts +57 -0
- package/lib/types/engine-kimi/types.d.ts +23 -0
- package/lib/types/engine-pi/agent.d.ts +150 -0
- package/lib/types/engine-pi/loop.d.ts +125 -0
- package/lib/types/engine-pi/permission.d.ts +43 -0
- package/lib/types/engine-pi/probe.d.ts +23 -0
- package/lib/types/engine-pi/rpc/client.d.ts +105 -0
- package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
- package/lib/types/engine-pi/rpc/types.d.ts +235 -0
- package/lib/types/engine-pi/skills.d.ts +55 -0
- package/lib/types/engine-pi/types.d.ts +27 -0
- package/lib/types/index.d.ts +114 -0
- package/lib/types/invariant.d.ts +23 -0
- package/lib/types/namespace.d.ts +9 -0
- package/lib/types/patch-manager.d.ts +59 -0
- package/lib/types/preset.d.ts +73 -0
- package/lib/types/provider-route.d.ts +49 -0
- package/lib/types/settings.d.ts +31 -0
- package/lib/types/skills.d.ts +93 -0
- package/package.json +104 -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
|