@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.
package/lib/client.js CHANGED
@@ -100,7 +100,7 @@ window.__ModuleLoader__.load({
100
100
  const NS = "local-agent-dsh";
101
101
  /** Simplified Chinese dictionary (the key-set source of truth). */
102
102
  const zh = {
103
- "card.title": "Local Agent · dsh",
103
+ "card.title": "本机 DSH 委派",
104
104
  "card.description": "dsh 自委派:开关、认证状态与常驻模式(live)",
105
105
  "card.expand": "展开",
106
106
  "card.collapse": "收起",
@@ -132,7 +132,7 @@ window.__ModuleLoader__.load({
132
132
  };
133
133
  /** English dictionary, checked complete against the zh key set. */
134
134
  const en = {
135
- "card.title": "Local Agent · dsh",
135
+ "card.title": "Local DSH Delegation",
136
136
  "card.description": "dsh self-delegation: toggle, credential status, and resident (live) mode",
137
137
  "card.expand": "Expand",
138
138
  "card.collapse": "Collapse",
@@ -219,6 +219,14 @@ window.__ModuleLoader__.load({
219
219
  };
220
220
  }
221
221
  /**
222
+ * Subscribe one harness's published status (no probe of its own): a sibling
223
+ * surface that WRITES the credential (the endpoint form's save) publishes
224
+ * through the same bus, and every block showing the status flips at once.
225
+ */
226
+ function usePublishedAuthStatus(harnessId) {
227
+ return (0, react.useSyncExternalStore)(subscribe, () => statuses.get(harnessId));
228
+ }
229
+ /**
222
230
  * Read one harness's latest known auth status. Every mount probes once and
223
231
  * publishes — a published value only snapshots the past (a credential expiring
224
232
  * mid-session must flip the dot at the next view, not stay green forever);
@@ -255,30 +263,30 @@ window.__ModuleLoader__.load({
255
263
  document.head.appendChild(tag);
256
264
  }
257
265
  var ProviderAuthBlock_module_css_default = {
266
+ "rowCard": "On5p7W_rowCard",
267
+ "rowName": "On5p7W_rowName",
258
268
  "rowHead": "On5p7W_rowHead",
259
- "rows": "On5p7W_rows",
260
- "rowTag": "On5p7W_rowTag",
261
- "status": "On5p7W_status",
262
- "intro": "On5p7W_intro",
263
269
  "section": "On5p7W_section",
264
- "rowCard": "On5p7W_rowCard",
270
+ "dotOff": "On5p7W_dotOff",
271
+ "logoutButton": "On5p7W_logoutButton",
272
+ "rosterError": "On5p7W_rosterError",
273
+ "dotOn": "On5p7W_dotOn",
274
+ "promptText": "On5p7W_promptText",
265
275
  "title": "On5p7W_title",
276
+ "rowTag": "On5p7W_rowTag",
277
+ "intro": "On5p7W_intro",
278
+ "rows": "On5p7W_rows",
266
279
  "dotPending": "On5p7W_dotPending",
267
- "rosterError": "On5p7W_rosterError",
268
- "retryButton": "On5p7W_retryButton",
269
- "loginPrompt": "On5p7W_loginPrompt",
270
- "codeInput": "On5p7W_codeInput",
271
- "logoutButton": "On5p7W_logoutButton",
272
- "loginButton": "On5p7W_loginButton",
273
280
  "rowActions": "On5p7W_rowActions",
274
- "promptText": "On5p7W_promptText",
281
+ "loginButton": "On5p7W_loginButton",
275
282
  "rowPending": "On5p7W_rowPending",
283
+ "retryButton": "On5p7W_retryButton",
276
284
  "openPage": "On5p7W_openPage",
277
- "dotOff": "On5p7W_dotOff",
278
- "dotOn": "On5p7W_dotOn",
285
+ "status": "On5p7W_status",
279
286
  "credentialDot": "On5p7W_credentialDot",
280
- "rowName": "On5p7W_rowName",
281
- "rowIdentity": "On5p7W_rowIdentity"
287
+ "loginPrompt": "On5p7W_loginPrompt",
288
+ "rowIdentity": "On5p7W_rowIdentity",
289
+ "codeInput": "On5p7W_codeInput"
282
290
  };
283
291
  //#endregion
284
292
  //#region ../local-agent/src/client/ProviderAuthBlock.tsx
@@ -312,12 +320,13 @@ window.__ModuleLoader__.load({
312
320
  * for the retainedBy.mainView count on each summary (the label is ui-session's
313
321
  * declaration merge, read here as a plain duck-typed count so this package
314
322
  * needs no ui-session edge), with the legacy field as the older-host fallback.
323
+ * Exported for sibling cards that run slash commands from their own rows.
315
324
  */
316
325
  const mainSessionId = (list) => Object.values(list.byId ?? {}).find((session) => (session.retainedBy?.mainView ?? 0) > 0)?.id ?? list.current;
317
326
  /**
318
327
  * One harness's auth interactions, shared between the per-provider settings
319
- * cards (the alpha.2 `plugins.bundle.config` pages and the 0.1.5
320
- * `settings.plugin.item` tab): auth status through
328
+ * cards (each a `plugins.item` entry on the Plugins page, registered while
329
+ * the Host serves the provider's namespace): auth status through
321
330
  * the read-only Remote channel and a web-login action that runs
322
331
  * `/<harness> login`, surfaces the device-code prompt, and offers a link to
323
332
  * the authorization page. The login command returns at prompt time while the
@@ -350,7 +359,8 @@ window.__ModuleLoader__.load({
350
359
  const capabilities = probe === void 0 ? {} : {
351
360
  loginable: probe.loginable ?? true,
352
361
  logoutable: probe.logoutable ?? true,
353
- ...probe.loginAwaitingCode === true ? { loginAwaitingCode: true } : {}
362
+ ...probe.loginAwaitingCode === true ? { loginAwaitingCode: true } : {},
363
+ ...probe.effectiveSettings?.cliVersion === void 0 ? {} : { cliVersion: probe.effectiveSettings.cliVersion }
354
364
  };
355
365
  setView((prev) => statusKind === "authenticated" ? {
356
366
  status: statusKind,
@@ -426,7 +436,7 @@ window.__ModuleLoader__.load({
426
436
  setView({ status: "unavailable" });
427
437
  });
428
438
  };
429
- const statusKind = view.status;
439
+ const statusKind = usePublishedAuthStatus(harness.id) ?? view.status;
430
440
  const authenticated = statusKind === "authenticated";
431
441
  const loginPending = pendingSince !== void 0;
432
442
  const loginable = view.loginable ?? true;
@@ -450,6 +460,10 @@ window.__ModuleLoader__.load({
450
460
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
451
461
  className: ProviderAuthBlock_module_css_default.rowTag,
452
462
  children: harness.id
463
+ }),
464
+ view.cliVersion !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
465
+ className: ProviderAuthBlock_module_css_default.rowTag,
466
+ children: view.cliVersion
453
467
  })
454
468
  ]
455
469
  }),
@@ -551,9 +565,9 @@ window.__ModuleLoader__.load({
551
565
  document.head.appendChild(tag);
552
566
  }
553
567
  var AuthStatusDot_module_css_default = {
554
- "dotOn": "OFjNUG_dotOn",
555
568
  "credentialDot": "OFjNUG_credentialDot",
556
569
  "dotOff": "OFjNUG_dotOff",
570
+ "dotOn": "OFjNUG_dotOn",
557
571
  "dotPending": "OFjNUG_dotPending"
558
572
  };
559
573
  //#endregion
@@ -580,40 +594,40 @@ window.__ModuleLoader__.load({
580
594
  document.head.appendChild(tag);
581
595
  }
582
596
  var SettingsCard_module_css_default = {
583
- "modelMenuButton": "_3LBPpW_modelMenuButton",
584
- "modelMenuUp": "_3LBPpW_modelMenuUp",
585
- "modelField": "_3LBPpW_modelField",
586
- "modelItemLabel": "_3LBPpW_modelItemLabel",
587
- "modelSave": "_3LBPpW_modelSave",
597
+ "errorText": "_3LBPpW_errorText",
598
+ "modelMenu": "_3LBPpW_modelMenu",
599
+ "rowLabel": "_3LBPpW_rowLabel",
600
+ "description": "_3LBPpW_description",
601
+ "body": "_3LBPpW_body",
588
602
  "chevron": "_3LBPpW_chevron",
603
+ "modelMenuButton": "_3LBPpW_modelMenuButton",
604
+ "modelItemCheck": "_3LBPpW_modelItemCheck",
605
+ "headText": "_3LBPpW_headText",
589
606
  "modelItemCurrent": "_3LBPpW_modelItemCurrent",
607
+ "header": "_3LBPpW_header",
608
+ "chevronOpen": "_3LBPpW_chevronOpen",
609
+ "knob": "_3LBPpW_knob",
610
+ "modelMenuChevronOpen": "_3LBPpW_modelMenuChevronOpen",
611
+ "modelItemDefault": "_3LBPpW_modelItemDefault",
612
+ "modelItemLabel": "_3LBPpW_modelItemLabel",
590
613
  "switch": "_3LBPpW_switch",
591
- "name": "_3LBPpW_name",
592
- "modelMenuChevron": "_3LBPpW_modelMenuChevron",
593
- "row": "_3LBPpW_row",
614
+ "blockTitle": "_3LBPpW_blockTitle",
615
+ "modelItem": "_3LBPpW_modelItem",
616
+ "modelInput": "_3LBPpW_modelInput",
617
+ "card": "_3LBPpW_card",
594
618
  "cardOpen": "_3LBPpW_cardOpen",
595
- "block": "_3LBPpW_block",
619
+ "row": "_3LBPpW_row",
620
+ "modelSave": "_3LBPpW_modelSave",
621
+ "modelMenuUp": "_3LBPpW_modelMenuUp",
596
622
  "saved": "_3LBPpW_saved",
597
- "card": "_3LBPpW_card",
598
- "rowLabel": "_3LBPpW_rowLabel",
599
- "headText": "_3LBPpW_headText",
600
- "modelInput": "_3LBPpW_modelInput",
601
- "modelItemDefault": "_3LBPpW_modelItemDefault",
602
- "body": "_3LBPpW_body",
603
- "chevronOpen": "_3LBPpW_chevronOpen",
604
- "blockTitle": "_3LBPpW_blockTitle",
605
- "errorText": "_3LBPpW_errorText",
623
+ "hint": "_3LBPpW_hint",
624
+ "modelMenuChevron": "_3LBPpW_modelMenuChevron",
625
+ "switchOn": "_3LBPpW_switchOn",
626
+ "block": "_3LBPpW_block",
606
627
  "nameRow": "_3LBPpW_nameRow",
607
- "modelMenuChevronOpen": "_3LBPpW_modelMenuChevronOpen",
608
- "modelMenu": "_3LBPpW_modelMenu",
609
- "modelItem": "_3LBPpW_modelItem",
610
- "modelItemCheck": "_3LBPpW_modelItemCheck",
611
- "header": "_3LBPpW_header",
628
+ "modelField": "_3LBPpW_modelField",
612
629
  "info": "_3LBPpW_info",
613
- "knob": "_3LBPpW_knob",
614
- "description": "_3LBPpW_description",
615
- "switchOn": "_3LBPpW_switchOn",
616
- "hint": "_3LBPpW_hint"
630
+ "name": "_3LBPpW_name"
617
631
  };
618
632
  //#endregion
619
633
  //#region src/client/SettingsCard.tsx
@@ -959,8 +973,9 @@ window.__ModuleLoader__.load({
959
973
  });
960
974
  }
961
975
  /**
962
- * The dsh harness settings card in the 0.1.5 plugin configuration tab: the
963
- * same collapsible chrome the official plugin cards use, re-implemented
976
+ * The dsh harness settings card of the retired 0.1.5 plugin configuration
977
+ * tab (no longer registered; the live surface is the plugins.item entry
978
+ * below): the same collapsible chrome the official plugin cards use, re-implemented
964
979
  * locally (the bundle-purity gate forbids value-importing the official chrome
965
980
  * — the context-guard pattern), with the at-a-glance credential dot in the
966
981
  * header. Explanation copy lives in hover/focus bubbles behind ⓘ anchors —
@@ -1012,10 +1027,12 @@ window.__ModuleLoader__.load({
1012
1027
  });
1013
1028
  }
1014
1029
  /**
1015
- * The bundle's configuration on the alpha.2 Plugins page
1016
- * (`plugins.bundle.config`, keyed by package name): the page draws the title,
1017
- * the icon, and the crumb itself, so the entry renders the one-liner for the
1018
- * `summary` view and the bare form — with its own save control — for `page`.
1030
+ * The provider's card on the Plugins page (the `plugins.item` entry
1031
+ * client/index.ts registers while the Host serves the namespace AND the core
1032
+ * predates the family card): the page
1033
+ * draws the title, the icon, and the crumb itself, so the entry renders the
1034
+ * one-liner for the `summary` view and the bare form — with its own save
1035
+ * control — for `page`.
1019
1036
  * @param props - the owner view, the injected scope/auth faces, and copy.
1020
1037
  * @returns the entry.
1021
1038
  */
@@ -1034,21 +1051,59 @@ window.__ModuleLoader__.load({
1034
1051
  t
1035
1052
  });
1036
1053
  }
1054
+ /**
1055
+ * The harness's section inside the core's「本机 Agent 委派」family card: the
1056
+ * same useCardState + CardBody the plugins.item page view renders — the
1057
+ * collapsible chrome belongs to the family card now. The delegation enable
1058
+ * switch keeps its semantics: it writes the scope, the host mounts/unmounts
1059
+ * the harness on the served value, and the section itself stays put.
1060
+ * @param props - the injected scope/auth faces, the forwarded session seat, and copy.
1061
+ * @returns the section body.
1062
+ */
1063
+ function DshCardSection({ active, useSettings, scope, auth, authT, renderModelPicker, harnessModel, useSessions, t }) {
1064
+ const state = useCardState(active, useSettings, scope, harnessModel, t);
1065
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, {
1066
+ state,
1067
+ auth,
1068
+ authT,
1069
+ renderModelPicker,
1070
+ useSessions,
1071
+ t
1072
+ });
1073
+ }
1037
1074
  //#endregion
1038
1075
  //#region src/client/index.ts
1039
1076
  /**
1040
- * This bundle's package name — the key the Plugins page dispatches
1041
- * `plugins.bundle.config` entries on (identity triangle: cordis.patch.yml,
1042
- * tsdown.config.ts, invariant.ts).
1077
+ * Local-agent-dsh plugin, browser half: the dsh settings card — a section of
1078
+ * the family core's「本机 Agent 委派」family card on the Plugins page
1079
+ * (registered through the core's `localAgentUi.registerHarnessCard` while the
1080
+ * Host serves the `local-agent-dsh` namespace), or this package's own
1081
+ * `plugins.item` entry when the core predates the family card (the
1082
+ * declare-and-degrade fallback). Every
1083
+ * install shape serves the row's volatile Config under the row id —
1084
+ * standalone install, family bundle, or mode pack — so the card follows the
1085
+ * served namespace rather than any carrier bundle, and a composition without
1086
+ * the row shows no trace of it. The card carries the family core's shared
1087
+ * ProviderAuthBlock (dsh authenticates through the host credentials, so the
1088
+ * block renders status only), the DeepSeek delegation switch — migrated from
1089
+ * the core section's `local-agent.settings.row-action` seat, which this
1090
+ * plugin no longer contributes to — and the resident-mode block (live switch)
1091
+ * plus the default-model block (free-text input, the broker's effective-model
1092
+ * line and suggestion vocabulary), whose writes ride the bound settingsScope:
1093
+ * the host watcher hot-applies them, no reload. Every read degrades: an
1094
+ * absent gateway renders the auth block's 'unavailable' state and drops the
1095
+ * model surface to the bare input, an unregistered namespace disables the
1096
+ * controls.
1097
+ * @module @khorsheed/dsh-local-agent-dsh/client
1043
1098
  */
1044
- const PACKAGE_NAME = "@khorsheed/dsh-local-agent-dsh";
1045
1099
  /**
1046
1100
  * Required services: slot registry, command Remote, and locale registry. The
1047
1101
  * settings scope is NOT a static inject — its service name differs per host
1048
1102
  * line (`settingsScope` on 0.1.5, `configForms` on rc.1), so a static entry
1049
1103
  * would pend the whole client plugin on the other line; bindSettingsScope
1050
1104
  * probes the serving one instead (the settings client package is a declared
1051
- * client inject, so one of them is always already up).
1105
+ * client inject, so one of them is always already up). The card track probes
1106
+ * `configForms` through the same kind of deferred inject (see apply).
1052
1107
  */
1053
1108
  const inject = [
1054
1109
  "slots",
@@ -1057,7 +1112,7 @@ window.__ModuleLoader__.load({
1057
1112
  "locale"
1058
1113
  ];
1059
1114
  /**
1060
- * Client plugin body: register the dictionaries and the dsh settings surfaces
1115
+ * Client plugin body: register the dictionaries and the dsh settings card
1061
1116
  * bound to the local-agent-dsh namespace.
1062
1117
  * @param ctx - client root context.
1063
1118
  */
@@ -1066,12 +1121,12 @@ window.__ModuleLoader__.load({
1066
1121
  zh,
1067
1122
  en
1068
1123
  }), "local-agent-dsh: dictionaries");
1124
+ const t = ctx.locale.bind(NS);
1069
1125
  const scope = bindSettingsScope(ctx, "local-agent-dsh");
1070
1126
  const authT = ctx.locale.bind("local-agent");
1071
1127
  const gateway = () => ctx.get("remote.localAgentGateway");
1072
- const cardInject = () => ({
1128
+ const cardFace = () => ({
1073
1129
  scope,
1074
- hooks: { settings: scope },
1075
1130
  authT,
1076
1131
  renderModelPicker: (props) => ctx.get("localAgentUi")?.renderHarnessModelPicker("dsh", props),
1077
1132
  harnessModel: () => gateway()?.harnessModel("dsh").then((result) => result.ok ? result.value : void 0) ?? Promise.resolve(void 0),
@@ -1080,28 +1135,45 @@ window.__ModuleLoader__.load({
1080
1135
  runCommand: (sessionId, line) => ctx.remote.commands.execute(sessionId, line, []).then((result) => result.ok ? result.value?.result.text : void 0)
1081
1136
  }
1082
1137
  });
1083
- ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
1084
- name: "plugins.bundle.config",
1085
- key: PACKAGE_NAME,
1086
- locale: NS,
1087
- inject: cardInject
1088
- }, DshBundleConfig));
1089
- ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
1090
- name: "plugins.row.config",
1091
- key: "@khorsheed/dsh-bundle-local-agent#local-agent-dsh",
1092
- locale: NS,
1093
- inject: cardInject
1094
- }, DshBundleConfig));
1095
- const legacy = ctx.slots;
1096
- legacy.inject("settings.plugin.item", () => legacy.register({
1097
- name: "settings.plugin.item",
1098
- key: "local-agent-dsh",
1099
- locale: NS,
1100
- inject: cardInject
1101
- }, DshSettingsCard));
1138
+ const cardInject = () => ({
1139
+ ...cardFace(),
1140
+ hooks: { settings: scope }
1141
+ });
1142
+ const subscribeScope = (listener) => scope.subscribe(listener);
1143
+ const readScope = () => scope.getSnapshot();
1144
+ const useSettings = (select) => select((0, react.useSyncExternalStore)(subscribeScope, readScope));
1145
+ ctx.inject(["configForms"], (formsCtx) => {
1146
+ const forms = formsCtx.configForms;
1147
+ if (typeof forms.whileServed !== "function") return;
1148
+ return forms.whileServed(["local-agent-dsh"], () => {
1149
+ const ui = ctx.get("localAgentUi");
1150
+ if (typeof ui?.registerHarnessCard === "function") return ui.registerHarnessCard({
1151
+ name: "dsh",
1152
+ order: 140,
1153
+ locale: NS,
1154
+ label: () => t("card.title"),
1155
+ render: ({ active, useSessions }) => (0, react.createElement)(DshCardSection, {
1156
+ active,
1157
+ useSessions,
1158
+ ...cardFace(),
1159
+ useSettings,
1160
+ t
1161
+ })
1162
+ });
1163
+ return ctx.slots.inject("plugins.item", () => ctx.slots.register({
1164
+ name: "plugins.item",
1165
+ id: "local-agent-dsh",
1166
+ order: 140,
1167
+ label: () => t("card.title"),
1168
+ locale: NS,
1169
+ inject: cardInject
1170
+ }, DshBundleConfig));
1171
+ });
1172
+ });
1102
1173
  }
