@khorsheed/dsh-local-agent-dsh 0.2.0-rc.1 → 0.2.0

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.
@@ -0,0 +1,1920 @@
1
+ import { provisionDshScope, unitSubProfileName } from "./provision.js";
2
+ import { t as resolveDshSessionLog } from "./session-log-DOsSz56-.js";
3
+ import { join } from "node:path";
4
+ import { LiveFlush, LiveStreamPublisher, assertResumeCwdUnchanged, assertResumeScopeUnchanged, containerExecSpawn, containerScopedHome, delegationEnv, establishSubagentCatalogChild, persistChildSession, probeCliVersion, resolveChildCwd, resolveRoundModel, subagentDelegationLabel } from "@khorsheed/dsh-local-agent";
5
+ import { createHash, randomUUID } from "node:crypto";
6
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
7
+ import { SessionId } from "@deepseek-ai/dsh-session";
8
+ import { NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, settleRunResult, subprocessRunHandle } from "@deepseek-ai/dsh-subagent";
9
+ import { MEMBER_BRIDGE_SOCKET_ENV, MEMBER_BRIDGE_TOKEN_ENV } from "@khorsheed/dsh-local-agent/types";
10
+ import { StringDecoder } from "node:string_decoder";
11
+ import { BlockAssembler, createAssistantMessage } from "@deepseek-ai/dsh-llm";
12
+ import { LIVE_SERVER_NAME, LIVE_WIRE_PROTOCOL_VERSION } from "@khorsheed/dsh-local-agent-dsh-headless/wire";
13
+ import { readFile, readdir } from "node:fs/promises";
14
+ import { promisify } from "node:util";
15
+ import { zstdDecompress } from "node:zlib";
16
+ //#region lib/types/model-launch.js
17
+ /** Private launch environment: never persisted in a profile, transcript or argv. */
18
+ const MODEL_CONFIG_ENV = "DSH_LOCAL_AGENT_MODEL_CONFIG";
19
+ const MODEL_KEY_ENV = "DSH_LOCAL_AGENT_MODEL_API_KEY";
20
+ function object(value) {
21
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
22
+ }
23
+ function settings(ctx) {
24
+ return new Map((ctx.get("settings")?.describe?.() ?? []).flatMap((row) => {
25
+ const value = object(row.value);
26
+ return value === void 0 ? [] : [[String(row.ns), value]];
27
+ }));
28
+ }
29
+ function piProfile(ctx, provider, values) {
30
+ const directory = ctx.get("llm")?.listConfigurableProviders?.().find((row) => row.provider === provider);
31
+ const ns = directory?.settingsNs ?? "llm-pi-ai";
32
+ if (ns !== "llm-pi-ai") return void 0;
33
+ if (directory && (directory.settingsPath.length !== 2 || directory.settingsPath[0] !== "providers" || directory.settingsPath[1] !== provider)) return void 0;
34
+ return object(object(values.get(ns)?.providers)?.[provider]);
35
+ }
36
+ /** Whether this provider has a supported local configuration transfer contract. */
37
+ function canInheritModelProvider(ctx, provider) {
38
+ if (provider === "deepseek-official") return true;
39
+ const profile = piProfile(ctx, provider, settings(ctx));
40
+ return typeof profile?.apiKeyEnv === "string" && profile.apiKeyEnv.trim() !== "";
41
+ }
42
+ /** Snapshot only the selected local route and resolve only its explicitly named credential. */
43
+ async function localModelLaunch(ctx, config, model) {
44
+ const provider = model?.includes("/") ? model.slice(0, model.indexOf("/")) : "deepseek-official";
45
+ const values = settings(ctx);
46
+ let inherited, ref, keyName;
47
+ if (provider === "deepseek-official") {
48
+ const profile = structuredClone(values.get("llm-deepseek") ?? {});
49
+ ref = config.apiKeyRef ?? (typeof profile.apiKeyEnv === "string" ? profile.apiKeyEnv : "DEEPSEEK_API_KEY");
50
+ inherited = { deepseek: {
51
+ ...profile,
52
+ apiKeyEnv: "DEEPSEEK_API_KEY"
53
+ } };
54
+ keyName = "DEEPSEEK_API_KEY";
55
+ } else {
56
+ const profile = piProfile(ctx, provider, values);
57
+ if (typeof profile?.apiKeyEnv !== "string" || profile.apiKeyEnv.trim() === "") throw new Error(`subagent-dsh: model provider "${provider}" has no inheritable API-key route; configure its credential reference in the host Models settings`);
58
+ ref = profile.apiKeyEnv;
59
+ inherited = { piAi: { providers: { [provider]: {
60
+ ...structuredClone(profile),
61
+ apiKeyEnv: MODEL_KEY_ENV
62
+ } } } };
63
+ keyName = MODEL_KEY_ENV;
64
+ }
65
+ const credential = await ctx.get("credentials")?.resolve(credentialRef(ref));
66
+ if (!credential?.value) throw new Error(`subagent-dsh: credential ${ref} is unavailable for model provider "${provider}"`);
67
+ const env = {
68
+ [MODEL_CONFIG_ENV]: JSON.stringify(inherited),
69
+ [keyName]: credential.value
70
+ };
71
+ return {
72
+ env,
73
+ fingerprint: createHash("sha256").update(JSON.stringify(env)).digest("hex")
74
+ };
75
+ }
76
+ //#endregion
77
+ //#region lib/types/session-mirror.js
78
+ /**
79
+ * Mirror a sub-dsh session's events into the parent-side dsh subagent session,
80
+ * so a dsh delegation shows its full conversation (task, replies, thinking)
81
+ * and real token usage in the standard 子代理 surface — the same fidelity the
82
+ * other family harnesses get from their CLI transcript files. The sub-dsh
83
+ * writes a real dsh session into the harness scoped home with the SAME id as
84
+ * the parent-side child session and the same SessionEvent format, so the
85
+ * mirror copies finalized `user/message`/`assistant/message` events verbatim
86
+ * (no parsing, no re-encoding): reasoning blocks render natively and usage
87
+ * rides the assistant events' own `usage` field into the tokenUsage
88
+ * projection.
89
+ *
90
+ * Round scoping: the parent provider appends one `turn/start` per delegation
91
+ * round, and the sub-dsh session numbers its turns identically (fresh = 1,
92
+ * each resume +1), so a round's span is the sub-dsh events from its
93
+ * `turn/start` with `turn === round` to the next `turn/start`. The sub-dsh's
94
+ * own scaffolding user messages (agent-instructions, plugin, skill-catalog
95
+ * sources) are filtered — only the caller task (`source.kind === 'user'`)
96
+ * crosses. The sub-dsh's own `step/start`/`step/end` pairs cross verbatim
97
+ * (each carries its (turn, step) coordinates): the real-time conversation
98
+ * assembler only registers a step — and materializes its assistant message —
99
+ * at a boundary, so without them the live subsession view renders the prompt
100
+ * and tool cards but no replies. Turn boundaries are NOT copied: the parent's
101
+ * real-time spawn→settle boundaries stay the authoritative timing.
102
+ *
103
+ * `todo/write` passthrough: the sub-dsh's task list is a standing whole-list
104
+ * snapshot (last-wins). Each pass appends the round's LATEST snapshot only
105
+ * when it differs from the child session's last mirrored one, so repeated
106
+ * passes never duplicate an identical snapshot and the child's `todos`
107
+ * projection (and the official TodoPanel in non-takeover views) reads the
108
+ * member's task list for free.
109
+ *
110
+ * Interrupted-tail closure: a sub-dsh that dies mid-step (the exec kill
111
+ * ladder, a live-runtime crash) leaves its log's step — and any advertised
112
+ * tool calls — OPEN, and the verbatim copy carries that open tail into the
113
+ * child session. The released-log reader (harness
114
+ * session-format-v3-to-v4/relationships.ts) rejects a `turn/end` over an open
115
+ * step on every cold read, so the provider's settle flow calls
116
+ * {@link closeMirroredOpenStep} after the settle mirror pass and before its
117
+ * own `turn/end`: each advertised-but-unresolved call receives a synthesized
118
+ * interrupted `tool/result` (the family's shared placeholder for a started
119
+ * call, the validator's exact TOOL_NOT_STARTED repair for one that never
120
+ * started) and the copied step receives its `step/end`. Mid-round the open
121
+ * pair still crosses as-is — the sub-dsh may yet write its end.
122
+ * @module @khorsheed/dsh-local-agent-dsh/session-mirror
123
+ */
124
+ /** Decompress one zstd session log (Node ≥22.15 built-in; engines require ^22.19). */
125
+ const decompressZstd = promisify(zstdDecompress);
126
+ /** Every zstd frame's little-endian magic number, marking a frame start in the stream. */
127
+ const ZSTD_FRAME_MAGIC = 4247762216;
128
+ /**
129
+ * Decompress a multi-frame zstd session log. The persistence layer appends
130
+ * one frame per flush batch, and Node's single-shot `zstdDecompress` stops at
131
+ * the first frame's end (a streaming decoder does the same), so each frame
132
+ * start is scanned and decompressed independently. A magic-looking byte
133
+ * sequence inside compressed data fails decompression and is skipped.
134
+ * @param content - the raw `.zstd` file bytes.
135
+ * @returns the concatenated plaintext of every frame.
136
+ */
137
+ async function decompressZstdFrames(content) {
138
+ const parts = [];
139
+ for (let offset = 0; offset + 4 <= content.length; offset += 1) {
140
+ if (content.readUInt32LE(offset) !== ZSTD_FRAME_MAGIC) continue;
141
+ try {
142
+ parts.push((await decompressZstd(content.subarray(offset))).toString("utf8"));
143
+ } catch {}
144
+ }
145
+ return parts.join("");
146
+ }
147
+ /**
148
+ * Read a sub-dsh session's event log from the scoped home. The session lives
149
+ * at `<homeDir>/sessions/<workspace>/<id>/` (the runner passes the bare uuid);
150
+ * WHICH file inside that directory holds the history is the host's choice —
151
+ * `session.jsonl` on the original generation, `session.v3.jsonl.zstd` on host
152
+ * 0.1.5 — so the name is resolved, never assumed (see
153
+ * {@link resolveDshSessionLog}). The first line is the header, the rest are
154
+ * events.
155
+ * @param homeDir - the `dsh` harness's scoped home.
156
+ * @param sessionId - the sub-dsh session id (same uuid as the child session).
157
+ * @returns the events and their source file, or undefined when the session is
158
+ * absent or unreadable.
159
+ */
160
+ async function readSubDshEvents(homeDir, sessionId) {
161
+ let workspaces;
162
+ try {
163
+ workspaces = await readdir(join(homeDir, "sessions"));
164
+ } catch {
165
+ return;
166
+ }
167
+ for (const workspace of workspaces) for (const name of [sessionId, `session-${sessionId}`]) {
168
+ const log = await resolveDshSessionLog(join(homeDir, "sessions", workspace, name));
169
+ if (log === void 0) continue;
170
+ let text;
171
+ try {
172
+ const content = await readFile(log.path);
173
+ text = log.compressed ? await decompressZstdFrames(content) : content.toString("utf8");
174
+ } catch {
175
+ continue;
176
+ }
177
+ const events = [];
178
+ for (const line of text.split("\n")) {
179
+ if (line === "") continue;
180
+ try {
181
+ const parsed = JSON.parse(line);
182
+ if (parsed.type === "session") continue;
183
+ events.push(parsed);
184
+ } catch {}
185
+ }
186
+ if (events.length > 0) return {
187
+ events,
188
+ log
189
+ };
190
+ }
191
+ }
192
+ /**
193
+ * Sum one more assistant event's usage into a running round total. Each
194
+ * sub-dsh assistant event carries ONE LLM call's accounting; the round's
195
+ * windows (live polls, settle pass) recompute it from the round's span, so
196
+ * the total is span-derived and never double counts across passes.
197
+ */
198
+ function addEventUsage(total, usage) {
199
+ const raw = usage;
200
+ if (raw === void 0 || typeof raw !== "object") return total;
201
+ const num = (value) => {
202
+ const n = Number(value);
203
+ return Number.isFinite(n) && n > 0 ? n : 0;
204
+ };
205
+ const merged = {
206
+ inputTokens: (total?.inputTokens ?? 0) + num(raw.inputTokens),
207
+ outputTokens: (total?.outputTokens ?? 0) + num(raw.outputTokens)
208
+ };
209
+ const cacheRead = (total?.cacheReadTokens ?? 0) + num(raw.cacheReadTokens);
210
+ const cacheWrite = (total?.cacheWriteTokens ?? 0) + num(raw.cacheWriteTokens);
211
+ if (cacheRead > 0) merged.cacheReadTokens = cacheRead;
212
+ if (cacheWrite > 0) merged.cacheWriteTokens = cacheWrite;
213
+ return merged;
214
+ }
215
+ /**
216
+ * Extract the round's observed model and token usage from its event span:
217
+ the LAST assistant event's `message.source` names the model that ran, and
218
+ * the assistant events' `usage` fields sum to the round's accounting.
219
+ */
220
+ function roundObservation(span) {
221
+ let observedModel;
222
+ let usage;
223
+ const byName = {};
224
+ let toolCount = 0;
225
+ for (const event of span) {
226
+ if (event.type === "tool/call") {
227
+ const name = event.data.name;
228
+ toolCount += 1;
229
+ const key = typeof name === "string" && name !== "" ? name : "tool";
230
+ byName[key] = (byName[key] ?? 0) + 1;
231
+ continue;
232
+ }
233
+ if (event.type !== "assistant/message") continue;
234
+ const data = event.data;
235
+ const source = data.message?.source;
236
+ if (source !== void 0 && typeof source === "object") {
237
+ const model = typeof source.model === "string" && source.model !== "" ? source.model : void 0;
238
+ if (model !== void 0) {
239
+ const provider = typeof source.provider === "string" && source.provider !== "" ? source.provider : void 0;
240
+ observedModel = provider === void 0 ? model : `${provider}/${model}`;
241
+ }
242
+ }
243
+ usage = addEventUsage(usage, data.usage);
244
+ }
245
+ return {
246
+ ...observedModel === void 0 ? {} : { observedModel },
247
+ ...usage === void 0 ? {} : { usage },
248
+ ...toolCount === 0 ? {} : { toolCalls: {
249
+ count: toolCount,
250
+ byName
251
+ } }
252
+ };
253
+ }
254
+ /** Visible answer text for progress/results; reasoning stays in the verbatim transcript. */
255
+ function mirroredEventText(event) {
256
+ const data = event.data;
257
+ return (data.message?.content ?? data.content ?? []).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
258
+ }
259
+ /** The delta-progress text for one mirrored tool event. */
260
+ function mirroredToolText(event) {
261
+ if (event.type === "tool/call") return `[工具 ${event.data.name ?? "tool"}]`;
262
+ const result = event.data.message?.content?.[0]?.content?.map((block) => block.text ?? "").join("") ?? "";
263
+ return result === "" ? "[工具结果]" : `[工具结果] ${result}`;
264
+ }
265
+ /**
266
+ * Find the child-session seq of the mirrored `tool/call` carrying `callId`,
267
+ * so a mirrored `tool/result`'s `sourceEventSeqs` points at the CHILD's call
268
+ * event — the source event's own seqs reference the sub-dsh session's
269
+ * numbering and are meaningless here.
270
+ */
271
+ function findMirroredCallSeq(childSession, callId) {
272
+ for (let index = childSession.snapshotEvents().length - 1; index >= 0; index -= 1) {
273
+ const event = childSession.snapshotEvents()[index];
274
+ if (event?.type !== "tool/call") continue;
275
+ if (event.data.callId === callId) return event.seq;
276
+ }
277
+ }
278
+ /**
279
+ * Append one mirrored `tool/call` or `tool/result` event — the native tool
280
+ * card pair, so the child session renders the sub-dsh's tool activity with
281
+ * the standard conversation's tool rows instead of not at all. Shared by the
282
+ * file mirror's span loop and the live driver's per-event mirror.
283
+ */
284
+ function appendMirroredToolEvent(childSession, event) {
285
+ if (event.type === "tool/call") childSession.append("tool/call", event.data);
286
+ else if (event.type === "tool/result") {
287
+ const data = event.data;
288
+ const callSeq = findMirroredCallSeq(childSession, String(data.message.source.callId));
289
+ childSession.append("tool/result", event.data, {
290
+ surfaceOp: "append",
291
+ ...callSeq === void 0 ? {} : { sourceEventSeqs: [callSeq] }
292
+ });
293
+ }
294
+ return mirroredToolText(event);
295
+ }
296
+ /**
297
+ * Append one mirrored `step/start` or `step/end` boundary verbatim — the event
298
+ * carries its own (turn, step) coordinates, so the child session's location
299
+ * index registers the same step the sub-dsh wrote. Shared by the file
300
+ * mirror's span loop and the live driver's per-event mirror. An interrupted
301
+ * step crosses as an OPEN pair (start without end): the location resolves to
302
+ * the step either way, and mid-round no boundary is synthesized — the sub-dsh
303
+ * may yet write its end. At settle the provider closes the copied tail with
304
+ * {@link closeMirroredOpenStep} before its own `turn/end`.
305
+ */
306
+ function appendMirroredStepBoundary(childSession, event) {
307
+ if (event.type === "step/start") childSession.append("step/start", event.data);
308
+ else if (event.type === "step/end") childSession.append("step/end", event.data);
309
+ }
310
+ /**
311
+ * Append one appendable message event verbatim and report its delta text —
312
+ * the single append path shared by the file mirror's span loop and the live
313
+ * driver's per-event mirror, so both transports produce identical child
314
+ * sessions.
315
+ */
316
+ function appendMirroredMessageEvent(childSession, event) {
317
+ if (event.type === "user/message") childSession.append("user/message", event.data, { surfaceOp: "append" });
318
+ else if (event.type === "assistant/message") childSession.append("assistant/message", event.data, { surfaceOp: "append" });
319
+ return mirroredEventText(event);
320
+ }
321
+ /**
322
+ * The placeholder text of the synthesized result for a tool call the sub-dsh
323
+ * started but never resolved (its process died between `tool/call` and
324
+ * `tool/result`). Shared verbatim with the kimi family's fold.
325
+ */
326
+ const DSH_INTERRUPTED_TOOL_RESULT = "(工具调用被中断,未能记录返回结果)";
327
+ /**
328
+ * The released-log reader's exact repair text for a call that was advertised
329
+ * but never recorded as started: relationships.ts's notStartedRepair admits
330
+ * only that shape (the `interrupted-tool-result-<callId>-<n>` id, the
331
+ * ToolNotStartedError/TOOL_NOT_STARTED error, no sourceEventSeqs, and this
332
+ * single text block) — no other wording resolves the call.
333
+ */
334
+ const TOOL_NOT_STARTED_REPAIR_TEXT = "The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed.";
335
+ /**
336
+ * The child session's currently-open copied step: the (turn, step) of a
337
+ * `step/start` whose `step/end` has not crossed, inside a turn the provider
338
+ * has not closed. Undefined when nothing is open. The live driver's
339
+ * interrupted-stream fold consults it: the fold's assistant/message may land
340
+ * only while its step is still open (a graceful sub-dsh unwind writes its own
341
+ * message and step/end, and the fold must not append at the closed step).
342
+ */
343
+ function mirroredOpenStep(childSession) {
344
+ const events = childSession.snapshotEvents();
345
+ for (let index = events.length - 1; index >= 0; index -= 1) {
346
+ const type = events[index]?.type;
347
+ if (type === "step/start") return events[index]?.data;
348
+ if (type === "step/end" || type === "turn/start" || type === "turn/end") return void 0;
349
+ }
350
+ }
351
+ /**
352
+ * Close the copied open tail of the child session's current turn at settle,
353
+ * before the provider's `turn/end`: the released-log reader rejects a
354
+ * `turn/end` over an open step ('turn/end does not match the open turn with
355
+ * no open step') and a step/end leaving an advertised call unresolved, so an
356
+ * interrupted sub-dsh's open step is ended here — each advertised-but-
357
+ * unresolved tool call first receiving its synthesized interrupted result
358
+ * (the family's placeholder for a started call; the validator's exact
359
+ * TOOL_NOT_STARTED repair for one that never started). Mid-round transports
360
+ * never call this: the sub-dsh may yet write its own end, and a synthesized
361
+ * step/end would collide with it on a later pass. Returns what it
362
+ * synthesized, or undefined when the copied tail was already balanced.
363
+ */
364
+ function closeMirroredOpenStep(childSession) {
365
+ const events = childSession.snapshotEvents();
366
+ let turnStart = -1;
367
+ for (let index = events.length - 1; index >= 0; index -= 1) {
368
+ const type = events[index]?.type;
369
+ if (type === "turn/end") return void 0;
370
+ if (type === "turn/start") {
371
+ turnStart = index;
372
+ break;
373
+ }
374
+ }
375
+ if (turnStart < 0) return void 0;
376
+ let openStep;
377
+ const advertised = /* @__PURE__ */ new Set();
378
+ const started = /* @__PURE__ */ new Map();
379
+ const resolved = /* @__PURE__ */ new Set();
380
+ for (const event of events.slice(turnStart + 1)) if (event.type === "step/start") openStep = event.data;
381
+ else if (event.type === "step/end") openStep = void 0;
382
+ else if (event.type === "assistant/message") {
383
+ const content = event.data.message?.content ?? [];
384
+ for (const block of content) if (block?.type === "tool-call" && typeof block.id === "string") advertised.add(block.id);
385
+ } else if (event.type === "tool/call") {
386
+ const callId = event.data.callId;
387
+ if (typeof callId === "string") started.set(callId, event.seq);
388
+ } else if (event.type === "tool/result") {
389
+ const message = event.data.message;
390
+ const id = message?.toolCallId ?? message?.content?.[0]?.toolCallId;
391
+ if (typeof id === "string") resolved.add(id);
392
+ }
393
+ if (openStep === void 0) return void 0;
394
+ const step = openStep;
395
+ const repaired = [];
396
+ let nextSeq = events.length;
397
+ for (const callId of advertised) {
398
+ if (resolved.has(callId)) continue;
399
+ const callSeq = started.get(callId);
400
+ const message = {
401
+ id: `interrupted-tool-result-${callId}-${nextSeq}`,
402
+ role: "tool",
403
+ toolCallId: callId,
404
+ isError: true,
405
+ source: {
406
+ kind: "tool",
407
+ callId
408
+ },
409
+ content: [{
410
+ type: "text",
411
+ text: callSeq === void 0 ? TOOL_NOT_STARTED_REPAIR_TEXT : DSH_INTERRUPTED_TOOL_RESULT
412
+ }]
413
+ };
414
+ nextSeq += 1;
415
+ childSession.append("tool/result", {
416
+ turn: step.turn,
417
+ step: step.step,
418
+ message,
419
+ error: callSeq === void 0 ? {
420
+ name: "ToolNotStartedError",
421
+ code: "TOOL_NOT_STARTED"
422
+ } : {
423
+ name: "ToolOutcomeUnknownError",
424
+ code: "TOOL_OUTCOME_UNKNOWN"
425
+ }
426
+ }, {
427
+ surfaceOp: "append",
428
+ ...callSeq === void 0 ? {} : { sourceEventSeqs: [callSeq] }
429
+ });
430
+ repaired.push(callId);
431
+ }
432
+ childSession.append("step/end", {
433
+ turn: step.turn,
434
+ step: step.step
435
+ });
436
+ return {
437
+ step,
438
+ repairedCalls: repaired
439
+ };
440
+ }
441
+ /**
442
+ * Mirror ONE live-pushed sub-dsh session event (the serve mode's
443
+ * `session/event` wire notification) into the child session. This is the
444
+ * live driver's transport-side entry into the SAME fold the file mirror
445
+ * owns: the filter (only the caller task's `user/message`, every
446
+ * `assistant/message`, the tool events, and the `step/start`/`step/end`
447
+ * boundaries cross — verbatim; turn boundaries stay the parent's; scaffolding
448
+ * stays behind) and the verbatim append are exactly `mirrorDshSession`'s
449
+ * span-loop rules. The 0.1.5 sub-dsh emits no per-chunk events (the
450
+ * `assistant/chunk` type is retired — an interrupted attempt settles as
451
+ * `assistant/attempt`, which carries no surface content and never crosses),
452
+ * so the `token` granularity no longer streams deltas into the child log;
453
+ * live text rides the run-progress channel only.
454
+ * The caller owns offset/dedupe (the live runtime pushes
455
+ * each event once) and persistence batching.
456
+ * @param childSession - the parent-side dsh subagent session.
457
+ * @param event - the live event from the resident sub-dsh.
458
+ * @param _options - granularity; retained for caller compatibility — the
459
+ * retirement of per-chunk events leaves the mirror granularity-invariant.
460
+ * @returns the mirrored text for delta progress, or undefined when the event
461
+ * was filtered out or carries no progress text (a step boundary crosses but
462
+ * is structure, not content).
463
+ */
464
+ function mirrorDshLiveEvent(childSession, event, _options) {
465
+ if (event.type === "user/message" && event.data.source.kind === "user") return appendMirroredMessageEvent(childSession, event);
466
+ if (event.type === "assistant/message") return appendMirroredMessageEvent(childSession, event);
467
+ if (event.type === "tool/call" || event.type === "tool/result") return appendMirroredToolEvent(childSession, event);
468
+ if (event.type === "step/start" || event.type === "step/end") {
469
+ appendMirroredStepBoundary(childSession, event);
470
+ return;
471
+ }
472
+ }
473
+ /**
474
+ * Mirror the current round's events from the sub-dsh session into the child
475
+ * session, then persist (a live session syncs through the core's cached write
476
+ * handle, a standalone one through a one-shot handle; see
477
+ * {@link persistIfStandalone}). Runs both
478
+ * from the provider's live poll (while the
479
+ * sub-dsh writes its log in batches — a torn final zstd frame is skipped
480
+ * until the next pass) and after the child process exits (the settle pass);
481
+ * the round's already-mirrored prefix in the child session is the offset, so
482
+ * the settle pass is a no-op when polling kept up. An aborted round still
483
+ * preserves its partial work and real usage. All failures degrade to a warn:
484
+ * the delegation result is already settled.
485
+ * @param ctx - host context carrying session persistence.
486
+ * @param childSession - the parent-side dsh subagent session.
487
+ * @param homeDir - the `dsh` harness's scoped home.
488
+ * @param subSessionId - the sub-dsh session id (same uuid as the child session).
489
+ * @returns the newly mirrored events' texts and the round's mirrored total.
490
+ */
491
+ async function mirrorDshSession(ctx, childSession, homeDir, subSessionId) {
492
+ const empty = {
493
+ texts: [],
494
+ total: 0
495
+ };
496
+ try {
497
+ const read = await readSubDshEvents(homeDir, subSessionId);
498
+ if (read === void 0) return empty;
499
+ const { events, log } = read;
500
+ const round = childSession.snapshotEvents().filter((event) => event.type === "turn/start").length;
501
+ if (round === 0) return empty;
502
+ let start = -1;
503
+ let end = events.length;
504
+ for (let index = 0; index < events.length; index += 1) {
505
+ const event = events[index];
506
+ if (event?.type !== "turn/start") continue;
507
+ if (start === -1 && event.data.turn === round) {
508
+ start = index;
509
+ continue;
510
+ }
511
+ if (start !== -1) {
512
+ end = index;
513
+ break;
514
+ }
515
+ }
516
+ if (start === -1) return empty;
517
+ let lastTurnStart = -1;
518
+ for (let index = 0; index < childSession.snapshotEvents().length; index += 1) if (childSession.snapshotEvents()[index]?.type === "turn/start") lastTurnStart = index;
519
+ const mirrored = childSession.snapshotEvents().slice(lastTurnStart + 1).filter((event) => event.type === "user/message" || event.type === "assistant/message" || event.type === "tool/call" || event.type === "tool/result" || event.type === "step/start" || event.type === "step/end").length;
520
+ const roundSpan = events.slice(start, end).filter((event) => event.type === "user/message" && event.data.source.kind === "user" || event.type === "assistant/message" || event.type === "tool/call" || event.type === "tool/result" || event.type === "step/start" || event.type === "step/end");
521
+ const span = roundSpan.slice(mirrored);
522
+ const texts = [];
523
+ let appended = 0;
524
+ for (const event of span) {
525
+ appended += 1;
526
+ if (event.type === "step/start" || event.type === "step/end") {
527
+ appendMirroredStepBoundary(childSession, event);
528
+ continue;
529
+ }
530
+ texts.push(event.type === "tool/call" || event.type === "tool/result" ? appendMirroredToolEvent(childSession, event) : appendMirroredMessageEvent(childSession, event));
531
+ }
532
+ const observation = roundObservation(roundSpan);
533
+ let todosAppended = 0;
534
+ const latestTodos = events.slice(start, end).filter((event) => event.type === "todo/write").at(-1);
535
+ const mirroredTodos = childSession.snapshotEvents().slice(lastTurnStart + 1).filter((event) => event.type === "todo/write");
536
+ if (latestTodos !== void 0 && latestTodos.type === "todo/write") {
537
+ if (JSON.stringify(mirroredTodos.at(-1)?.data) !== JSON.stringify(latestTodos.data)) {
538
+ childSession.append("todo/write", latestTodos.data);
539
+ todosAppended = 1;
540
+ }
541
+ }
542
+ if (appended > 0 || todosAppended > 0) await persistChildSession(ctx, childSession);
543
+ return {
544
+ texts,
545
+ total: mirrored + mirroredTodos.length + appended + todosAppended,
546
+ sessionLogFile: log.filename,
547
+ ...observation
548
+ };
549
+ } catch (error) {
550
+ ctx.logger.warn(`subagent-dsh: session mirror failed: ${error instanceof Error ? error.message : String(error)}`);
551
+ return empty;
552
+ }
553
+ }
554
+ //#endregion
555
+ //#region lib/types/live-driver.js
556
+ /**
557
+ * The dsh provider's live driver: one resident sub-dsh serve process per
558
+ * member (child session), driven over the family-internal wire
559
+ * (`@khorsheed/dsh-local-agent-dsh-headless/wire`). A delegation round is a
560
+ * `turn/start` request to the member's resident runtime instead of a fresh
561
+ * one-shot process, which buys what the process boundary could not:
562
+ * runtime-level graceful interrupt (`turn/interrupt` → in-process
563
+ * `Agent.cancel`; the process survives and the session stays continuable)
564
+ * and push-mode mirroring (session events arrive as `session/event`
565
+ * notifications and are mirrored event-by-event through the same fold the
566
+ * file mirror owns, with the file-based `mirrorDshSession` as the settle
567
+ * reconciliation pass).
568
+ *
569
+ * Lifecycle (the mode's main cost): the runtime spawns lazily on the member's
570
+ * first live round, is reclaimed after an idle timeout or on plugin unload,
571
+ * and is re-spawned — resuming the on-disk session — after a crash. Every
572
+ * process is registered in {@link DshLiveDriver.runtimes} until reclaimed, so
573
+ * no zombies survive a profile restart.
574
+ * @module @khorsheed/dsh-local-agent-dsh/live-driver
575
+ */
576
+ /** Default idle lifetime of an unused resident runtime before reclaim. */
577
+ const DEFAULT_LIVE_IDLE_MS = 18e5;
578
+ /** Default timeout for a wire request acknowledgement. */
579
+ const DEFAULT_LIVE_REQUEST_TIMEOUT_MS = 3e4;
580
+ /** Default timeout for the serve handshake (a cold sub-dsh boot can be slow). */
581
+ const DEFAULT_LIVE_INITIALIZE_TIMEOUT_MS = 6e4;
582
+ /** Bounded wait for an interrupted turn to converge while a run disposes. */
583
+ const DEFAULT_LIVE_DISPOSE_CONVERGE_MS = 5e3;
584
+ /** Bounded wait for a reclaimed runtime's own shutdown before SIGTERM. */
585
+ const RECLAIM_SHUTDOWN_GRACE_MS = 1e3;
586
+ /** How long the channel stays broken before a round retries live. */
587
+ const DEFAULT_LIVE_CHANNEL_RETRY_MS = 3e5;
588
+ /**
589
+ * The serve channel could not come up (spawn failure or handshake
590
+ * timeout/mismatch). The provider catches exactly this and falls back to the
591
+ * exec one-shot; any other error is a real round failure.
592
+ */
593
+ var LiveChannelUnavailableError = class extends Error {
594
+ constructor(message) {
595
+ super(message);
596
+ this.name = "LiveChannelUnavailableError";
597
+ }
598
+ };
599
+ const DEFAULT_TIMEOUTS = {
600
+ initializeMs: DEFAULT_LIVE_INITIALIZE_TIMEOUT_MS,
601
+ requestMs: DEFAULT_LIVE_REQUEST_TIMEOUT_MS,
602
+ convergeMs: DEFAULT_LIVE_DISPOSE_CONVERGE_MS,
603
+ channelRetryMs: DEFAULT_LIVE_CHANNEL_RETRY_MS
604
+ };
605
+ function delay(ms) {
606
+ return new Promise((resolve) => setTimeout(resolve, ms));
607
+ }
608
+ function thrown$1(value) {
609
+ return value instanceof Error ? value : new Error(String(value));
610
+ }
611
+ /**
612
+ * One resident serve process: the wire client, the process registry entry,
613
+ * and the reclaim ladder (wire shutdown → grace → SIGTERM → grace → SIGKILL
614
+ * via the subprocess handle's own terminate).
615
+ */
616
+ var LiveRuntime = class {
617
+ child;
618
+ timeouts;
619
+ warn;
620
+ /** Set once the process exited or was reclaimed; a dead runtime never serves again. */
621
+ dead = false;
622
+ /**
623
+ * The model this process bound at spawn (`--model`), or undefined when it
624
+ * spawned with no model flag at all. A round whose start model differs
625
+ * retires the runtime instead of silently running the old one.
626
+ */
627
+ modelLaunchFingerprint;
628
+ configurationKey;
629
+ boundModel;
630
+ reclaimed = false;
631
+ pending = /* @__PURE__ */ new Map();
632
+ nextId = 0;
633
+ buffer = "";
634
+ decoder = new StringDecoder("utf8");
635
+ stderrDecoder = new StringDecoder("utf8");
636
+ stderrTail = "";
637
+ /**
638
+ * Per-round handlers, installed by the active round (one at a time per
639
+ * member) and CLEARED when it settles — a notification with no active
640
+ * round is dropped, never delivered into a stale closure.
641
+ */
642
+ onEvent;
643
+ onStream;
644
+ onIdle;
645
+ /** Fires once when the process dies or is reclaimed (driver bookkeeping). */
646
+ onDead;
647
+ constructor(child, timeouts, warn) {
648
+ this.child = child;
649
+ this.timeouts = timeouts;
650
+ this.warn = warn;
651
+ child.stdout?.on("data", (chunk) => {
652
+ this.feed(this.decoder.write(chunk));
653
+ });
654
+ child.stderr?.on("data", (chunk) => {
655
+ this.stderrTail = (this.stderrTail + this.stderrDecoder.write(chunk)).slice(-4096);
656
+ });
657
+ child.done.then(() => {
658
+ this.markDead();
659
+ }, () => {
660
+ this.markDead();
661
+ });
662
+ }
663
+ /** Diagnostic tail for round-failure logs. */
664
+ get diagnostics() {
665
+ return this.stderrTail.trim();
666
+ }
667
+ feed(text) {
668
+ this.buffer += text;
669
+ let index = this.buffer.indexOf("\n");
670
+ while (index >= 0) {
671
+ const line = this.buffer.slice(0, index);
672
+ this.buffer = this.buffer.slice(index + 1);
673
+ index = this.buffer.indexOf("\n");
674
+ if (line.trim() === "") continue;
675
+ this.dispatchLine(line);
676
+ }
677
+ }
678
+ /** Handle one wire line; a malformed or hostile line never escapes this frame. */
679
+ dispatchLine(line) {
680
+ let message;
681
+ try {
682
+ message = JSON.parse(line);
683
+ } catch {
684
+ this.warn("subagent-dsh live: ignored a malformed wire line");
685
+ return;
686
+ }
687
+ try {
688
+ if (typeof message.id === "number" && ("result" in message || "error" in message)) {
689
+ const request = this.pending.get(message.id);
690
+ if (request === void 0) return;
691
+ this.pending.delete(message.id);
692
+ clearTimeout(request.timer);
693
+ const wireError = message.error;
694
+ if (wireError !== void 0 && wireError !== null) request.reject(/* @__PURE__ */ new Error(`subagent-dsh live: ${wireError.message ?? "wire error"}`));
695
+ else request.resolve(message.result);
696
+ return;
697
+ }
698
+ const params = message.params ?? {};
699
+ if (typeof params.sessionId !== "string") return;
700
+ if (message.method === "session/event") {
701
+ const event = params.event;
702
+ if (event === null || typeof event !== "object" || typeof event.type !== "string") return;
703
+ this.onEvent?.(params.sessionId, typeof params.turn === "number" ? params.turn : null, event);
704
+ } else if (message.method === "session/assistant-stream") {
705
+ if (typeof params.turn !== "number" || params.frame === null || typeof params.frame !== "object") return;
706
+ this.onStream?.(params.sessionId, params.turn, params.frame);
707
+ } else if (message.method === "session/idle") {
708
+ if (typeof params.turn !== "number") return;
709
+ this.onIdle?.(params.sessionId, params.turn, params.reason ?? null);
710
+ }
711
+ } catch (error) {
712
+ this.warn(`subagent-dsh live: notification handler failed: ${thrown$1(error).message}`);
713
+ }
714
+ }
715
+ /** Send one request and await its acknowledgement. */
716
+ request(method, params, timeoutMs) {
717
+ if (this.dead) return Promise.reject(/* @__PURE__ */ new Error("subagent-dsh live: the runtime process is gone"));
718
+ this.nextId += 1;
719
+ const id = this.nextId;
720
+ return new Promise((resolve, reject) => {
721
+ const timer = setTimeout(() => {
722
+ this.pending.delete(id);
723
+ reject(/* @__PURE__ */ new Error(`subagent-dsh live: ${method} timed out`));
724
+ timer.unref();
725
+ }, timeoutMs ?? this.timeouts.requestMs);
726
+ timer.unref();
727
+ this.pending.set(id, {
728
+ resolve: (result) => resolve(result),
729
+ reject,
730
+ timer
731
+ });
732
+ this.child.stdin?.write(JSON.stringify({
733
+ jsonrpc: "2.0",
734
+ id,
735
+ method,
736
+ params
737
+ }) + "\n");
738
+ });
739
+ }
740
+ markDead() {
741
+ if (this.dead) return;
742
+ this.dead = true;
743
+ for (const request of this.pending.values()) {
744
+ clearTimeout(request.timer);
745
+ request.reject(/* @__PURE__ */ new Error("subagent-dsh live: the runtime process exited"));
746
+ }
747
+ this.pending.clear();
748
+ this.onDead?.();
749
+ }
750
+ /** Graceful teardown: wire shutdown, a beat to exit on its own, then the terminate ladder. */
751
+ async reclaim() {
752
+ if (this.reclaimed) return;
753
+ this.reclaimed = true;
754
+ if (!this.dead) {
755
+ await this.request("shutdown", {}, RECLAIM_SHUTDOWN_GRACE_MS).catch(() => {});
756
+ await Promise.race([this.child.done.catch(() => {}), delay(RECLAIM_SHUTDOWN_GRACE_MS)]);
757
+ }
758
+ if (!this.dead) {
759
+ this.child.terminate();
760
+ await this.child.waitForExit();
761
+ }
762
+ await this.child.done.catch(() => {});
763
+ this.markDead();
764
+ }
765
+ };
766
+ /**
767
+ * The live driver owns every resident runtime of this provider generation.
768
+ * The LiveDriverSwitch builds a generation when the settings resolve live on,
769
+ * retires one with {@link DshLiveDriver.drain} (never interrupts), and
770
+ * disposes with the enabled toggle (disposeAll).
771
+ */
772
+ var DshLiveDriver = class {
773
+ ctx;
774
+ config;
775
+ timeouts;
776
+ runtimes = /* @__PURE__ */ new Map();
777
+ /** In-flight spawns by member: concurrent rounds share one, disposeAll waits them out. */
778
+ ensuring = /* @__PURE__ */ new Map();
779
+ idleTimers = /* @__PURE__ */ new Map();
780
+ /** Per-member round serialization (the resume lock covers resume-vs-resume only). */
781
+ roundChains = /* @__PURE__ */ new Map();
782
+ /** When the channel last failed its spawn/handshake probe (breaker with cooldown). */
783
+ channelBrokenAt;
784
+ disposed = false;
785
+ /**
786
+ * Set by drain() (a settings-driven generation handoff): new rounds are
787
+ * refused so the provider falls back to exec, while in-flight rounds finish
788
+ * on their runtime undisturbed. Unlike `disposed`, the driver still serves
789
+ * what it already accepted.
790
+ */
791
+ draining = false;
792
+ /** Aborts in-flight spawns when the driver is disposed mid-handshake. */
793
+ disposeController = new AbortController();
794
+ constructor(ctx, config, timeouts = DEFAULT_TIMEOUTS) {
795
+ this.ctx = ctx;
796
+ this.config = config;
797
+ this.timeouts = timeouts;
798
+ }
799
+ /**
800
+ * Whether the channel is in its post-failure cooldown. A spawn/handshake
801
+ * failure trips the breaker (rounds fall back to exec), but only for
802
+ * `channelRetryMs` — a transient boot fault must not disable live driving
803
+ * until the next plugin reload.
804
+ */
805
+ get disabled() {
806
+ return this.channelBrokenAt !== void 0 && Date.now() - this.channelBrokenAt < (this.timeouts.channelRetryMs ?? 3e5);
807
+ }
808
+ /** Count of resident runtimes currently registered (zombie accounting in tests). */
809
+ get liveCount() {
810
+ return this.runtimes.size;
811
+ }
812
+ clearIdleTimer(key) {
813
+ const timer = this.idleTimers.get(key);
814
+ if (timer !== void 0) clearTimeout(timer);
815
+ this.idleTimers.delete(key);
816
+ }
817
+ /** Arm the idle reaper; every round settlement re-arms it. */
818
+ armIdleTimer(key) {
819
+ this.clearIdleTimer(key);
820
+ const idleMs = this.config.liveIdleMs ?? 18e5;
821
+ const timer = setTimeout(() => {
822
+ this.reclaim(key);
823
+ }, idleMs);
824
+ timer.unref();
825
+ this.idleTimers.set(key, timer);
826
+ }
827
+ /** Reclaim one runtime: deregister, wire shutdown, terminate ladder, member release. */
828
+ async reclaim(key) {
829
+ const runtime = this.runtimes.get(key);
830
+ this.clearIdleTimer(key);
831
+ if (runtime === void 0) return;
832
+ this.runtimes.delete(key);
833
+ await runtime.reclaim();
834
+ }
835
+ /** Reclaim every runtime (plugin unload); no process survives the profile. */
836
+ async disposeAll() {
837
+ this.disposed = true;
838
+ this.disposeController.abort();
839
+ await Promise.all([...this.ensuring.values()].map((pending) => pending.catch(() => void 0)));
840
+ for (const key of [...this.runtimes.keys()]) await this.reclaim(key);
841
+ }
842
+ /**
843
+ * True while the member's runtime exists or is being spawned. The settings
844
+ * controller gates a new generation on this: a fresh driver must not serve
845
+ * a member whose retiring generation still hosts the (same) sub-dsh session.
846
+ */
847
+ hasRuntime(key) {
848
+ return this.runtimes.has(key) || this.ensuring.has(key);
849
+ }
850
+ /** @deprecated Compatibility no-op: live output is always incremental. */
851
+ setLiveMirrorGranularity(_granularity) {}
852
+ /**
853
+ * Drain for a settings-driven generation handoff: refuse new rounds (the
854
+ * provider's catch falls back to exec), let every in-flight round finish on
855
+ * its runtime, then reclaim. Resolves when no runtime or spawn remains.
856
+ * Unlike disposeAll, in-flight work is never interrupted.
857
+ */
858
+ async drain() {
859
+ this.draining = true;
860
+ const keys = /* @__PURE__ */ new Set([
861
+ ...this.runtimes.keys(),
862
+ ...this.ensuring.keys(),
863
+ ...this.roundChains.keys()
864
+ ]);
865
+ await Promise.all([...keys].map(async (key) => {
866
+ await (this.roundChains.get(key) ?? Promise.resolve()).catch(() => void 0);
867
+ await (this.ensuring.get(key) ?? Promise.resolve()).catch(() => void 0);
868
+ await this.reclaim(key);
869
+ }));
870
+ }
871
+ /**
872
+ * The model the member's live runtime bound at spawn: the identifier, or
873
+ * undefined when the runtime spawned with no `--model`. Null when the
874
+ * member has no live runtime in this generation — the broker's switch check
875
+ * distinguishes "no runtime to retire" from "runtime bound to no model".
876
+ */
877
+ boundModelOf(key) {
878
+ const runtime = this.runtimes.get(key);
879
+ if (runtime === void 0 || runtime.dead) return null;
880
+ return runtime.boundModel;
881
+ }
882
+ /**
883
+ * Retire the member's resident runtime so the NEXT round respawns (the
884
+ * sub-dsh session itself carries over via the on-disk resume). The
885
+ * composer's model switch calls this after storing the new override; the
886
+ * caller guarantees no round is in flight for the member.
887
+ */
888
+ async retireRuntime(key) {
889
+ await this.reclaim(key);
890
+ }
891
+ /**
892
+ * The member's live runtime, spawning it (once per member at a time) when
893
+ * absent or dead. `signal` abandons the spawn on cancellation.
894
+ */
895
+ async ensureRuntime(spec, launch, signal) {
896
+ const key = spec.sessionId;
897
+ const startModel = spec.startModel?.trim();
898
+ const existing = this.runtimes.get(key);
899
+ if (existing !== void 0 && !existing.dead) {
900
+ if (existing.modelLaunchFingerprint !== launch.fingerprint || spec.configuration !== void 0 && existing.configurationKey !== JSON.stringify(spec.configuration) || spec.configuration === void 0 && existing.configurationKey !== void 0 || startModel !== void 0 && startModel !== "" && existing.boundModel !== startModel) await this.reclaim(key);
901
+ else {
902
+ this.clearIdleTimer(key);
903
+ return existing;
904
+ }
905
+ } else if (existing !== void 0) this.runtimes.delete(key);
906
+ const pending = this.ensuring.get(key);
907
+ if (pending !== void 0) return pending;
908
+ const spawn = this.spawnRuntime(spec, launch, signal).finally(() => {
909
+ this.ensuring.delete(key);
910
+ });
911
+ this.ensuring.set(key, spawn);
912
+ return spawn;
913
+ }
914
+ /**
915
+ * Spawn the member's resident runtime and prove the wire with a handshake.
916
+ * Spawn/handshake failure trips the channel breaker (exec fallback) and
917
+ * throws {@link LiveChannelUnavailableError}; a later crash is NOT a broken
918
+ * channel — the next round re-spawns and resumes the on-disk session. A
919
+ * cancellation mid-spawn reclaims the half-started runtime and throws the
920
+ * abort instead, without touching the breaker.
921
+ */
922
+ async spawnRuntime(spec, launch, signal) {
923
+ const key = spec.sessionId;
924
+ if (this.disposed) throw new LiveChannelUnavailableError("the live driver is disposed");
925
+ const profileName = this.config.profileName ?? "headless-local-agent-dsh";
926
+ const member = registerMemberRun(this.ctx, "dsh-cli", spec.sessionId, spec.parentSessionId);
927
+ const startModel = spec.startModel?.trim();
928
+ const model = spec.configuration !== void 0 ? spec.configuration.model : startModel !== void 0 && startModel !== "" ? startModel : this.config.modelFor?.(key)?.trim();
929
+ const spawnSpec = {
930
+ argv: [
931
+ ...dshLaunchArgv(this.config),
932
+ "--profile",
933
+ profileName,
934
+ "--serve",
935
+ ...model === void 0 || model === "" ? [] : ["--model", model],
936
+ ...spec.configuration?.effort === void 0 ? [] : ["--effort", spec.configuration.effort]
937
+ ],
938
+ cwd: spec.cwd,
939
+ stdio: {
940
+ stdin: "pipe",
941
+ stdout: "pipe",
942
+ stderr: "pipe"
943
+ },
944
+ graceMs: DEFAULT_DISPOSE_GRACE_MS,
945
+ env: delegationEnv({
946
+ DSH_HOME: spec.homeDir,
947
+ ...launch.env,
948
+ ...process.env.TSX_TSCONFIG_PATH === void 0 ? {} : { TSX_TSCONFIG_PATH: process.env.TSX_TSCONFIG_PATH },
949
+ ...member === void 0 ? {} : member.env
950
+ })
951
+ };
952
+ let child;
953
+ try {
954
+ child = this.ctx.subprocess.spawn(spawnSpec);
955
+ } catch (error) {
956
+ member?.release();
957
+ this.markChannelBroken();
958
+ throw new LiveChannelUnavailableError(`the serve process failed to spawn: ${thrown$1(error).message}`);
959
+ }
960
+ const runtime = new LiveRuntime(child, this.timeouts, (message) => {
961
+ this.ctx.logger.warn(message);
962
+ });
963
+ runtime.boundModel = model === void 0 || model === "" ? void 0 : model;
964
+ runtime.modelLaunchFingerprint = launch.fingerprint;
965
+ runtime.configurationKey = spec.configuration === void 0 ? void 0 : JSON.stringify(spec.configuration);
966
+ runtime.onDead = () => {
967
+ if (this.runtimes.get(key) === runtime) this.runtimes.delete(key);
968
+ this.clearIdleTimer(key);
969
+ member?.release();
970
+ };
971
+ const aborted = new Promise((_, reject) => {
972
+ const cancel = () => {
973
+ reject(/* @__PURE__ */ new Error("subagent-dsh: run cancelled locally"));
974
+ };
975
+ const unload = () => {
976
+ reject(new LiveChannelUnavailableError("the live driver was disposed during spawn"));
977
+ };
978
+ if (signal.aborted) {
979
+ cancel();
980
+ return;
981
+ }
982
+ if (this.disposeController.signal.aborted) {
983
+ unload();
984
+ return;
985
+ }
986
+ signal.addEventListener("abort", cancel, { once: true });
987
+ this.disposeController.signal.addEventListener("abort", unload, { once: true });
988
+ });
989
+ aborted.catch(() => {});
990
+ try {
991
+ const hello = await Promise.race([runtime.request("initialize", {}, this.timeouts.initializeMs), aborted]);
992
+ if (hello.protocolVersion !== LIVE_WIRE_PROTOCOL_VERSION || hello.serverInfo.name !== LIVE_SERVER_NAME) throw new Error("the serve handshake reported an unexpected identity");
993
+ } catch (error) {
994
+ await runtime.reclaim();
995
+ if (signal.aborted) throw thrown$1(error);
996
+ this.markChannelBroken();
997
+ throw new LiveChannelUnavailableError(`the serve handshake failed: ${thrown$1(error).message}`);
998
+ }
999
+ if (this.disposed) {
1000
+ await runtime.reclaim();
1001
+ throw new LiveChannelUnavailableError("the live driver was disposed during spawn");
1002
+ }
1003
+ this.channelBrokenAt = void 0;
1004
+ this.runtimes.set(key, runtime);
1005
+ return runtime;
1006
+ }
1007
+ markChannelBroken() {
1008
+ this.channelBrokenAt = Date.now();
1009
+ }
1010
+ /**
1011
+ * Drive one delegation round on the member's resident runtime. Rounds of
1012
+ * one member are strictly serialized: the facade's resume lock covers
1013
+ * resume-vs-resume only, and a resume racing an in-flight fresh round would
1014
+ * otherwise overwrite the runtime's single notification sink and strand
1015
+ * the earlier round forever.
1016
+ */
1017
+ /** Prepare the owned headless agent and validate its adapter without a model turn. */
1018
+ async prepare(spec, signal) {
1019
+ signal.throwIfAborted();
1020
+ if (this.draining || this.disabled) throw new LiveChannelUnavailableError("the live driver is unavailable for preparation");
1021
+ try {
1022
+ provisionDshScope(spec.homeDir, {
1023
+ ...this.config,
1024
+ inheritModelConfiguration: true
1025
+ });
1026
+ const launch = await localModelLaunch(this.ctx, this.config, spec.configuration?.model ?? spec.startModel ?? this.config.modelFor?.(spec.sessionId));
1027
+ await (await this.ensureRuntime(spec, launch, signal)).request("session/prepare", {
1028
+ sessionId: spec.sessionId,
1029
+ resume: spec.resume !== void 0
1030
+ });
1031
+ signal.throwIfAborted();
1032
+ this.armIdleTimer(spec.sessionId);
1033
+ } catch (error) {
1034
+ await this.reclaim(spec.sessionId);
1035
+ throw error;
1036
+ }
1037
+ }
1038
+ async startRound(request, spec) {
1039
+ if (this.draining) throw new LiveChannelUnavailableError("the live driver is draining (a settings change retired this generation)");
1040
+ const key = String(spec.childSession.id);
1041
+ const round = (this.roundChains.get(key) ?? Promise.resolve()).catch(() => {}).then(() => this.startRoundLocked(request, spec));
1042
+ this.roundChains.set(key, round.then((handle) => handle.result.catch(() => ({})), () => ({})));
1043
+ return round;
1044
+ }
1045
+ /**
1046
+ * The serialized round body. Mirrors the exec run's settlement contract
1047
+ * exactly (settleRunResult + turn/end bookkeeping) so the facade, the
1048
+ * tool, and the projections cannot tell the difference — except `cancel`
1049
+ * is a runtime interrupt and the process survives. Cancellation is honored
1050
+ * in EVERY window: a slow spawn/handshake or a slow accept races the abort
1051
+ * signal and settles aborted instead of running the turn to completion
1052
+ * unwatched.
1053
+ */
1054
+ async startRoundLocked(request, spec) {
1055
+ const task = dshTextTask(request.prompt);
1056
+ if (request.signal.aborted) throw new Error("subagent-dsh: request was aborted before the run started");
1057
+ if (this.draining) throw new LiveChannelUnavailableError("the live driver is draining (a settings change retired this generation)");
1058
+ if (this.disposed) throw new Error("subagent-dsh: the live driver is disposed");
1059
+ const turn = spec.resume?.turn ?? 1;
1060
+ const childSession = spec.childSession;
1061
+ const localAgent = this.ctx.get("localAgent");
1062
+ const runAbort = new AbortController();
1063
+ let roundSettled = false;
1064
+ let runtime;
1065
+ /** True once turn/start may have reached the sub-dsh (interrupt becomes meaningful). */
1066
+ let acceptSent = false;
1067
+ /** True once the parent's turn/start boundary is in the child session. */
1068
+ let turnOpened = false;
1069
+ let lastText = "";
1070
+ let mirroredMessages = 0;
1071
+ /** Events of this round that arrived before the boundary opened (same-chunk batching). */
1072
+ const bufferedEvents = [];
1073
+ let persistQueue = Promise.resolve();
1074
+ const persist = () => {
1075
+ persistQueue = persistQueue.then(() => persistChildSession(this.ctx, childSession)).catch((error) => {
1076
+ this.ctx.logger.warn(`subagent-dsh: live persistence failed: ${thrown$1(error).message}`);
1077
+ });
1078
+ };
1079
+ const streamPublisher = localAgent?.liveStreams === void 0 ? void 0 : new LiveStreamPublisher(localAgent.liveStreams, childSession, turn, persist, (error) => this.ctx.logger.warn(`live checkpoint failed: ${String(error)}`));
1080
+ const liveFlush = new LiveFlush((error) => this.ctx.logger.warn(`subagent-dsh: live flush failed: ${String(error)}`));
1081
+ let streaming;
1082
+ const publishStream = () => {
1083
+ if (streaming === void 0 || streamPublisher === void 0) return;
1084
+ const blocks = streaming.assembler.interruptedBlocks();
1085
+ const receivedAt = streaming.receivedAt ?? Date.now();
1086
+ for (const [index, block] of blocks.entries()) if ((block.type === "reasoning" || block.type === "text") && block.text !== "") streamPublisher.update(streaming.step, block.type === "reasoning" ? "think" : "text", block.text, {
1087
+ itemId: `${streaming.id}:${index}`,
1088
+ receivedAt
1089
+ });
1090
+ delete streaming.receivedAt;
1091
+ };
1092
+ const acceptStream = (frame) => {
1093
+ if (streamPublisher === void 0) return;
1094
+ if (frame.type === "start") {
1095
+ if (streaming !== void 0) liveFlush.cancel(streaming.step);
1096
+ streaming = {
1097
+ id: String(frame.attemptId),
1098
+ step: frame.step,
1099
+ assembler: new BlockAssembler(),
1100
+ next: 0
1101
+ };
1102
+ } else if (frame.type === "chunk" && streaming?.id === frame.attemptId) {
1103
+ if (frame.index < streaming.next) return;
1104
+ if (frame.index !== streaming.next) throw new Error("non-contiguous DSH assistant stream");
1105
+ streaming.next++;
1106
+ streaming.receivedAt ??= Date.now();
1107
+ streaming.assembler.push(frame.chunk);
1108
+ liveFlush.schedule(streaming.step, publishStream);
1109
+ }
1110
+ };
1111
+ const requestCancel = () => {
1112
+ if (roundSettled || runAbort.signal.aborted) return;
1113
+ runAbort.abort(/* @__PURE__ */ new Error("subagent-dsh: run cancelled locally"));
1114
+ if (acceptSent && runtime !== void 0 && !runtime.dead) runtime.request("turn/interrupt", { sessionId: spec.sessionId }).catch(() => {});
1115
+ };
1116
+ const onAbort = () => {
1117
+ requestCancel();
1118
+ };
1119
+ request.signal.addEventListener("abort", onAbort, { once: true });
1120
+ const abortBranch = new Promise((_, reject) => {
1121
+ runAbort.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("subagent-dsh: run cancelled locally")), { once: true });
1122
+ });
1123
+ const mirrorOne = (event) => {
1124
+ const text = mirrorDshLiveEvent(childSession, event);
1125
+ if (event.type === "assistant/message") {
1126
+ liveFlush.cancel(event.data.step);
1127
+ streamPublisher?.finish(event.data.step);
1128
+ if (streaming?.step === event.data.step) streaming = void 0;
1129
+ }
1130
+ if (event.type === "user/message" || event.type === "assistant/message") {
1131
+ mirroredMessages += 1;
1132
+ persist();
1133
+ }
1134
+ if (text !== void 0) localAgent?.reportRunProgress(childSession.id, {
1135
+ kind: "delta",
1136
+ text
1137
+ });
1138
+ if (event.type === "assistant/message" && text !== void 0 && text !== "") lastText = text;
1139
+ };
1140
+ const openTurn = () => {
1141
+ childSession.append("turn/start", { turn });
1142
+ turnOpened = true;
1143
+ for (const entry of bufferedEvents.splice(0)) if (entry.kind === "event") mirrorOne(entry.event);
1144
+ else acceptStream(entry.frame);
1145
+ };
1146
+ let nativeFailure;
1147
+ let resolveIdle;
1148
+ const idle = new Promise((resolve) => {
1149
+ resolveIdle = resolve;
1150
+ });
1151
+ const installHandlers = (rt) => {
1152
+ rt.onEvent = (sessionId, eventTurn, event) => {
1153
+ if (sessionId !== spec.sessionId || eventTurn !== turn) return;
1154
+ if (!turnOpened) {
1155
+ bufferedEvents.push({
1156
+ kind: "event",
1157
+ event
1158
+ });
1159
+ return;
1160
+ }
1161
+ mirrorOne(event);
1162
+ };
1163
+ rt.onStream = (sessionId, eventTurn, frame) => {
1164
+ if (sessionId !== spec.sessionId || eventTurn !== turn || roundSettled) return;
1165
+ if (!turnOpened) bufferedEvents.push({
1166
+ kind: "stream",
1167
+ frame
1168
+ });
1169
+ else acceptStream(frame);
1170
+ };
1171
+ rt.onIdle = (sessionId, idleTurn, reason) => {
1172
+ if (sessionId !== spec.sessionId || idleTurn !== turn) return;
1173
+ resolveIdle({ reason });
1174
+ };
1175
+ };
1176
+ provisionDshScope(spec.homeDir, {
1177
+ ...this.config,
1178
+ inheritModelConfiguration: true
1179
+ });
1180
+ const launch = await localModelLaunch(this.ctx, this.config, spec.configuration?.model ?? spec.startModel ?? this.config.modelFor?.(spec.sessionId));
1181
+ const accepted = (async () => {
1182
+ const rt = await this.ensureRuntime(spec, launch, request.signal);
1183
+ runtime = rt;
1184
+ if (runAbort.signal.aborted) {
1185
+ await this.reclaim(spec.sessionId);
1186
+ throw new Error("subagent-dsh: run cancelled locally");
1187
+ }
1188
+ installHandlers(rt);
1189
+ acceptSent = true;
1190
+ try {
1191
+ await rt.request("turn/start", {
1192
+ sessionId: spec.sessionId,
1193
+ text: task,
1194
+ resume: spec.resume !== void 0,
1195
+ turn
1196
+ });
1197
+ } catch (error) {
1198
+ if (!runAbort.signal.aborted) await this.reclaim(spec.sessionId);
1199
+ throw thrown$1(error);
1200
+ }
1201
+ openTurn();
1202
+ })();
1203
+ const attempt = accepted.then(async () => {
1204
+ if (runAbort.signal.aborted) throw new Error("subagent-dsh: run cancelled locally");
1205
+ const processFailure = runtime.child.done.then((outcome) => Promise.reject(/* @__PURE__ */ new Error(`subagent-dsh: the live runtime exited mid-round (code ${String(outcome.exitCode)}, signal ${String(outcome.signal)})`)), (error) => Promise.reject(thrown$1(error)));
1206
+ processFailure.catch(() => {});
1207
+ const { reason } = await Promise.race([idle, processFailure]);
1208
+ if (reason?.kind === "aborted") return {
1209
+ output: collectOutput(),
1210
+ stopReason: "aborted"
1211
+ };
1212
+ if (reason?.kind === "error") {
1213
+ nativeFailure = {
1214
+ message: reason.error.message,
1215
+ code: reason.error.code
1216
+ };
1217
+ throw new Error(`subagent-dsh live: the turn failed: ${reason.error.message}`);
1218
+ }
1219
+ if (reason?.kind !== "completed") throw new Error("subagent-dsh live: the turn produced no outcome");
1220
+ const output = collectOutput();
1221
+ if (output.length === 0) throw new Error("subagent-dsh live: the turn completed but produced no answer");
1222
+ return {
1223
+ output,
1224
+ stopReason: "completed"
1225
+ };
1226
+ });
1227
+ const collectOutput = () => lastText === "" ? [] : [{
1228
+ type: "text",
1229
+ text: lastText
1230
+ }];
1231
+ const result = settleRunResult({
1232
+ attempt: () => Promise.race([attempt, abortBranch]),
1233
+ collectOutput,
1234
+ cancelled: () => runAbort.signal.aborted,
1235
+ onError: (error, stopReason) => {
1236
+ const diagnostics = runtime?.diagnostics ?? "";
1237
+ const suffix = diagnostics === "" ? "" : `; ${diagnostics}`;
1238
+ this.ctx.logger.warn(`subagent-dsh: live round failed (${stopReason}): ${error.message}${suffix}`);
1239
+ },
1240
+ signal: request.signal,
1241
+ onAbort
1242
+ }).then(async (settled) => {
1243
+ roundSettled = true;
1244
+ liveFlush.dispose();
1245
+ const openStep = turnOpened ? mirroredOpenStep(childSession) : void 0;
1246
+ if (turnOpened && streaming !== void 0 && streamPublisher !== void 0 && openStep !== void 0 && openStep.step === streaming.step) {
1247
+ const blocks = streaming.assembler.interruptedBlocks();
1248
+ if (blocks.length > 0) {
1249
+ childSession.append("assistant/message", {
1250
+ turn,
1251
+ step: streaming.step,
1252
+ message: createAssistantMessage({
1253
+ content: blocks,
1254
+ source: {
1255
+ provider: "dsh-local",
1256
+ model: "unobserved"
1257
+ }
1258
+ }),
1259
+ stream: [],
1260
+ interrupted: true
1261
+ }, { surfaceOp: "append" });
1262
+ streamPublisher.finish(streaming.step);
1263
+ persist();
1264
+ }
1265
+ }
1266
+ streamPublisher?.dispose();
1267
+ if (runtime !== void 0) {
1268
+ runtime.onStream = void 0;
1269
+ runtime.onEvent = void 0;
1270
+ runtime.onIdle = void 0;
1271
+ }
1272
+ if (turnOpened) {
1273
+ try {
1274
+ if (mirroredMessages > 0) persist();
1275
+ await persistQueue.catch(() => {});
1276
+ const delta = await mirrorDshSession(this.ctx, childSession, spec.homeDir, spec.sessionId);
1277
+ if (localAgent !== void 0) {
1278
+ for (const text of delta.texts) localAgent.reportRunProgress(childSession.id, {
1279
+ kind: "delta",
1280
+ text
1281
+ });
1282
+ localAgent.reportRunProgress(childSession.id, {
1283
+ kind: "mirror",
1284
+ mirroredLines: delta.total
1285
+ });
1286
+ const round = {
1287
+ ...delta.observedModel === void 0 ? {} : { observedModel: delta.observedModel },
1288
+ ...delta.usage === void 0 ? {} : { usage: delta.usage },
1289
+ ...delta.toolCalls === void 0 ? {} : { toolCalls: delta.toolCalls }
1290
+ };
1291
+ localAgent?.recordRoundSettled?.(childSession.id, round);
1292
+ }
1293
+ } catch (error) {
1294
+ this.ctx.logger.warn(`subagent-dsh: live settle mirror failed: ${thrown$1(error).message}`);
1295
+ }
1296
+ closeMirroredOpenStep(childSession);
1297
+ if (settled.stopReason === "completed") childSession.append("turn/end", {
1298
+ turn,
1299
+ reason: { kind: "completed" }
1300
+ });
1301
+ else if (settled.stopReason === "aborted") childSession.append("turn/end", {
1302
+ turn,
1303
+ reason: {
1304
+ kind: "aborted",
1305
+ reason: { kind: "parent" }
1306
+ }
1307
+ });
1308
+ else childSession.append("turn/end", {
1309
+ turn,
1310
+ reason: {
1311
+ kind: "error",
1312
+ error: nativeFailure ?? {
1313
+ message: "the live round did not complete",
1314
+ code: "UNKNOWN"
1315
+ }
1316
+ }
1317
+ });
1318
+ persist();
1319
+ await persistQueue;
1320
+ }
1321
+ this.armIdleTimer(spec.sessionId);
1322
+ return settled;
1323
+ });
1324
+ await Promise.race([accepted, abortBranch.catch(() => void 0)]);
1325
+ return subprocessRunHandle({
1326
+ id: SessionId(randomUUID()),
1327
+ result,
1328
+ signal: request.signal,
1329
+ onAbort,
1330
+ requestCancel,
1331
+ teardown: async () => {
1332
+ if (roundSettled) return;
1333
+ await Promise.race([idle.catch(() => ({ reason: null })), delay(this.timeouts.convergeMs)]);
1334
+ }
1335
+ });
1336
+ }
1337
+ };
1338
+ //#endregion
1339
+ //#region lib/types/dsh-cli-provider.js
1340
+ /**
1341
+ * One-shot and resumable dsh CLI subagent lifecycle: spawn the sub-dsh
1342
+ * headless profile (`dsh --profile <name> --session-id <uuid> "<task>"` or
1343
+ * `--resume <uuid>`) through the subprocess seam with the harness scoped home
1344
+ * as the child's `$DSH_HOME` and the parent's resolved DeepSeek key injected
1345
+ * explicitly (the shared env scrub drops credential-shaped names and every
1346
+ * `DSH_*` fact; the explicit env layer is the sanctioned override). The
1347
+ * caller-supplied session id is the same uuid on every round: the fresh round
1348
+ * creates the sub-dsh session with it, and each resume round continues exactly
1349
+ * that session. No stdout parsing — the sub-dsh session id is known a priori.
1350
+ * @module @khorsheed/dsh-local-agent-dsh/dsh-cli-provider
1351
+ */
1352
+ /**
1353
+ * Spawn-env name carrying the bridge entry path into the sub-dsh process,
1354
+ * where the headless bundle's mcp-client row reads it (`!!js`). The name is a
1355
+ * family-internal contract between this provider and
1356
+ * `@khorsheed/dsh-local-agent-dsh-headless`'s cordis.patch.yml — the member
1357
+ * bridge itself reads only the socket/token env.
1358
+ */
1359
+ const MEMBER_BRIDGE_ENTRY_ENV = "DSH_MEMBER_BRIDGE_ENTRY";
1360
+ /**
1361
+ * The `NODE_OPTIONS` a containerized round carries. dsh's HTTP client is
1362
+ * node's `fetch` (undici), which does NOT read `HTTP(S)_PROXY` by default: in
1363
+ * an evaluation unit whose only egress is a whitelist proxy the CLI dials the
1364
+ * API directly and fails with a transport error while the proxy never even
1365
+ * receives a `CONNECT`. `--use-env-proxy` opens undici's `EnvHttpProxyAgent`,
1366
+ * which does read those variables. Measured on the T16 image: without it the
1367
+ * round fails, with it the same round answers in 1.3 s. dsh is the only one
1368
+ * of the four CLIs that needs an extra knob to run inside a unit — the other
1369
+ * three carry their own proxy support — so the injection is automatic rather
1370
+ * than something every caller must remember, and the harness reports it in
1371
+ * its effective settings so the condition file can see it.
1372
+ */
1373
+ const CONTAINER_NODE_OPTIONS = "--use-env-proxy";
1374
+ /** Default POSIX grace between subprocess termination tiers. */
1375
+ const DEFAULT_DISPOSE_GRACE_MS = 3e3;
1376
+ /** Default interval between live session-mirror polls during a run. */
1377
+ const DEFAULT_LIVE_MIRROR_INTERVAL_MS = 2e3;
1378
+ /** Register one member run with the channel; see {@link MemberRunHandle}. */
1379
+ function registerMemberRun(ctx, providerName, childSessionId, parentSessionId) {
1380
+ const registry = ctx.localAgent;
1381
+ if (typeof registry.registerMemberRun !== "function" || typeof registry.memberBridgeSocketPath !== "function" || typeof registry.memberBridgeCommand !== "function") return void 0;
1382
+ const token = registry.registerMemberRun({
1383
+ childSessionId,
1384
+ parentSessionId,
1385
+ provider: providerName
1386
+ });
1387
+ const bridge = registry.memberBridgeCommand();
1388
+ let released = false;
1389
+ return {
1390
+ env: {
1391
+ [MEMBER_BRIDGE_SOCKET_ENV]: registry.memberBridgeSocketPath(),
1392
+ [MEMBER_BRIDGE_TOKEN_ENV]: token,
1393
+ [MEMBER_BRIDGE_ENTRY_ENV]: bridge.args[0] ?? ""
1394
+ },
1395
+ release: () => {
1396
+ if (released) return;
1397
+ released = true;
1398
+ registry.unregisterMemberRun(token);
1399
+ }
1400
+ };
1401
+ }
1402
+ /**
1403
+ * One-shot dsh CLI subagent provider: every accepted fresh run starts a fresh
1404
+ * sub-dsh headless process in the delegating Session's workspace, under the
1405
+ * harness scoped home; a resume round (the family tool's staged resume intent)
1406
+ * continues the SAME sub-dsh session with `--resume <uuid>` inside the SAME
1407
+ * dsh child session. Mirrors the other family providers' one-shot lifecycle,
1408
+ * including `NO_START_CAPABILITIES` — continuation is the family's own resume
1409
+ * mechanism, not the official Agent-type continuable seam. With the live
1410
+ * driver configured (`live: true`), rounds instead go to the resident sub-dsh
1411
+ * serve process (see live-driver.ts); the exec path below stays the fallback.
1412
+ */
1413
+ var DshCliProvider = class {
1414
+ ctx;
1415
+ config;
1416
+ live;
1417
+ model;
1418
+ overrides;
1419
+ name = "dsh-cli";
1420
+ capabilities = NO_START_CAPABILITIES;
1421
+ inheritsParentContext = false;
1422
+ /**
1423
+ * @param live - the live driver, or a resolver returning the current
1424
+ * generation's driver per member (the settings toggle swaps generations;
1425
+ * a resolver may return undefined to steer one member's round to exec
1426
+ * while a retiring generation still hosts it).
1427
+ */
1428
+ /**
1429
+ * @param model - resolver for the harness's `model` plugin-config key, read
1430
+ * PER ROUND so a settings-card write reaches the next delegation without a
1431
+ * reload. A delegation that names its own model outranks it.
1432
+ * @param overrides - resolver for the member's session-level model override
1433
+ * (the composer picker, owned by the model broker). It outranks BOTH the
1434
+ * delegation's recorded model and the configured one: on the exec path it
1435
+ * re-points the next round's `--model`, and on the live path it is the
1436
+ * member's start model — a resident runtime bound to a different model is
1437
+ * retired so the round respawns onto the override.
1438
+ */
1439
+ constructor(ctx, config, live, model, overrides) {
1440
+ this.ctx = ctx;
1441
+ this.config = config;
1442
+ this.live = live;
1443
+ this.model = model;
1444
+ this.overrides = overrides;
1445
+ }
1446
+ /** Resolve the live driver for one round's member, if live is on for it. */
1447
+ liveDriver(childSessionId) {
1448
+ const live = this.live;
1449
+ if (live === void 0) return void 0;
1450
+ return typeof live === "function" ? live(childSessionId) : live;
1451
+ }
1452
+ /** Per-round member-channel registration for the exec path (see {@link registerMemberRun}). */
1453
+ memberRun(childSessionId, parentSessionId) {
1454
+ return registerMemberRun(this.ctx, this.name, childSessionId, parentSessionId);
1455
+ }
1456
+ async start(request) {
1457
+ const intent = this.ctx.localAgent.takeDelegationIntent(request.parent.session.id, this.name);
1458
+ const scope = intent?.scope;
1459
+ const homeDir = this.ctx.localAgent.homeDir("dsh", scope);
1460
+ const cwd = resolveChildCwd(request.parent.session.header.cwd, intent?.cwd);
1461
+ if (cwd === void 0) throw new Error("subagent-dsh: the parent session has no working directory to run the CLI in");
1462
+ const exec = intent?.exec;
1463
+ if (exec !== void 0) containerScopedHome(exec, "DSH_HOME", "subagent-dsh");
1464
+ if (intent !== void 0 && intent.kind === "resume") {
1465
+ const record = this.ctx.localAgent.getDelegation(intent.childSessionId);
1466
+ assertResumeCwdUnchanged(record, cwd, "subagent-dsh");
1467
+ assertResumeScopeUnchanged(record, scope, "subagent-dsh");
1468
+ if (typeof this.ctx.localAgent.withMemberConfigurationRound !== "function") return this.startDshResume(request, intent, cwd, homeDir, exec, scope, record?.model);
1469
+ return this.ctx.localAgent.withMemberConfigurationRound({
1470
+ childSessionId: intent.childSessionId,
1471
+ provider: this.name,
1472
+ parentSessionId: request.parent.session.id,
1473
+ cwd,
1474
+ ...scope === void 0 ? {} : { scope },
1475
+ ...record?.model === void 0 ? {} : { model: record.model },
1476
+ ...record?.effort === void 0 ? {} : { effort: record.effort },
1477
+ ...record?.configurationLock === void 0 ? {} : { configurationLock: record.configurationLock }
1478
+ }, (configuration) => this.startDshResume(request, intent, cwd, homeDir, exec, scope, record?.model, configuration), request.signal, intent?.onAdmitted);
1479
+ }
1480
+ const childSessionId = SessionId(intent?.preparedMemberId ?? intent?.childSessionId ?? randomUUID());
1481
+ if (typeof this.ctx.localAgent.withMemberConfigurationRound !== "function") {
1482
+ if (intent?.effort !== void 0) throw new Error("DSH effort requires the configuration admission core");
1483
+ return this.startDshFresh(request, cwd, homeDir, exec, scope, intent?.model, childSessionId);
1484
+ }
1485
+ return this.ctx.localAgent.withMemberConfigurationRound({
1486
+ childSessionId,
1487
+ provider: this.name,
1488
+ parentSessionId: request.parent.session.id,
1489
+ cwd,
1490
+ ...scope === void 0 ? {} : { scope },
1491
+ ...intent?.model === void 0 ? {} : { model: intent.model },
1492
+ ...intent?.effort === void 0 ? {} : { effort: intent.effort },
1493
+ ...intent?.configurationLock === void 0 ? {} : { configurationLock: intent.configurationLock }
1494
+ }, (configuration) => this.startDshFresh(request, cwd, homeDir, exec, scope, intent?.model, childSessionId, configuration), request.signal, intent?.onAdmitted);
1495
+ }
1496
+ /** Fresh round: record the child session and delegation, spawn the sub-dsh create. */
1497
+ async startDshFresh(request, cwd, homeDir, exec, scope, requestedModel, runId, configuration) {
1498
+ let childSession;
1499
+ try {
1500
+ const sessions = this.ctx.get("sessions");
1501
+ if (sessions === void 0) throw new Error("the sessions service is not mounted");
1502
+ childSession = sessions.get(runId) ?? sessions.create(runId, { meta: {
1503
+ cwd,
1504
+ parentSession: request.parent.session.id,
1505
+ origin: "subagent",
1506
+ delegationDepth: (request.parent.session.header.delegationDepth ?? 0) + 1
1507
+ } });
1508
+ const harness = this.ctx.localAgent.get("dsh");
1509
+ const label = subagentDelegationLabel(harness?.displayName ?? "dsh", request.label || void 0);
1510
+ childSession.append("subagent/descriptor", {
1511
+ version: SUBAGENT_DESCRIPTOR_VERSION,
1512
+ mode: "one-shot",
1513
+ provider: this.name,
1514
+ label
1515
+ });
1516
+ establishSubagentCatalogChild(request.parent.session, childSession.header, label);
1517
+ } catch (error) {
1518
+ this.ctx.logger.warn(`subagent-dsh: subagent session record failed: ${error instanceof Error ? error.message : String(error)}`);
1519
+ }
1520
+ this.ctx.localAgent.recordDelegation({
1521
+ childSessionId: runId,
1522
+ provider: this.name,
1523
+ parentSessionId: request.parent.session.id,
1524
+ cliSessionId: runId,
1525
+ cwd,
1526
+ ...scope === void 0 ? {} : { scope },
1527
+ ...requestedModel === void 0 ? {} : { model: requestedModel }
1528
+ });
1529
+ const live = exec === void 0 ? this.liveDriver(runId) : void 0;
1530
+ if (live !== void 0 && childSession !== void 0 && !live.disabled) {
1531
+ const startModel = configuration === void 0 ? this.overrides?.(runId) ?? requestedModel : configuration.resolved.model;
1532
+ try {
1533
+ return await live.startRound(request, {
1534
+ cwd,
1535
+ homeDir,
1536
+ childSession,
1537
+ sessionId: runId,
1538
+ parentSessionId: request.parent.session.id,
1539
+ ...startModel === void 0 ? {} : { startModel },
1540
+ ...configuration === void 0 ? {} : { configuration: configuration.resolved }
1541
+ });
1542
+ } catch (error) {
1543
+ if (!(error instanceof LiveChannelUnavailableError) || request.signal.aborted) throw error;
1544
+ this.ctx.logger.warn(`subagent-dsh: live driver unavailable, using the exec one-shot: ${error.message}`);
1545
+ }
1546
+ }
1547
+ const member = exec === void 0 ? this.memberRun(runId, request.parent.session.id) : void 0;
1548
+ try {
1549
+ const run = await startDshCliRun(request, {
1550
+ cwd,
1551
+ homeDir,
1552
+ childSession,
1553
+ sessionId: runId,
1554
+ ...configuration?.resolved ?? resolveRoundModel(this.overrides?.(runId) ?? requestedModel, this.model),
1555
+ resume: void 0,
1556
+ config: this.config,
1557
+ ctx: this.ctx,
1558
+ ...member === void 0 ? {} : { memberEnv: member.env },
1559
+ ...exec === void 0 ? {} : { exec },
1560
+ cliVersion: () => dshCliVersion(this.ctx, this.config, homeDir),
1561
+ ...scope === void 0 ? {} : { scope }
1562
+ });
1563
+ if (member !== void 0) run.result.then(member.release, member.release);
1564
+ return run;
1565
+ } catch (error) {
1566
+ member?.release();
1567
+ throw error;
1568
+ }
1569
+ }
1570
+ /** Resume round: continue the recorded sub-dsh session inside the existing child session. */
1571
+ async startDshResume(request, intent, cwd, homeDir, exec, scope, requestedModel, configuration) {
1572
+ if (!this.ctx.localAgent.acquireResumeLock(intent.childSessionId)) throw new Error(`subagent-dsh: 该子会话有进行中的委派,等其完成后再追问 (child session ${intent.childSessionId})`);
1573
+ try {
1574
+ const childSession = this.ctx.get("sessions")?.get(SessionId(intent.childSessionId));
1575
+ if (childSession === void 0) throw new Error(`subagent-dsh: resume target child session ${intent.childSessionId} is not live — start a fresh delegation instead`);
1576
+ const nextTurn = childSession.snapshotEvents().filter((event) => event.type === "turn/start").length + 1;
1577
+ const live = exec === void 0 ? this.liveDriver(intent.childSessionId) : void 0;
1578
+ if (live !== void 0 && !live.disabled) {
1579
+ const startModel = configuration === void 0 ? this.overrides?.(intent.childSessionId) ?? requestedModel : configuration.resolved.model;
1580
+ try {
1581
+ const liveRun = await live.startRound(request, {
1582
+ cwd,
1583
+ homeDir,
1584
+ childSession,
1585
+ sessionId: intent.cliSessionId,
1586
+ parentSessionId: request.parent.session.id,
1587
+ resume: { turn: nextTurn },
1588
+ ...startModel === void 0 ? {} : { startModel },
1589
+ ...configuration === void 0 ? {} : { configuration: configuration.resolved }
1590
+ });
1591
+ liveRun.result.then(() => {
1592
+ this.ctx.localAgent.releaseResumeLock(intent.childSessionId);
1593
+ }, () => {
1594
+ this.ctx.localAgent.releaseResumeLock(intent.childSessionId);
1595
+ });
1596
+ return liveRun;
1597
+ } catch (error) {
1598
+ if (!(error instanceof LiveChannelUnavailableError) || request.signal.aborted) throw error;
1599
+ this.ctx.logger.warn(`subagent-dsh: live driver unavailable, using the exec one-shot: ${error.message}`);
1600
+ }
1601
+ }
1602
+ const member = exec === void 0 ? this.memberRun(intent.childSessionId, request.parent.session.id) : void 0;
1603
+ try {
1604
+ const run = await startDshCliRun(request, {
1605
+ cwd,
1606
+ homeDir,
1607
+ childSession,
1608
+ sessionId: intent.cliSessionId,
1609
+ ...configuration?.resolved ?? resolveRoundModel(this.overrides?.(intent.childSessionId) ?? requestedModel, this.model),
1610
+ resume: {
1611
+ cliSessionId: intent.cliSessionId,
1612
+ turn: nextTurn
1613
+ },
1614
+ config: this.config,
1615
+ ctx: this.ctx,
1616
+ ...member === void 0 ? {} : { memberEnv: member.env },
1617
+ ...exec === void 0 ? {} : { exec },
1618
+ cliVersion: () => dshCliVersion(this.ctx, this.config, homeDir),
1619
+ ...scope === void 0 ? {} : { scope }
1620
+ });
1621
+ run.result.then(() => {
1622
+ this.ctx.localAgent.releaseResumeLock(intent.childSessionId);
1623
+ member?.release();
1624
+ }, () => {
1625
+ this.ctx.localAgent.releaseResumeLock(intent.childSessionId);
1626
+ member?.release();
1627
+ });
1628
+ return run;
1629
+ } catch (error) {
1630
+ member?.release();
1631
+ throw error;
1632
+ }
1633
+ } catch (error) {
1634
+ this.ctx.localAgent.releaseResumeLock(intent.childSessionId);
1635
+ throw error;
1636
+ }
1637
+ }
1638
+ };
1639
+ /** Validate and join the one-shot task before crossing the process boundary. */
1640
+ function dshTextTask(prompt) {
1641
+ if (prompt.length === 0) throw new Error("subagent-dsh: the one-shot task must contain only text blocks");
1642
+ const texts = [];
1643
+ for (const block of prompt) {
1644
+ if (block.type !== "text") throw new Error("subagent-dsh: the one-shot task must contain only text blocks");
1645
+ texts.push(block.text);
1646
+ }
1647
+ if (texts.every((text) => text.trim().length === 0)) throw new Error("subagent-dsh: the one-shot task must not be empty");
1648
+ return texts.join("\n");
1649
+ }
1650
+ function thrown(value) {
1651
+ /* v8 ignore next -- typed subprocess failures reject with Error. */
1652
+ return value instanceof Error ? value : new Error(String(value));
1653
+ }
1654
+ /**
1655
+ * The dsh launch argv the provider replicates: the parent instance's own
1656
+ * node + tsx import + entry point, so the sub-dsh runs the same dsh build as
1657
+ * its parent. A configured `cliLaunch` replaces this entirely.
1658
+ * @param config - plugin config carrying the optional override.
1659
+ * @returns the launch prefix before the profile flag.
1660
+ */
1661
+ function dshLaunchArgv(config) {
1662
+ if (config.cliLaunch !== void 0 && config.cliLaunch.length > 0) return config.cliLaunch;
1663
+ return [
1664
+ process.execPath,
1665
+ ...process.execArgv,
1666
+ process.argv[1] ?? "dsh"
1667
+ ];
1668
+ }
1669
+ /**
1670
+ * Mark the harness credential verified after a completed round, degrading
1671
+ * silently on a core that predates the mark (the family's companion-pair
1672
+ * rule: a provider paired with an older core loses the grade, never the run).
1673
+ * @param ctx - host context carrying the family registry.
1674
+ */
1675
+ function markCredentialVerified(ctx, scope) {
1676
+ const registry = ctx.get("localAgent");
1677
+ if (registry === void 0 || typeof registry.reportAuthSuccess !== "function") return;
1678
+ registry.reportAuthSuccess("dsh", scope);
1679
+ }
1680
+ /**
1681
+ * The dsh build the sub-dsh runs, probed by asking the very launch argv the
1682
+ * delegation would spawn for its `--version` (the sub-dsh replicates the
1683
+ * parent instance's own node + entry point, so the parent's build IS the
1684
+ * child's). Cached by the family probe against that entry script's identity,
1685
+ * so a harness upgrade re-probes and an unchanged one never spawns twice.
1686
+ * @param ctx - host context carrying the subprocess seam.
1687
+ * @param config - plugin config carrying the optional launch override.
1688
+ * @param homeDir - the harness's scoped home.
1689
+ * @returns the version, or undefined when the launch cannot be asked.
1690
+ */
1691
+ function dshCliVersion(ctx, config, homeDir) {
1692
+ const subprocess = ctx.get("subprocess");
1693
+ if (subprocess === void 0) return Promise.resolve(void 0);
1694
+ return probeCliVersion({
1695
+ argv: [...dshLaunchArgv(config), "--version"],
1696
+ cwd: homeDir,
1697
+ spawn: (spec) => subprocess.spawn(spec),
1698
+ env: delegationEnv({ DSH_HOME: homeDir })
1699
+ });
1700
+ }
1701
+ /**
1702
+ * Start the real sub-dsh headless child and publish its run. The final answer
1703
+ * is printed to stdout; stderr is inherited for diagnostics. The run id is
1704
+ * the child session id for a session-backed run, so the tool's
1705
+ * `resume="<id>"` self-description names the same handle the registry
1706
+ * records.
1707
+ * @param request - resolved shared subagent request.
1708
+ * @param spec - workspace, scoped home, child session, and resume facts.
1709
+ * @returns the published run after the child starts.
1710
+ */
1711
+ async function startDshCliRun(request, spec) {
1712
+ const { config, ctx } = spec;
1713
+ const task = dshTextTask(request.prompt);
1714
+ if (request.signal.aborted) throw new Error("subagent-dsh: request was aborted before the CLI started");
1715
+ const profileName = spec.exec === void 0 ? config.profileName ?? "headless-local-agent-dsh" : unitSubProfileName(config);
1716
+ if (spec.exec === void 0) provisionDshScope(spec.homeDir, {
1717
+ ...config,
1718
+ inheritModelConfiguration: true
1719
+ });
1720
+ const modelEnv = spec.exec === void 0 ? (await localModelLaunch(ctx, config, spec.model)).env : { DEEPSEEK_API_KEY: await resolveApiKey(ctx, config) };
1721
+ const turn = spec.resume?.turn ?? 1;
1722
+ spec.childSession?.append("turn/start", { turn });
1723
+ const launch = dshLaunchArgv(config);
1724
+ const modelArgv = [...spec.model === void 0 ? [] : ["--model", spec.model], ...spec.effort === void 0 ? [] : ["--effort", spec.effort]];
1725
+ const argv = spec.resume === void 0 ? [
1726
+ ...launch,
1727
+ "--profile",
1728
+ profileName,
1729
+ "--session-id",
1730
+ spec.sessionId,
1731
+ ...modelArgv,
1732
+ task
1733
+ ] : [
1734
+ ...launch,
1735
+ "--profile",
1736
+ profileName,
1737
+ "--resume",
1738
+ spec.sessionId,
1739
+ ...modelArgv,
1740
+ task
1741
+ ];
1742
+ const env = delegationEnv({
1743
+ DSH_HOME: spec.homeDir,
1744
+ ...modelEnv,
1745
+ ...spec.exec !== void 0 || process.env.TSX_TSCONFIG_PATH === void 0 ? {} : { TSX_TSCONFIG_PATH: process.env.TSX_TSCONFIG_PATH },
1746
+ ...spec.exec === void 0 || spec.exec.env?.["NODE_OPTIONS"] !== void 0 ? {} : { NODE_OPTIONS: CONTAINER_NODE_OPTIONS },
1747
+ ...spec.memberEnv
1748
+ });
1749
+ const spawnSpec = {
1750
+ ...spec.exec === void 0 ? {
1751
+ argv,
1752
+ env
1753
+ } : containerExecSpawn(spec.exec, {
1754
+ argv,
1755
+ env
1756
+ }, "subagent-dsh"),
1757
+ cwd: spec.cwd,
1758
+ stdio: {
1759
+ stdin: "ignore",
1760
+ stdout: "pipe",
1761
+ stderr: "pipe"
1762
+ },
1763
+ graceMs: DEFAULT_DISPOSE_GRACE_MS
1764
+ };
1765
+ const child = ctx.subprocess.spawn(spawnSpec);
1766
+ let output = "";
1767
+ child.stdout?.on("data", (chunk) => {
1768
+ output += chunk.toString();
1769
+ });
1770
+ let stderr = "";
1771
+ child.stderr?.on("data", (chunk) => {
1772
+ stderr += chunk.toString();
1773
+ });
1774
+ let mirrorQueue = Promise.resolve();
1775
+ const enqueueMirror = (task) => {
1776
+ mirrorQueue = mirrorQueue.then(task);
1777
+ };
1778
+ const mirrorAndReport = async (childSession, alwaysReport) => {
1779
+ const delta = await mirrorDshSession(ctx, childSession, spec.homeDir, spec.sessionId);
1780
+ const localAgent = ctx.get("localAgent");
1781
+ if (localAgent === void 0) return;
1782
+ if (delta.texts.length === 0 && !alwaysReport) return;
1783
+ for (const text of delta.texts) localAgent.reportRunProgress(childSession.id, {
1784
+ kind: "delta",
1785
+ text
1786
+ });
1787
+ localAgent.reportRunProgress(childSession.id, {
1788
+ kind: "mirror",
1789
+ mirroredLines: delta.total
1790
+ });
1791
+ if (alwaysReport) {
1792
+ const cliVersion = await spec.cliVersion?.();
1793
+ localAgent.recordRoundSettled(childSession.id, {
1794
+ ...delta.observedModel === void 0 ? {} : { observedModel: delta.observedModel },
1795
+ ...cliVersion === void 0 ? {} : { cliVersion },
1796
+ ...delta.usage === void 0 ? {} : { usage: delta.usage },
1797
+ ...delta.toolCalls === void 0 ? {} : { toolCalls: delta.toolCalls }
1798
+ });
1799
+ }
1800
+ };
1801
+ if (spec.childSession !== void 0) {
1802
+ const childSession = spec.childSession;
1803
+ const liveMirror = setInterval(() => {
1804
+ enqueueMirror(() => mirrorAndReport(childSession, false));
1805
+ }, spec.liveMirrorIntervalMs ?? 2e3);
1806
+ liveMirror.unref();
1807
+ child.done.then(() => {
1808
+ clearInterval(liveMirror);
1809
+ }, () => {
1810
+ clearInterval(liveMirror);
1811
+ });
1812
+ }
1813
+ const disposeProcess = async () => {
1814
+ child.terminate();
1815
+ await child.waitForExit().catch(() => false);
1816
+ await child.done.catch(() => {});
1817
+ };
1818
+ const runAbort = new AbortController();
1819
+ const requestCancel = () => {
1820
+ if (runAbort.signal.aborted) return;
1821
+ runAbort.abort(/* @__PURE__ */ new Error("subagent-dsh: run cancelled locally"));
1822
+ };
1823
+ const onAbort = () => {
1824
+ requestCancel();
1825
+ };
1826
+ request.signal.addEventListener("abort", onAbort, { once: true });
1827
+ const abortBranch = new Promise((_, reject) => {
1828
+ runAbort.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("subagent-dsh: run cancelled locally")), { once: true });
1829
+ });
1830
+ const processFailure = child.done.then((outcome) => Promise.reject(/* @__PURE__ */ new Error(`subagent-dsh: CLI exited before the run settled (code ${String(outcome.exitCode)}, signal ${String(outcome.signal)})`)), (error) => Promise.reject(thrown(error)));
1831
+ processFailure.catch(() => {});
1832
+ const collectOutput = () => {
1833
+ const text = output.trim();
1834
+ return text === "" ? [] : [{
1835
+ type: "text",
1836
+ text
1837
+ }];
1838
+ };
1839
+ const result = settleRunResult({
1840
+ attempt: () => Promise.race([
1841
+ child.done.then((outcome) => {
1842
+ if (outcome.exitCode !== 0) throw new Error(`subagent-dsh: dsh exited with code ${String(outcome.exitCode)}`);
1843
+ const answer = collectOutput();
1844
+ if (answer.length === 0) throw new Error("subagent-dsh: dsh exited 0 but produced no answer");
1845
+ return {
1846
+ output: answer,
1847
+ stopReason: "completed"
1848
+ };
1849
+ }),
1850
+ processFailure,
1851
+ abortBranch
1852
+ ]),
1853
+ collectOutput,
1854
+ cancelled: () => runAbort.signal.aborted,
1855
+ onError: (error, stopReason) => {
1856
+ const suffix = stderr.trim() === "" ? "" : `; ${stderr.trim()}`;
1857
+ ctx.logger.warn(`subagent-dsh: child run failed (${stopReason}): ${error.message}${suffix}`);
1858
+ },
1859
+ signal: request.signal,
1860
+ onAbort
1861
+ }).then((settled) => {
1862
+ if (settled.stopReason === "completed") markCredentialVerified(ctx, spec.scope);
1863
+ return settled;
1864
+ });
1865
+ result.then(() => child.done).then(() => {
1866
+ if (spec.childSession !== void 0) {
1867
+ const childSession = spec.childSession;
1868
+ return enqueueMirror(async () => {
1869
+ const settled = await result;
1870
+ try {
1871
+ await mirrorAndReport(childSession, true);
1872
+ } catch (error) {
1873
+ ctx.logger.warn(`subagent-dsh: settle mirror failed: ${error instanceof Error ? error.message : String(error)}`);
1874
+ }
1875
+ closeMirroredOpenStep(childSession);
1876
+ if (settled.stopReason === "completed") childSession.append("turn/end", {
1877
+ turn,
1878
+ reason: { kind: "completed" }
1879
+ });
1880
+ else if (settled.stopReason === "aborted") childSession.append("turn/end", {
1881
+ turn,
1882
+ reason: {
1883
+ kind: "aborted",
1884
+ reason: { kind: "parent" }
1885
+ }
1886
+ });
1887
+ else childSession.append("turn/end", {
1888
+ turn,
1889
+ reason: {
1890
+ kind: "error",
1891
+ error: {
1892
+ message: "dsh exited before the run completed",
1893
+ code: "UNKNOWN"
1894
+ }
1895
+ }
1896
+ });
1897
+ await persistChildSession(ctx, childSession).catch((error) => {
1898
+ ctx.logger.warn(`subagent-dsh: turn/end persistence failed: ${error instanceof Error ? error.message : String(error)}`);
1899
+ });
1900
+ });
1901
+ }
1902
+ }, () => {});
1903
+ return Promise.resolve(subprocessRunHandle({
1904
+ id: SessionId(randomUUID()),
1905
+ result,
1906
+ signal: request.signal,
1907
+ onAbort,
1908
+ requestCancel,
1909
+ teardown: disposeProcess
1910
+ }));
1911
+ }
1912
+ /** Resolve the sub-dsh credential or fail loud before any process spawns. */
1913
+ async function resolveApiKey(ctx, config) {
1914
+ const ref = config.apiKeyRef ?? "DEEPSEEK_API_KEY";
1915
+ const resolved = await ctx.credentials.resolve(credentialRef(ref));
1916
+ if (resolved === void 0) throw new Error(`subagent-dsh: ${ref} is not configured; set it in the credential settings to delegate to dsh`);
1917
+ return resolved.value;
1918
+ }
1919
+ //#endregion
1920
+ export { MEMBER_BRIDGE_ENTRY_ENV as a, dshTextTask as c, startDshCliRun as d, DEFAULT_LIVE_IDLE_MS as f, localModelLaunch as h, DshCliProvider as i, registerMemberRun as l, canInheritModelProvider as m, DEFAULT_DISPOSE_GRACE_MS as n, dshCliVersion as o, DshLiveDriver as p, DEFAULT_LIVE_MIRROR_INTERVAL_MS as r, dshLaunchArgv as s, CONTAINER_NODE_OPTIONS as t, resolveApiKey as u };