@vidge/dsh-agent-hub 0.1.0-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/LICENSE +21 -0
- package/README.md +133 -0
- package/README.zh.md +115 -0
- package/cordis.patch.yml +22 -0
- package/lib/client.js +1494 -0
- package/lib/index.js +5045 -0
- package/lib/invariant.js +96 -0
- package/lib/types/client/LoopEngineComposerSelect.d.ts +73 -0
- package/lib/types/client/LoopEngineSection.d.ts +43 -0
- package/lib/types/client/engine-rpc.d.ts +74 -0
- package/lib/types/client/index.d.ts +28 -0
- package/lib/types/client/locales.d.ts +44 -0
- package/lib/types/client/session-location.d.ts +63 -0
- package/lib/types/client/store.d.ts +58 -0
- package/lib/types/commands.d.ts +69 -0
- package/lib/types/driver-core/context-files.d.ts +62 -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 +116 -0
- package/lib/types/engine-claude/loop.d.ts +99 -0
- package/lib/types/engine-claude/mapping.d.ts +84 -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/provider-env.d.ts +50 -0
- package/lib/types/engine-claude/sdk.d.ts +101 -0
- package/lib/types/engine-claude/types.d.ts +28 -0
- package/lib/types/engine-codex/agent.d.ts +109 -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 +92 -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-pi/agent.d.ts +125 -0
- package/lib/types/engine-pi/loop.d.ts +96 -0
- package/lib/types/engine-pi/permission.d.ts +43 -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/engine-record.d.ts +124 -0
- package/lib/types/index.d.ts +138 -0
- package/lib/types/invariant.d.ts +23 -0
- package/lib/types/llm-compat.d.ts +32 -0
- package/lib/types/namespace.d.ts +19 -0
- package/lib/types/patch-manager.d.ts +78 -0
- package/lib/types/router.d.ts +189 -0
- package/lib/types/rpc.d.ts +113 -0
- package/lib/types/settings.d.ts +29 -0
- package/lib/types/skills.d.ts +93 -0
- package/package.json +107 -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-agent-hub/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-agent-hub/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-agent-hub/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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable per-session engine records.
|
|
3
|
+
*
|
|
4
|
+
* A session's engine must survive the process, because resuming a session on
|
|
5
|
+
* a different engine replays history that engine cannot act on. The harness
|
|
6
|
+
* offers no writable durable slot for this:
|
|
7
|
+
*
|
|
8
|
+
* - `SessionHeader.agentPreset` is owned by the agent-presets subsystem — the
|
|
9
|
+
* web controller writes it through `composeAgent()` and asserts it unchanged
|
|
10
|
+
* on resume — and `SessionHeader` has no free-form dict (its fields are
|
|
11
|
+
* closed at two `meta` literals and the SQLite column list).
|
|
12
|
+
* - A custom session event is worse than unavailable: the persistence
|
|
13
|
+
* coordinator refuses to load any log carrying a type outside the generated
|
|
14
|
+
* `KNOWN_SESSION_EVENT_TYPES` unless the envelope sets `ignorable`, which
|
|
15
|
+
* `Session.append()` provides no way to do. An out-of-repo event would make
|
|
16
|
+
* every session this plugin wrote permanently unloadable.
|
|
17
|
+
*
|
|
18
|
+
* So the record lives in a plugin-owned sidecar keyed by session id, written
|
|
19
|
+
* with the same atomic temp+rename discipline as the managed patch file.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-agent-hub/engine-record
|
|
22
|
+
*/
|
|
23
|
+
import { type LoopEngineId } from './settings.ts';
|
|
24
|
+
/** Backend-owned artifact location, as returned by `sessionPersistence.locate`. */
|
|
25
|
+
export interface SessionLocation {
|
|
26
|
+
readonly kind: string;
|
|
27
|
+
readonly path: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The session metadata `locate` reads to derive an artifact path.
|
|
31
|
+
*
|
|
32
|
+
* `cwd` is NOT optional decoration: the JSONL backend groups sessions into a
|
|
33
|
+
* per-project directory keyed by it (`projectDir(root, cwd)`), and an
|
|
34
|
+
* `undefined` cwd selects the literal `_no-cwd` bucket. Passing only `id`
|
|
35
|
+
* therefore returns a path in a directory the session does not live in — the
|
|
36
|
+
* sidecar is written where nothing will ever read it, and every resume falls
|
|
37
|
+
* through to the `in-process` default.
|
|
38
|
+
*/
|
|
39
|
+
export interface SessionMetaLike {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly cwd?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The persistence surface the record store borrows. Declared structurally so
|
|
45
|
+
* this module needs no peer dependency on the persistence package, and so a
|
|
46
|
+
* profile without persistence degrades to the shared fallback directory.
|
|
47
|
+
*/
|
|
48
|
+
export interface RecordPersistence {
|
|
49
|
+
locate(meta: SessionMetaLike): SessionLocation | undefined;
|
|
50
|
+
inspect(id: string, signal?: AbortSignal): Promise<{
|
|
51
|
+
meta: SessionMetaLike;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
/** Fallback directory for backends with no per-session artifact (e.g. SQLite). */
|
|
55
|
+
export declare function fallbackDir(): string;
|
|
56
|
+
/** The fallback record path for one session id. */
|
|
57
|
+
export declare function fallbackPathFor(sessionId: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Reads and writes the per-session engine record.
|
|
60
|
+
*
|
|
61
|
+
* The store prefers a sidecar beside the backend's own per-session artifact so
|
|
62
|
+
* the record travels with the session; backends that own no such artifact fall
|
|
63
|
+
* back to a shared directory under the dsh home.
|
|
64
|
+
*/
|
|
65
|
+
export declare class EngineRecordStore {
|
|
66
|
+
private readonly persistence;
|
|
67
|
+
constructor(persistence: () => RecordPersistence | undefined);
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the record path for a session, preferring the backend's artifact
|
|
70
|
+
* directory. Uses `locate`, which explicitly does not read, create, or
|
|
71
|
+
* materialize the artifact.
|
|
72
|
+
*
|
|
73
|
+
* `cwd` must be supplied whenever it is known: it selects the backend's
|
|
74
|
+
* per-project directory, so omitting it silently addresses a different
|
|
75
|
+
* directory than the one holding the session.
|
|
76
|
+
*/
|
|
77
|
+
private pathFor;
|
|
78
|
+
/**
|
|
79
|
+
* Record a session's engine. Called before the engine creates the session,
|
|
80
|
+
* so the record is never missing for a session that exists.
|
|
81
|
+
* @param meta - the session being created, including the `cwd` that keys its artifact directory.
|
|
82
|
+
* @param engine - the engine that will own it.
|
|
83
|
+
*/
|
|
84
|
+
remember(meta: SessionMetaLike, engine: LoopEngineId): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Recover a session's engine.
|
|
87
|
+
*
|
|
88
|
+
* The session's `cwd` is not known to the caller on the resume path — only
|
|
89
|
+
* its id — so the backend's own header is consulted first to rebuild the
|
|
90
|
+
* artifact path. A backend that cannot inspect (or a session it does not
|
|
91
|
+
* know) degrades to the shared fallback directory rather than failing the
|
|
92
|
+
* resume: a lost record costs the default engine, an exception costs the
|
|
93
|
+
* session.
|
|
94
|
+
*
|
|
95
|
+
* Sessions written before this plugin gained per-session routing carry no
|
|
96
|
+
* record; they ran on whichever engine was globally selected, and the base
|
|
97
|
+
* in-process loop is the only safe default — it is what an unpatched profile
|
|
98
|
+
* boots with.
|
|
99
|
+
* @param sessionId - the session being resumed.
|
|
100
|
+
* @returns the recorded engine, or `in-process` when no record exists.
|
|
101
|
+
*/
|
|
102
|
+
recall(sessionId: string): Promise<LoopEngineId>;
|
|
103
|
+
/**
|
|
104
|
+
* Rebuild a session's sidecar path from the backend's stored header.
|
|
105
|
+
*
|
|
106
|
+
* `inspect` is the correct probe here rather than `prepare`: it is a
|
|
107
|
+
* non-exclusive borrow that returns a validated header and leaves the
|
|
108
|
+
* coordinator's reusable cold session in place for the real engine's
|
|
109
|
+
* subsequent `prepare`, whereas `prepare` would take the exclusive
|
|
110
|
+
* reservation out from under it.
|
|
111
|
+
*
|
|
112
|
+
* @param sessionId - the session whose artifact directory is wanted.
|
|
113
|
+
* @returns the sidecar path, or `undefined` when it cannot be derived.
|
|
114
|
+
*/
|
|
115
|
+
private locateByInspect;
|
|
116
|
+
/**
|
|
117
|
+
* Drop fallback records whose session no longer resolves, bounded per run so
|
|
118
|
+
* a large history cannot stall startup. Sidecars beside a backend artifact
|
|
119
|
+
* need no sweep: the backend removes the directory with the session.
|
|
120
|
+
* @param limit - maximum number of records to examine.
|
|
121
|
+
*/
|
|
122
|
+
collectOrphans(limit?: number): Promise<number>;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=engine-record.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-switchable agent loop engine, node half.
|
|
3
|
+
*
|
|
4
|
+
* Hosts every agent-loop engine (the base in-process loop, Claude Code, Codex,
|
|
5
|
+
* and Pi) and gives each session its own. The harness admits exactly one
|
|
6
|
+
* AgentFactory, so this plugin registers a {@link LoopEngineRouter} in that slot
|
|
7
|
+
* for the life of the process and mounts the four engines behind it through
|
|
8
|
+
* shadowed contexts that redirect their `setFactory` calls into router
|
|
9
|
+
* registration. The engine classes themselves are unmodified.
|
|
10
|
+
*
|
|
11
|
+
* A session's engine is fixed when it is created and recorded durably
|
|
12
|
+
* ({@link EngineRecordStore}), because resuming a session on a different engine
|
|
13
|
+
* would replay history that engine cannot act on — each engine writes its own
|
|
14
|
+
* provenance. The `agent-loop-engine` settings section therefore selects the
|
|
15
|
+
* engine for the *next new* session only; switching it never disturbs a running
|
|
16
|
+
* session and needs no restart.
|
|
17
|
+
*
|
|
18
|
+
* The managed block in the profile's `cordis.patch.yml` is now permanent and
|
|
19
|
+
* engine-independent: it disables the base bundle's `agent-loop` row so this
|
|
20
|
+
* plugin owns the slot in every configuration.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-agent-hub
|
|
23
|
+
*/
|
|
24
|
+
import { Context, type Fiber } from '@deepseek-ai/cordis';
|
|
25
|
+
import z from '@deepseek-ai/schemastery';
|
|
26
|
+
import { type Config as ClaudeCodeConfig } from './engine-claude/loop.ts';
|
|
27
|
+
import type { CodexApprovalPolicy, CodexSandboxMode } from './engine-codex/types.ts';
|
|
28
|
+
import { type LoopEngineId } from './settings.ts';
|
|
29
|
+
export declare const name = "loop-engine";
|
|
30
|
+
/**
|
|
31
|
+
* Services the plugin's own fiber requires.
|
|
32
|
+
*
|
|
33
|
+
* `agents` and `systemPrompt` are read directly by {@link apply}: the router
|
|
34
|
+
* claims the single AgentFactory slot on `ctx.agents`, and each engine mounts
|
|
35
|
+
* through a context that shadows both members. Cordis refuses a bare property
|
|
36
|
+
* read on an undeclared service ("cannot get property \"agents\" without
|
|
37
|
+
* inject"), so both must be listed — and listing them also makes the fiber wait
|
|
38
|
+
* for them rather than racing their providers.
|
|
39
|
+
*
|
|
40
|
+
* The optional host services (`commands`, `skills`) stay out: they are resolved
|
|
41
|
+
* lazily via `ctx.get` and may legitimately be absent from a minimal profile.
|
|
42
|
+
* The hosted engine factories declare their own `inject` when mounted as
|
|
43
|
+
* children.
|
|
44
|
+
*/
|
|
45
|
+
export declare const inject: string[];
|
|
46
|
+
/** Composition entry for the loop engine selection and the hosted engine drivers. */
|
|
47
|
+
export interface Config extends ClaudeCodeConfig {
|
|
48
|
+
/** Profile whose `cordis.patch.yml` carries the managed block; defaults to `web`. */
|
|
49
|
+
profile?: string;
|
|
50
|
+
/** Patch file name inside the profile; defaults to `cordis.patch.yml`. */
|
|
51
|
+
patchFilename?: string;
|
|
52
|
+
/** Explicit absolute path to the patch file, overriding profile + filename. */
|
|
53
|
+
patchPath?: string;
|
|
54
|
+
/** Pinned Codex sandbox mode; falls back to the session's dsh permission knobs. */
|
|
55
|
+
sandboxMode?: CodexSandboxMode;
|
|
56
|
+
/** Pinned Codex approval policy; falls back to the session's dsh permission knobs. */
|
|
57
|
+
approvalPolicy?: CodexApprovalPolicy;
|
|
58
|
+
/** LLM provider for the Pi RPC child (`--provider`). */
|
|
59
|
+
piProvider?: string;
|
|
60
|
+
/** Thinking/reasoning level for the Pi RPC child, appended to its `--model`. */
|
|
61
|
+
piThinking?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Schema of the loop engine composition entry.
|
|
65
|
+
*
|
|
66
|
+
* A schemastery object validates each field only when it is present and lets
|
|
67
|
+
* an absent key fall through as `undefined`, so omitted knobs are accepted —
|
|
68
|
+
* matching the permissive interface and read path (`resolvePatchPath` defaults
|
|
69
|
+
* the patch path; each engine driver resolves only the knobs it owns and
|
|
70
|
+
* omitted deployment tunables fall back to the session). The composition entry
|
|
71
|
+
* is an engine-agnostic superset: the selectable knobs belong to whichever
|
|
72
|
+
* engine the settings pick at runtime, so both engines' knobs may coexist and
|
|
73
|
+
* only the selected one is consumed.
|
|
74
|
+
*/
|
|
75
|
+
export declare const Config: z<Config>;
|
|
76
|
+
/** Resolve the managed patch file from configuration, defaulting to the web profile. */
|
|
77
|
+
export declare function resolvePatchPath(config: Config): string;
|
|
78
|
+
/** Atomically replace the patch file (same-directory temp + rename). */
|
|
79
|
+
export declare function writePatchFile(path: string, text: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Synchronously atomically replace the patch file. The engine-selection
|
|
82
|
+
* onChange is a synchronous hook with no await, and the write MUST land before
|
|
83
|
+
* the caller is told the switch committed — otherwise a user who restarts
|
|
84
|
+
* `dsh web` immediately reads the stale file and the previous engine boots.
|
|
85
|
+
* @param path - the profile's patch file.
|
|
86
|
+
* @param text - the next file content.
|
|
87
|
+
*/
|
|
88
|
+
export declare function writePatchFileSync(path: string, text: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Ensure the permanent managed block is present, preserving the rest of the
|
|
91
|
+
* file byte for byte. Only writes when the file actually differs.
|
|
92
|
+
* @param path - the profile's patch file.
|
|
93
|
+
* @returns whether a write occurred.
|
|
94
|
+
*/
|
|
95
|
+
export declare function syncManagedBlock(path: string): Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Mount the base in-process loop behind the router.
|
|
98
|
+
*
|
|
99
|
+
* The plugin now owns the AgentFactory slot in every configuration, so the base
|
|
100
|
+
* loop can no longer register itself through the bundle — the managed block
|
|
101
|
+
* disables its row. It is instead hosted here, through the same shadowed
|
|
102
|
+
* context as the other engines, which keeps `in-process` a first-class
|
|
103
|
+
* per-session choice.
|
|
104
|
+
*
|
|
105
|
+
* The import is dynamic and failure-tolerant: the package is a peer, and a
|
|
106
|
+
* deployment that omits it should lose only the in-process engine rather than
|
|
107
|
+
* failing the whole plugin tree.
|
|
108
|
+
*
|
|
109
|
+
* @param ctx - the plugin context, used for diagnostics.
|
|
110
|
+
* @param loopCtx - the shadowed context that redirects `setFactory` to the router.
|
|
111
|
+
* @param mount - the shared fiber-start helper.
|
|
112
|
+
*/
|
|
113
|
+
export declare function mountBaseLoop(ctx: Context, loopCtx: Context, mount: (engine: LoopEngineId, plugin: () => (Fiber & PromiseLike<Fiber>)) => void): void;
|
|
114
|
+
/**
|
|
115
|
+
* Register an engine's skill provider into ONE agent's own scope layer.
|
|
116
|
+
*
|
|
117
|
+
* The harness's skill registry is layered by `scopeOf(ctx)`, and every engine
|
|
118
|
+
* mints its agent as its own scope key then hands that scope-tagged context to
|
|
119
|
+
* `setup`. Registering through that context therefore makes the provider
|
|
120
|
+
* visible to exactly one session — a codex session never sees
|
|
121
|
+
* `~/.claude/skills/` — with no per-provider engine predicate, and the
|
|
122
|
+
* registration unwinds with the agent's scope.
|
|
123
|
+
*
|
|
124
|
+
* `in-process` contributes nothing: the base loop brings the harness's own
|
|
125
|
+
* skills, and this plugin adds no engine-specific ones for it.
|
|
126
|
+
*
|
|
127
|
+
* @param engine - the engine that owns the session being set up.
|
|
128
|
+
* @param agentCtx - the scope-tagged agent context the engine passes to `setup`.
|
|
129
|
+
*/
|
|
130
|
+
export declare function registerEngineSkills(engine: LoopEngineId, agentCtx: Context): void;
|
|
131
|
+
/**
|
|
132
|
+
* Apply the plugin: own the AgentFactory slot with the router, mount every
|
|
133
|
+
* engine behind it, and track the selection for new sessions.
|
|
134
|
+
* @param ctx - the composing context.
|
|
135
|
+
* @param config - composition entry for the managed patch file.
|
|
136
|
+
*/
|
|
137
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
138
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for the managed patch block.
|
|
3
|
+
*
|
|
4
|
+
* The plugin's owned relationship is that the managed block is a permanent
|
|
5
|
+
* fixed point: applying it is idempotent, it always yields the row that frees
|
|
6
|
+
* the AgentFactory slot for this plugin's router, and it upgrades a legacy
|
|
7
|
+
* engine-tagged block from the era when the block encoded the selection. The
|
|
8
|
+
* companion asserts these against the pure transform.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-agent-hub/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,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-version shim for the one dsh-llm export that was renamed mid-line.
|
|
3
|
+
*
|
|
4
|
+
* `dsh-llm` renamed its tool-call brand between the two published lines:
|
|
5
|
+
*
|
|
6
|
+
* - `0.1.1-rc.2` (dist-tag `latest`, what plain `dsh` installs) exports `CallId`
|
|
7
|
+
* - `0.1.2-alpha.2` (dist-tag `alpha`, the harness monorepo source) exports `ToolCallId`
|
|
8
|
+
*
|
|
9
|
+
* A named import of either one is a hard ESM link error against the other line
|
|
10
|
+
* ("does not provide an export named ..."), which fails the whole plugin tree at
|
|
11
|
+
* boot before any of our code runs. A namespace import is not checked per-member
|
|
12
|
+
* at link time, so it loads under both and we can pick the survivor at runtime.
|
|
13
|
+
*
|
|
14
|
+
* The brand type is deliberately derived from `ToolResultMessageInput['callId']`
|
|
15
|
+
* rather than hard-coded to either `Branded<'CallId'>` or `Branded<'ToolCallId'>`:
|
|
16
|
+
* the two lines brand with different literals, so only the installed package's
|
|
17
|
+
* own view of the type is assignable to the APIs we hand these values to.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-agent-hub/llm-compat
|
|
20
|
+
*/
|
|
21
|
+
import type { ToolResultMessageInput } from '@deepseek-ai/dsh-llm';
|
|
22
|
+
/**
|
|
23
|
+
* The tool-call id brand, as the *installed* dsh-llm defines it. Aliased off a
|
|
24
|
+
* consuming API so it stays correct on both the `CallId` and `ToolCallId` lines.
|
|
25
|
+
*/
|
|
26
|
+
export type CallId = ToolResultMessageInput['callId'];
|
|
27
|
+
/**
|
|
28
|
+
* Brand a raw string as a tool-call id, resolving to whichever name the
|
|
29
|
+
* installed dsh-llm publishes.
|
|
30
|
+
*/
|
|
31
|
+
export declare const CallId: (id: string) => CallId;
|
|
32
|
+
//# sourceMappingURL=llm-compat.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop engine constants shared by both halves, in a module with no runtime
|
|
3
|
+
* imports so the browser bundle can import them without dragging
|
|
4
|
+
* `dsh-settings` (a host-side service) into the client artifact.
|
|
5
|
+
*
|
|
6
|
+
* That constraint is load-bearing, not stylistic: the browser bundle inlines
|
|
7
|
+
* non-seed specifiers (see `build.mjs`), so a value imported from
|
|
8
|
+
* `./settings.ts` — which imports `dsh-settings` — pulls the whole host-side
|
|
9
|
+
* settings module into the client build. Anything both halves need as a
|
|
10
|
+
* *value* belongs here; `./settings.ts` re-exports it for the node half.
|
|
11
|
+
* @module dsh-agent-hub/namespace
|
|
12
|
+
*/
|
|
13
|
+
/** Settings namespace carrying the deployment's selected agent loop engine. */
|
|
14
|
+
export declare const LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
|
|
15
|
+
/** The installed engines driving new Agent turns. */
|
|
16
|
+
export declare const LOOP_ENGINE_IDS: readonly ["in-process", "claude-code", "codex", "pi"];
|
|
17
|
+
/** Installed agent loop engine id. */
|
|
18
|
+
export type LoopEngineId = (typeof LOOP_ENGINE_IDS)[number];
|
|
19
|
+
//# sourceMappingURL=namespace.d.ts.map
|