1103
1174
  //#endregion
1104
1175
  exports.DshBundleConfig = DshBundleConfig;
1176
+ exports.DshCardSection = DshCardSection;
1105
1177
  exports.DshSettingsCard = DshSettingsCard;
1106
1178
  exports.NS = NS;
1107
1179
  exports.apply = apply;
@@ -1,11 +1,11 @@
1
- import { provisionDshScope } from "./provision.js";
1
+ import { provisionDshScope, unitSubProfileName } from "./provision.js";
2
2
  import { t as resolveDshSessionLog } from "./session-log-DOsSz56-.js";
3
3
  import { join } from "node:path";
4
- import { credentialRef } from "@deepseek-ai/dsh-credentials";
5
4
  import { LiveFlush, LiveStreamPublisher, assertResumeCwdUnchanged, assertResumeScopeUnchanged, containerExecSpawn, containerScopedHome, delegationEnv, establishSubagentCatalogChild, persistChildSession, probeCliVersion, resolveChildCwd, resolveRoundModel, subagentDelegationLabel } from "@khorsheed/dsh-local-agent";
6
- import { randomUUID } from "node:crypto";
5
+ import { createHash, randomUUID } from "node:crypto";
6
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
7
7
  import { SessionId } from "@deepseek-ai/dsh-session";
8
- import { NO_START_CAPABILITIES, settleRunResult, subprocessRunHandle } from "@deepseek-ai/dsh-subagent";
8
+ import { NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, settleRunResult, subprocessRunHandle } from "@deepseek-ai/dsh-subagent";
9
9
  import { MEMBER_BRIDGE_SOCKET_ENV, MEMBER_BRIDGE_TOKEN_ENV } from "@khorsheed/dsh-local-agent/types";
10
10
  import { StringDecoder } from "node:string_decoder";
11
11
  import { BlockAssembler, createAssistantMessage } from "@deepseek-ai/dsh-llm";
@@ -13,6 +13,67 @@ import { LIVE_SERVER_NAME, LIVE_WIRE_PROTOCOL_VERSION } from "@khorsheed/dsh-loc
13
13
  import { readFile, readdir } from "node:fs/promises";
14
14
  import { promisify } from "node:util";
15
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
16
77
  //#region lib/types/session-mirror.js
17
78
  /**
18
79
  * Mirror a sub-dsh session's events into the parent-side dsh subagent session,
@@ -428,6 +489,7 @@ var LiveRuntime = class {
428
489
  * spawned with no model flag at all. A round whose start model differs
429
490
  * retires the runtime instead of silently running the old one.
430
491
  */
492
+ modelLaunchFingerprint;
431
493
  configurationKey;
432
494
  boundModel;
433
495
  reclaimed = false;
@@ -695,12 +757,12 @@ var DshLiveDriver = class {
695
757
  * The member's live runtime, spawning it (once per member at a time) when
696
758
  * absent or dead. `signal` abandons the spawn on cancellation.
697
759
  */
698
- async ensureRuntime(spec, apiKey, signal) {
760
+ async ensureRuntime(spec, launch, signal) {
699
761
  const key = spec.sessionId;
700
762
  const startModel = spec.startModel?.trim();
701
763
  const existing = this.runtimes.get(key);
702
764
  if (existing !== void 0 && !existing.dead) {
703
- if (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);
765
+ 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);
704
766
  else {
705
767
  this.clearIdleTimer(key);
706
768
  return existing;
@@ -708,7 +770,7 @@ var DshLiveDriver = class {
708
770
  } else if (existing !== void 0) this.runtimes.delete(key);
709
771
  const pending = this.ensuring.get(key);
710
772
  if (pending !== void 0) return pending;
711
- const spawn = this.spawnRuntime(spec, apiKey, signal).finally(() => {
773
+ const spawn = this.spawnRuntime(spec, launch, signal).finally(() => {
712
774
  this.ensuring.delete(key);
713
775
  });
714
776
  this.ensuring.set(key, spawn);
@@ -722,7 +784,7 @@ var DshLiveDriver = class {
722
784
  * cancellation mid-spawn reclaims the half-started runtime and throws the
723
785
  * abort instead, without touching the breaker.
724
786
  */
725
- async spawnRuntime(spec, apiKey, signal) {
787
+ async spawnRuntime(spec, launch, signal) {
726
788
  const key = spec.sessionId;
727
789
  if (this.disposed) throw new LiveChannelUnavailableError("the live driver is disposed");
728
790
  const profileName = this.config.profileName ?? "headless-local-agent-dsh";
@@ -747,7 +809,7 @@ var DshLiveDriver = class {
747
809
  graceMs: DEFAULT_DISPOSE_GRACE_MS,
748
810
  env: delegationEnv({
749
811
  DSH_HOME: spec.homeDir,
750
- DEEPSEEK_API_KEY: apiKey,
812
+ ...launch.env,
751
813
  ...process.env.TSX_TSCONFIG_PATH === void 0 ? {} : { TSX_TSCONFIG_PATH: process.env.TSX_TSCONFIG_PATH },
752
814
  ...member === void 0 ? {} : member.env
753
815
  })
@@ -764,6 +826,7 @@ var DshLiveDriver = class {
764
826
  this.ctx.logger.warn(message);
765
827
  });
766
828
  runtime.boundModel = model === void 0 || model === "" ? void 0 : model;
829
+ runtime.modelLaunchFingerprint = launch.fingerprint;
767
830
  runtime.configurationKey = spec.configuration === void 0 ? void 0 : JSON.stringify(spec.configuration);
768
831
  runtime.onDead = () => {
769
832
  if (this.runtimes.get(key) === runtime) this.runtimes.delete(key);
@@ -821,9 +884,12 @@ var DshLiveDriver = class {
821
884
  signal.throwIfAborted();
822
885
  if (this.draining || this.disabled) throw new LiveChannelUnavailableError("the live driver is unavailable for preparation");
823
886
  try {
824
- provisionDshScope(spec.homeDir, this.config);
825
- const apiKey = await resolveApiKey(this.ctx, this.config);
826
- await (await this.ensureRuntime(spec, apiKey, signal)).request("session/prepare", {
887
+ provisionDshScope(spec.homeDir, {
888
+ ...this.config,
889
+ inheritModelConfiguration: true
890
+ });
891
+ const launch = await localModelLaunch(this.ctx, this.config, spec.configuration?.model ?? spec.startModel ?? this.config.modelFor?.(spec.sessionId));
892
+ await (await this.ensureRuntime(spec, launch, signal)).request("session/prepare", {
827
893
  sessionId: spec.sessionId,
828
894
  resume: spec.resume !== void 0
829
895
  });
@@ -942,6 +1008,7 @@ var DshLiveDriver = class {
942
1008
  for (const entry of bufferedEvents.splice(0)) if (entry.kind === "event") mirrorOne(entry.event);
943
1009
  else acceptStream(entry.frame);
944
1010
  };
1011
+ let nativeFailure;
945
1012
  let resolveIdle;
946
1013
  const idle = new Promise((resolve) => {
947
1014
  resolveIdle = resolve;
@@ -971,10 +1038,13 @@ var DshLiveDriver = class {
971
1038
  resolveIdle({ reason });
972
1039
  };
973
1040
  };
974
- provisionDshScope(spec.homeDir, this.config);
975
- const apiKey = await resolveApiKey(this.ctx, this.config);
1041
+ provisionDshScope(spec.homeDir, {
1042
+ ...this.config,
1043
+ inheritModelConfiguration: true
1044
+ });
1045
+ const launch = await localModelLaunch(this.ctx, this.config, spec.configuration?.model ?? spec.startModel ?? this.config.modelFor?.(spec.sessionId));
976
1046
  const accepted = (async () => {
977
- const rt = await this.ensureRuntime(spec, apiKey, request.signal);
1047
+ const rt = await this.ensureRuntime(spec, launch, request.signal);
978
1048
  runtime = rt;
979
1049
  if (runAbort.signal.aborted) {
980
1050
  await this.reclaim(spec.sessionId);
@@ -1004,7 +1074,13 @@ var DshLiveDriver = class {
1004
1074
  output: collectOutput(),
1005
1075
  stopReason: "aborted"
1006
1076
  };
1007
- if (reason?.kind === "error") throw new Error(`subagent-dsh live: the turn failed: ${reason.error.message}`);
1077
+ if (reason?.kind === "error") {
1078
+ nativeFailure = {
1079
+ message: reason.error.message,
1080
+ code: reason.error.code
1081
+ };
1082
+ throw new Error(`subagent-dsh live: the turn failed: ${reason.error.message}`);
1083
+ }
1008
1084
  if (reason?.kind !== "completed") throw new Error("subagent-dsh live: the turn produced no outcome");
1009
1085
  const output = collectOutput();
1010
1086
  if (output.length === 0) throw new Error("subagent-dsh live: the turn completed but produced no answer");
@@ -1068,7 +1144,7 @@ var DshLiveDriver = class {
1068
1144
  turn,
1069
1145
  reason: {
1070
1146
  kind: "error",
1071
- error: {
1147
+ error: nativeFailure ?? {
1072
1148
  message: "the live round did not complete",
1073
1149
  code: "UNKNOWN"
1074
1150
  }
@@ -1118,7 +1194,7 @@ var DshLiveDriver = class {
1118
1194
  });
1119
1195
  await Promise.race([accepted, abortBranch.catch(() => void 0)]);
1120
1196
  return subprocessRunHandle({
1121
- id: childSession.id,
1197
+ id: SessionId(randomUUID()),
1122
1198
  result,
1123
1199
  signal: request.signal,
1124
1200
  onAbort,
@@ -1272,7 +1348,7 @@ var DshCliProvider = class {
1272
1348
  ...record?.configurationLock === void 0 ? {} : { configurationLock: record.configurationLock }
1273
1349
  }, (configuration) => this.startDshResume(request, intent, cwd, homeDir, exec, scope, record?.model, configuration), request.signal, intent?.onAdmitted);
1274
1350
  }
1275
- const childSessionId = SessionId(intent?.preparedMemberId ?? randomUUID());
1351
+ const childSessionId = SessionId(intent?.preparedMemberId ?? intent?.childSessionId ?? randomUUID());
1276
1352
  if (typeof this.ctx.localAgent.withMemberConfigurationRound !== "function") {
1277
1353
  if (intent?.effort !== void 0) throw new Error("DSH effort requires the configuration admission core");
1278
1354
  return this.startDshFresh(request, cwd, homeDir, exec, scope, intent?.model, childSessionId);
@@ -1301,9 +1377,11 @@ var DshCliProvider = class {
1301
1377
  delegationDepth: (request.parent.session.header.delegationDepth ?? 0) + 1
1302
1378
  } });
1303
1379
  const harness = this.ctx.localAgent.get("dsh");
1304
- const label = subagentDelegationLabel(harness?.displayName ?? "dsh", request.descriptor.label);
1380
+ const label = subagentDelegationLabel(harness?.displayName ?? "dsh", request.label || void 0);
1305
1381
  childSession.append("subagent/descriptor", {
1306
- ...request.descriptor,
1382
+ version: SUBAGENT_DESCRIPTOR_VERSION,
1383
+ mode: "one-shot",
1384
+ provider: this.name,
1307
1385
  label
1308
1386
  });
1309
1387
  establishSubagentCatalogChild(request.parent.session, childSession.header, label);
@@ -1505,9 +1583,12 @@ async function startDshCliRun(request, spec) {
1505
1583
  const { config, ctx } = spec;
1506
1584
  const task = dshTextTask(request.prompt);
1507
1585
  if (request.signal.aborted) throw new Error("subagent-dsh: request was aborted before the CLI started");
1508
- const profileName = config.profileName ?? "headless-local-agent-dsh";
1509
- if (spec.exec === void 0) provisionDshScope(spec.homeDir, config);
1510
- const apiKey = await resolveApiKey(ctx, config);
1586
+ const profileName = spec.exec === void 0 ? config.profileName ?? "headless-local-agent-dsh" : unitSubProfileName(config);
1587
+ if (spec.exec === void 0) provisionDshScope(spec.homeDir, {
1588
+ ...config,
1589
+ inheritModelConfiguration: true
1590
+ });
1591
+ const modelEnv = spec.exec === void 0 ? (await localModelLaunch(ctx, config, spec.model)).env : { DEEPSEEK_API_KEY: await resolveApiKey(ctx, config) };
1511
1592
  const turn = spec.resume?.turn ?? 1;
1512
1593
  spec.childSession?.append("turn/start", { turn });
1513
1594
  const launch = dshLaunchArgv(config);
@@ -1531,7 +1612,7 @@ async function startDshCliRun(request, spec) {
1531
1612
  ];
1532
1613
  const env = delegationEnv({
1533
1614
  DSH_HOME: spec.homeDir,
1534
- DEEPSEEK_API_KEY: apiKey,
1615
+ ...modelEnv,
1535
1616
  ...spec.exec !== void 0 || process.env.TSX_TSCONFIG_PATH === void 0 ? {} : { TSX_TSCONFIG_PATH: process.env.TSX_TSCONFIG_PATH },
1536
1617
  ...spec.exec === void 0 || spec.exec.env?.["NODE_OPTIONS"] !== void 0 ? {} : { NODE_OPTIONS: CONTAINER_NODE_OPTIONS },
1537
1618
  ...spec.memberEnv
@@ -1682,7 +1763,7 @@ async function startDshCliRun(request, spec) {
1682
1763
  }
1683
1764
  }, () => {});
1684
1765
  return Promise.resolve(subprocessRunHandle({
1685
- id: spec.childSession?.id ?? SessionId(randomUUID()),
1766
+ id: SessionId(randomUUID()),
1686
1767
  result,
1687
1768
  signal: request.signal,
1688
1769
  onAbort,
@@ -1698,4 +1779,4 @@ async function resolveApiKey(ctx, config) {
1698
1779
  return resolved.value;
1699
1780
  }
1700
1781
  //#endregion
1701
- export { MEMBER_BRIDGE_ENTRY_ENV as a, dshTextTask as c, startDshCliRun as d, DEFAULT_LIVE_IDLE_MS as f, DshCliProvider as i, registerMemberRun as l, 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 };
1782
+ 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 };