dsh-session-bridge 0.3.1 → 0.3.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -119,13 +119,25 @@ time — not just its final reply — and act on it:
119
119
  # type-check + bundle the host build + pack a tgz (DSH_CHECKOUT points at the dsh source checkout)
120
120
  bash scripts/build.sh && npm run build:client
121
121
 
122
+ # type-check src/ against the dsh that is actually installed (no checkout needed)
123
+ npm run check:compat
124
+
122
125
  # via the injector toolchain
123
126
  dev_build_plugin dsh-session-bridge
124
127
  ```
125
128
 
126
129
  `build.sh` type-links against a local DSH checkout and is only for local dev.
130
+ That checkout frequently lags the harness the plugin is loaded into, so a green
131
+ `build.sh` does **not** prove the plugin works on the running DSH — `build.sh`
132
+ warns when the two versions differ. Use `npm run check:compat` for that: it
133
+ type-checks `src/` against the `lib/types/*.d.ts` shipped inside the installed
134
+ DSH package, then reads the built `lib/index.js.map` to verify the
135
+ self-contained bundle actually inlined that same DSH version. (Type-checking
136
+ alone is not enough — a stale checkout type-checks green while the artifact
137
+ ships old DSH code.)
138
+
127
139
  The GitHub Actions CI (`ci.yml`) instead resolves the `@deepseek-ai/dsh-*`
128
- prereleases from the registry — pinned to the `0.1.2-alpha.2` line, which is
140
+ prereleases from the registry — pinned to the `0.1.6-alpha.2` line, which is
129
141
  the DSH API surface this code targets — then runs `pnpm typecheck` and
130
142
  `pnpm build:client` (the self-contained `tsdown` bundle). Bump that pin
131
143
  together with the code when you migrate to a newer DSH API.
@@ -149,6 +161,14 @@ publishing.
149
161
  npx -p @deepseek-ai/dsh dsh plugin --profile web add dsh-session-bridge
150
162
  ```
151
163
 
164
+ A prerelease tag (`v0.3.2-alpha.1`) is published to its own dist-tag (`alpha`,
165
+ `beta`, `rc`) instead of `latest`, so it cannot displace the stable release for
166
+ other users. Opt in explicitly:
167
+
168
+ ```bash
169
+ npx -p @deepseek-ai/dsh dsh plugin --profile web add dsh-session-bridge@alpha
170
+ ```
171
+
152
172
  pnpm installs the published tarball and runs its `prepare` script (`tsdown`) to
153
173
  ensure `lib/` is present, then `dsh` activates the bundle.
154
174
 
@@ -259,6 +279,19 @@ scripts/
259
279
  build.sh type-check + link types against the DSH checkout
260
280
  ```
261
281
 
282
+ ## Lifecycle and unloading
283
+
284
+ DSH ≥ 0.1.6 mounts and **unloads plugins at runtime** (Settings → Plugins, and
285
+ the injector's hot reload). This plugin unloads cleanly: it registers no
286
+ loader-level state, its tools are disposed with the plugin fiber, and the
287
+ monitor's timer is released through `ctx.effect` on unload.
288
+
289
+ One consequence of that ownership model: sessions created by
290
+ `session_bridge_create` are owned by the plugin fiber (the agent is created
291
+ under the plugin's context), so **unloading or reloading the plugin stops their
292
+ live agents**. The sessions themselves are persisted and show as offline; bring
293
+ one back with `session_bridge_resume`. Monitor watchdogs also stop on unload.
294
+
262
295
  ## License
263
296
 
264
297
  MIT
package/README.zh.md CHANGED
@@ -87,10 +87,19 @@
87
87
  # 类型检查 + 打包 host bundle + 生成 tgz(DSH_CHECKOUT 指向 dsh 源码 checkout)
88
88
  bash scripts/build.sh && npm run build:client
89
89
 
90
+ # 针对"实际安装的 dsh"做类型检查(不需要 checkout)
91
+ npm run check:compat
92
+
90
93
  # 或经注入器工具链
91
94
  dev_build_plugin dsh-session-bridge
92
95
  ```
93
96
 
97
+ `build.sh` 按本地 dsh 源码 checkout 做类型链接,仅用于本地开发。该 checkout 常常
98
+ 落后于插件实际加载进的 harness,因此 `build.sh` 通过**并不**代表插件在运行中的
99
+ DSH 上可用——两者版本不一致时 `build.sh` 会给出警告。要验证运行版本请用
100
+ `npm run check:compat`:它按已安装 DSH 包内随附的 `lib/types/*.d.ts`(即插件真正
101
+ 加载的 API 面)对 `src/` 做类型检查。
102
+
94
103
  ## 部署
95
104
 
96
105
  DSH web 从活动 profile 加载外部插件。本包是一个 **bundle**:`package.json` 声明了
@@ -107,6 +116,13 @@ DSH web 从活动 profile 加载外部插件。本包是一个 **bundle**:`pac
107
116
  npx -p @deepseek-ai/dsh dsh plugin --profile web add dsh-session-bridge
108
117
  ```
109
118
 
119
+ 预发布标签(`v0.3.2-alpha.1`)会发布到自己的 dist-tag(`alpha`/`beta`/`rc`),
120
+ 而不会占用 `latest`,因此不会顶掉其他用户使用的稳定版。需要显式选用:
121
+
122
+ ```bash
123
+ npx -p @deepseek-ai/dsh dsh plugin --profile web add dsh-session-bridge@alpha
124
+ ```
125
+
110
126
  pnpm 会安装发布的 tarball 并运行其 `prepare` 脚本(`tsdown`)以确保 `lib/` 就绪,
111
127
  随后 `dsh` 激活该 bundle。
112
128
 
@@ -208,6 +224,17 @@ scripts/
208
224
  build.sh 类型检查 + 链接 DSH checkout 类型
209
225
  ```
210
226
 
227
+ ## 生命周期与卸载
228
+
229
+ DSH ≥ 0.1.6 支持**运行时挂载/卸载**插件(设置 → 插件页开关、注入器热重载)。
230
+ 本插件可干净卸载:不注册 loader 级状态,工具随插件 fiber 一并释放,监控定时器
231
+ 经 `ctx.effect` 在卸载时清理。
232
+
233
+ 该所有权模型带来一个后果:`session_bridge_create` 创建的会话归插件 fiber 所有
234
+ (agent 在插件上下文下创建),因此**卸载/重载插件会停止这些会话的活动 agent**。
235
+ 会话本身已持久化并显示为离线,可用 `session_bridge_resume` 重新上线;守护循环
236
+ 同样在卸载时停止。
237
+
211
238
  ## License
212
239
 
213
240
  MIT
package/dsh.plugin.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-session-bridge",
3
- "version": "0.3.1",
3
+ "version": "0.3.2-alpha.2",
4
4
  "main": "./lib/index.js",
5
5
  "description": "会话桥:创建主会话 / 向任意会话发消息 / 等待回复 / 读取消息 / 按名或 id 查找会话(支持跨工作区);另含监控/调度主任务与归档会话。",
6
6
  "engines": {
7
- "dsh": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0"
7
+ "dsh": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0"
8
8
  },
9
9
  "contributes": {
10
10
  "tools": [
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { appendFileSync, mkdirSync } from "node:fs";
6
6
  import { randomUUID } from "node:crypto";
7
- //#region node_modules/.pnpm/@deepseek-ai+dsh-home-paths@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-home-paths/lib/index.js
7
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-home-paths@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-home-paths/lib/index.js
8
8
  /**
9
9
  * Shared filesystem path helpers for DeepSeek Harness user data.
10
10
  *
@@ -2200,7 +2200,7 @@ var Service = class Service {
2200
2200
  }
2201
2201
  };
2202
2202
  //#endregion
2203
- //#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
2203
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
2204
2204
  /** The one Remote failure class shared by owners, the Gateway, and consumers. */
2205
2205
  /**
2206
2206
  * One Remote call failure: a real Error carrying its stable code and typed
@@ -2345,7 +2345,7 @@ function validateName(subject, value) {
2345
2345
  if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
2346
2346
  }
2347
2347
  //#endregion
2348
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
2348
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
2349
2349
  /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
2350
2350
  /**
2351
2351
  * Mark an unreachable closed-union branch.
@@ -2557,7 +2557,7 @@ function deepFreeze(value) {
2557
2557
  return value;
2558
2558
  }
2559
2559
  //#endregion
2560
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypto@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
2560
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypto@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
2561
2561
  /**
2562
2562
  * Random v4 UUID, minted from `crypto.getRandomValues`.
2563
2563
  * @returns the UUID string.
@@ -2570,7 +2570,7 @@ function randomUUID$1() {
2570
2570
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2571
2571
  }
2572
2572
  //#endregion
2573
- //#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-brand/lib/index.js
2573
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-brand/lib/index.js
2574
2574
  /**
2575
2575
  * Duplicate-install-safe nominal primitive helpers.
2576
2576
  *
@@ -3187,11 +3187,11 @@ defineMethod("transform", [
3187
3187
  "preserve"
3188
3188
  ], ({ inner }, isInner) => inner.toString(isInner));
3189
3189
  //#endregion
3190
- //#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
3190
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
3191
3191
  /** Largest delay Node schedules without clamping it to one millisecond. */
3192
3192
  const MAX_TIMER_DELAY_MS = 2147483647;
3193
3193
  //#endregion
3194
- //#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-llm/lib/index.js
3194
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-llm/lib/index.js
3195
3195
  /**
3196
3196
  * Bound one `notice` summary to {@link CONTEXT_SUMMARY_MAX_CHARS}.
3197
3197
  * @param summary - the producer's one-line account, of any length.
@@ -3441,13 +3441,15 @@ function failureSnapshot(value) {
3441
3441
  const status = candidate.status;
3442
3442
  const providerRetryAfterMs = candidate.providerRetryAfterMs;
3443
3443
  const requestId = candidate.requestId;
3444
- if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0)) return void 0;
3444
+ const offloadImages = candidate.offloadImages;
3445
+ if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0) || offloadImages !== void 0 && (!Number.isSafeInteger(offloadImages) || offloadImages <= 0)) return void 0;
3445
3446
  return Object.freeze({
3446
3447
  message,
3447
3448
  code,
3448
3449
  ...status === void 0 ? {} : { status },
3449
3450
  ...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
3450
- ...requestId === void 0 ? {} : { requestId }
3451
+ ...requestId === void 0 ? {} : { requestId },
3452
+ ...offloadImages === void 0 ? {} : { offloadImages }
3451
3453
  });
3452
3454
  } catch (_sdkFailureGetter) {
3453
3455
  return;
@@ -3686,7 +3688,8 @@ var LlmError = class extends HarnessError {
3686
3688
  code,
3687
3689
  ...options?.status === void 0 ? {} : { status: options.status },
3688
3690
  ...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
3689
- ...options?.requestId === void 0 ? {} : { requestId: options.requestId }
3691
+ ...options?.requestId === void 0 ? {} : { requestId: options.requestId },
3692
+ ...options?.offloadImages === void 0 ? {} : { offloadImages: options.offloadImages }
3690
3693
  });
3691
3694
  }
3692
3695
  };
@@ -3954,7 +3957,8 @@ var LlmError = class extends HarnessError {
3954
3957
  id: model.id,
3955
3958
  ...model.name === void 0 ? {} : { name: model.name },
3956
3959
  ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
3957
- ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
3960
+ ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
3961
+ ...model.inputModalities === void 0 ? {} : { inputModalities: [...model.inputModalities] }
3958
3962
  });
3959
3963
  }
3960
3964
  return models;
@@ -9077,7 +9081,7 @@ function superRefine(fn, params) {
9077
9081
  return /* @__PURE__ */ _superRefine(fn, params);
9078
9082
  }
9079
9083
  //#endregion
9080
- //#region node_modules/.pnpm/@deepseek-ai+dsh-storage@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-storage/lib/index.js
9084
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-storage@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-storage/lib/index.js
9081
9085
  /**
9082
9086
  * Backend-facing vocabulary of the storage hub: a backend owns one medium
9083
9087
  * (a file-tree root, a database file) and exposes operation groups over it.
@@ -9088,7 +9092,7 @@ function superRefine(fn, params) {
9088
9092
  /** Allowed format for unit and table names: safe as a file name and as a SQL identifier segment without escaping. */
9089
9093
  const UNIT_NAME_RE = /^[a-z][a-z0-9_]*$/;
9090
9094
  //#endregion
9091
- //#region node_modules/.pnpm/@deepseek-ai+dsh-storage-domain@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+ds_8f094eabec231701d08ef59df76cca5d/node_modules/@deepseek-ai/dsh-storage-domain/lib/index.js
9095
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-storage-domain@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+ds_5fa439225de576bdb2338dd42cd57e82/node_modules/@deepseek-ai/dsh-storage-domain/lib/index.js
9092
9096
  /**
9093
9097
  * Domain declaration vocabulary. A spec object is the single source of a
9094
9098
  * domain's identity, layout, and record schemas: the owning package defines
@@ -9211,11 +9215,19 @@ Service.init;
9211
9215
  */
9212
9216
  /**
9213
9217
  * 兼容读取不同 dsh-session 版本上的会话事件:
9214
- * - 旧版 `Session` 暴露 `get events(): readonly SessionEvent[]`;
9215
- * - 新版(如宿主实际运行的 0.1.2-rc.x)将 `get events` 改为
9216
- * `snapshotEvents(fromSeq?, toSeqExclusive?)` 方法——`.events` 直接读取会
9217
- * 得到 `undefined`,对 for...of 迭代即抛 `events is not iterable`。
9218
+ * - 旧版 \`Session\` 暴露 \`get events(): readonly SessionEvent[]\`;
9219
+ * - 新版将 \`get events\` 改为 \`snapshotEvents(fromSeq?, toSeqExclusive?)\` 方法——
9220
+ * \`.events\` 直接读取会得到 \`undefined\`,对 for...of 迭代即抛
9221
+ * \`events is not iterable\`。
9218
9222
  * 两者都读不到(或 session 不存在)时回退为空数组,绝不抛迭代错误。
9223
+ *
9224
+ * 注意:dsh 0.1.6-alpha.1 起 \`snapshotEvents()\`(连同 \`eventAt()\` /
9225
+ * \`ownEvents()\`)已标记 @deprecated —— 官方策略是"现有逻辑可暂不迁移,但禁止
9226
+ * 新增调用";其替代不是同步读,而是(a)恢复后读取 Session 投影/派生状态,或
9227
+ * (b)按需异步分页读取历史窗口(见 dsh 决策
9228
+ * 2026-09-09-deprecate-synchronous-session-event-reads)。本函数的调用方
9229
+ * (foldMessages / segmentsSince / wait 基线)仍依赖完整同步快照,故按该决定暂缓
9230
+ * 迁移;将来官方补齐分页读后,只需改造这一个入口。
9219
9231
  */
9220
9232
  function sessionEvents(session) {
9221
9233
  const s = session;
@@ -10038,7 +10050,7 @@ var SessionMonitor = class {
10038
10050
  }
10039
10051
  };
10040
10052
  //#endregion
10041
- //#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-invaria_a35c65afc1061b4908fad5a531841731/node_modules/@deepseek-ai/dsh-scope/lib/index.js
10053
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-invaria_8802cbeca2a66ef97a5b4e663664f4cd/node_modules/@deepseek-ai/dsh-scope/lib/index.js
10042
10054
  /**
10043
10055
  * Shared insertion-ordered storage and effect ownership for scope-aware registries.
10044
10056
  *
@@ -10317,7 +10329,89 @@ function scopeTarget(base, key) {
10317
10329
  return carrier;
10318
10330
  }
10319
10331
  //#endregion
10320
- //#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1.5-alpha.2_2b754afda902b21956a20e8a701ed6ef/node_modules/@deepseek-ai/dsh-tools/lib/index.js
10332
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-sandbox@0.1.6-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-llm@0_5328c6913d63e98a7878ca36cab93ca2/node_modules/@deepseek-ai/dsh-sandbox/lib/index.js
10333
+ /**
10334
+ * The escalation vocabulary and choreography shared by every sandbox-enforcing
10335
+ * tool family (`@deepseek-ai/dsh-tool-bash`, `@deepseek-ai/dsh-tool-fs`): the
10336
+ * strictly-wider ladder, the argument-pairing validation, the model-facing
10337
+ * denial/hint markers, and {@link approveEscalation} — the ordered fail-closed
10338
+ * sequence that resolves a `sandbox_permissions` request through a
10339
+ * user-approval channel BEFORE anything executes. One home keeps the two
10340
+ * families' approval ordering and verbatim error texts from drifting apart.
10341
+ *
10342
+ * The channel is a minimal STRUCTURAL function shape ({@link EscalationAsk}),
10343
+ * not the approval service type: the tool layer — which owns the agent, the
10344
+ * call id, and the tool name — closes over `ctx.approval.request(...)` and
10345
+ * hands the closure down, so this package never depends on the approval or
10346
+ * agent packages.
10347
+ *
10348
+ * @module dsh-sandbox/escalation
10349
+ */
10350
+ /**
10351
+ * The strictly-wider table: what a call whose effective mode is the key may
10352
+ * escalate TO. Checked at EXECUTION, never baked into a tool schema — the
10353
+ * schema's enum is {@link ESCALATION_TARGETS}, because schemas are
10354
+ * registry-global while the effective mode is per-call truth.
10355
+ */
10356
+ const WIDER_MODES = {
10357
+ "read-only": ["workspace-write", "danger-full-access"],
10358
+ "workspace-write": ["danger-full-access"]
10359
+ };
10360
+ /**
10361
+ * The closed escalation-target vocabulary — every mode a call could ever
10362
+ * escalate TO (`read-only` is the floor; nothing escalates to it). Advertised
10363
+ * whenever the mounted capability confines: cutting the enum down to the modes
10364
+ * wider than the composition's DEFAULT would strand a session whose effective
10365
+ * mode sits below it (a `danger-full-access` default would advertise nothing
10366
+ * while a narrower-switched session stays confined with no lever).
10367
+ */
10368
+ const ESCALATION_TARGETS = ["workspace-write", "danger-full-access"];
10369
+ /**
10370
+ * Validate the escalation argument pairing a tool schema cannot express:
10371
+ * `sandbox_permissions` and `justification` travel together — an approval
10372
+ * prompt without a reason, or a reason driving nothing, is a malformed ask —
10373
+ * and the justification must be a non-empty sentence.
10374
+ * @param sandboxPermissions - the raw `sandbox_permissions` argument, if given.
10375
+ * @param justification - the raw `justification` argument, if given.
10376
+ */
10377
+ function validateEscalationArgs(sandboxPermissions, justification) {
10378
+ if (sandboxPermissions !== void 0 && justification === void 0) throw new Error("invalid escalation: sandbox_permissions requires a justification");
10379
+ if (justification !== void 0 && sandboxPermissions === void 0) throw new Error("invalid escalation: justification is only valid together with sandbox_permissions");
10380
+ if (justification !== void 0 && justification.trim().length === 0) throw new Error("invalid justification: expected a non-empty sentence");
10381
+ }
10382
+ /**
10383
+ * Resolve a sandbox permission request before execution. Repeating the call's
10384
+ * effective mode returns it without approval. A strictly wider mode requires
10385
+ * approval and applies only to this call. Narrower or unsupported targets,
10386
+ * missing approval services or agents for widening, and non-grant outcomes
10387
+ * throw before execution.
10388
+ * @param request - the escalation to judge (see {@link EscalationRequest}).
10389
+ * @param approval - the approval ingredients the tool holds (see {@link EscalationApproval}).
10390
+ * @returns the granted mode, consumed by the one call that asked.
10391
+ */
10392
+ async function approveEscalation(request, approval) {
10393
+ const { requestedMode: mode, effectiveMode, justification, subject } = request;
10394
+ if (mode === effectiveMode) return effectiveMode;
10395
+ if (!(WIDER_MODES[effectiveMode] ?? []).includes(mode)) throw new Error(`sandbox escalation to "${mode}" is not strictly wider than this call's current "${effectiveMode}" mode`);
10396
+ if (approval.approver === void 0) throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval service is composed`);
10397
+ if (approval.agent === void 0) throw new Error(`sandbox escalation to "${mode}" requires approval, but the call has no agent to route it through`);
10398
+ const outcome = await approval.approver.request({
10399
+ agent: approval.agent,
10400
+ toolName: approval.toolName,
10401
+ callId: approval.callId,
10402
+ reason: `escalate sandbox to ${mode}: ${justification}`,
10403
+ ...approval.signal ? { signal: approval.signal } : {}
10404
+ });
10405
+ switch (outcome) {
10406
+ case "allowed-once": return mode;
10407
+ case "rejected": throw new Error(`the user rejected escalating this ${subject} to "${mode}"`);
10408
+ case "cancelled": throw new Error(`approval for escalating to "${mode}" was cancelled`);
10409
+ case "unavailable": throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval channel is available`);
10410
+ default: return assertNever(outcome, "EscalationOutcome");
10411
+ }
10412
+ }
10413
+ //#endregion
10414
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1.6-alpha.2_e8d664b8446198a3dac4043bd11091d7/node_modules/@deepseek-ai/dsh-tools/lib/index.js
10321
10415
  /**
10322
10416
  * Enforced JSON Schema subset shared by tool outputs, generated PTC mode
10323
10417
  * types, subagents, and workflows. The subset accepts any JSON root, an
@@ -11188,7 +11282,7 @@ const TYPESCRIPT_FLAVOR = {
11188
11282
  description: "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run.",
11189
11283
  codeDescription: "The program: the body of an async TypeScript function."
11190
11284
  };
11191
- /** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link CodeSdkLanguage}. */
11285
+ /** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link PtcSdkLanguage}. */
11192
11286
  const RUN_CODE_FLAVORS = {
11193
11287
  typescript: TYPESCRIPT_FLAVOR,
11194
11288
  python: {
@@ -11203,17 +11297,48 @@ const RUN_CODE_FLAVORS = {
11203
11297
  * can never drift.
11204
11298
  */
11205
11299
  const RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION = "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\".";
11300
+ const RUN_CODE_CONTROLS = {
11301
+ timeoutMs: {
11302
+ type: "number",
11303
+ description: "Positive elapsed-time budget in milliseconds, capped by the deployment maximum."
11304
+ },
11305
+ sandbox_permissions: {
11306
+ type: "string",
11307
+ enum: [...ESCALATION_TARGETS],
11308
+ description: "Wider sandbox mode for this complete program execution; requires justification and approval."
11309
+ },
11310
+ justification: {
11311
+ type: "string",
11312
+ description: "Reason this complete program needs wider access, shown to the user for approval."
11313
+ }
11314
+ };
11315
+ function controlParameters(runtime) {
11316
+ if (runtime === void 0) return RUN_CODE_CONTROLS;
11317
+ return {
11318
+ ...runtime.timeout === void 0 ? {} : { timeoutMs: {
11319
+ ...RUN_CODE_CONTROLS.timeoutMs,
11320
+ description: `Positive elapsed-time budget in milliseconds, including nested tool and approval waits. Default ${runtime.timeout.defaultMs}; capped at ${runtime.timeout.maxMs}. Zero does not disable the deadline.`
11321
+ } },
11322
+ ...runtime.sandboxMode === void 0 ? {} : {
11323
+ sandbox_permissions: RUN_CODE_CONTROLS.sandbox_permissions,
11324
+ justification: RUN_CODE_CONTROLS.justification
11325
+ }
11326
+ };
11327
+ }
11328
+ function escalationGuidance(runtime) {
11329
+ return runtime?.sandboxMode === void 0 ? "" : " A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.";
11330
+ }
11206
11331
  /**
11207
11332
  * Resolve the {@link RunCodeFlavor} for the loaded runtime's language, read at
11208
11333
  * schema-emission time so the model-visible `run_code` schema always matches
11209
11334
  * the SDK section's language. `peekRuntime` returns `undefined` only when no
11210
11335
  * runtime is mounted, which reaches this function through definition readers
11211
11336
  * and `schemas()` — the doc-catalog harvest is the only shipped one, and none
11212
- * of them feeds a model, because `wireSchemas` calls `requireCodeRuntime`
11337
+ * of them feeds a model, because `wireSchemas` calls `requirePtcRuntime`
11213
11338
  * before projecting — so that path degrades to {@link TYPESCRIPT_FLAVOR}. A
11214
11339
  * mounted runtime whose language has no flavor entry fails loud, exactly as
11215
- * `requireCodeRuntime` rejects it at assembly. Keeping this table in step with
11216
- * `SDK_RENDERERS` is the compiler's job ({@link CodeSdkLanguage}); what this
11340
+ * `requirePtcRuntime` rejects it at assembly. Keeping this table in step with
11341
+ * `SDK_RENDERERS` is the compiler's job ({@link PtcSdkLanguage}); what this
11217
11342
  * guard owns is the runtime-supplied language neither table knows, which never
11218
11343
  * yields a wrong-language schema for a real runtime.
11219
11344
  */
@@ -11382,7 +11507,8 @@ function createRunCodeTool(registry, options) {
11382
11507
  type: "string",
11383
11508
  required: true,
11384
11509
  description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
11385
- }
11510
+ },
11511
+ ...RUN_CODE_CONTROLS
11386
11512
  },
11387
11513
  output: {
11388
11514
  schema: {
@@ -11394,12 +11520,37 @@ function createRunCodeTool(registry, options) {
11394
11520
  required: true,
11395
11521
  items: { type: "string" }
11396
11522
  },
11397
- result: { type: "json" }
11523
+ result: { type: "json" },
11524
+ sandbox: {
11525
+ type: "object",
11526
+ additionalProperties: false,
11527
+ properties: {
11528
+ mode: {
11529
+ type: "string",
11530
+ required: true,
11531
+ enum: [
11532
+ "read-only",
11533
+ "workspace-write",
11534
+ "danger-full-access"
11535
+ ]
11536
+ },
11537
+ denied: {
11538
+ type: "boolean",
11539
+ required: true
11540
+ },
11541
+ enforcement: {
11542
+ type: "string",
11543
+ enum: ["full", "partial"]
11544
+ }
11545
+ }
11546
+ }
11398
11547
  }
11399
11548
  },
11400
11549
  render: (_args, value) => {
11401
11550
  const rendered = value.result === void 0 ? "" : renderValue(value.result);
11402
11551
  const parts = [value.logs.join("\n"), rendered].filter((part) => part.length > 0);
11552
+ if (value.sandbox?.enforcement === "partial") parts.push("File sandbox enforcement is partial on this host.");
11553
+ if (value.sandbox?.denied) parts.push(`The ${value.sandbox.mode} file sandbox denied an operation.${escalationGuidance(peekRuntime())}`);
11403
11554
  return [{
11404
11555
  type: "text",
11405
11556
  text: parts.length > 0 ? parts.join("\n") : "(run_code completed with no output)"
@@ -11409,6 +11560,31 @@ function createRunCodeTool(registry, options) {
11409
11560
  async execute(args, exec) {
11410
11561
  if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
11411
11562
  const runtime = requireRuntime();
11563
+ validateEscalationArgs(args.sandbox_permissions, args.justification);
11564
+ if (args.timeoutMs !== void 0 && runtime.timeout === void 0) throw new Error("timeoutMs is not available for this PTC runtime");
11565
+ if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error("invalid timeoutMs: expected a positive finite number");
11566
+ const standingPolicy = runtime.sandboxMode === void 0 ? void 0 : options.resolveSandboxPolicy(exec);
11567
+ let policy = standingPolicy;
11568
+ if (args.sandbox_permissions !== void 0 && args.justification !== void 0) {
11569
+ if (standingPolicy === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
11570
+ const approvedMode = await approveEscalation({
11571
+ requestedMode: args.sandbox_permissions,
11572
+ justification: args.justification,
11573
+ effectiveMode: standingPolicy.mode,
11574
+ subject: "program"
11575
+ }, {
11576
+ approver: options.peekApprover(),
11577
+ agent: exec.agent,
11578
+ callId: exec.callId,
11579
+ toolName: RUN_CODE_NAME,
11580
+ signal: exec.signal
11581
+ });
11582
+ policy = {
11583
+ ...standingPolicy,
11584
+ mode: approvedMode
11585
+ };
11586
+ }
11587
+ exec.signal.throwIfAborted();
11412
11588
  const runController = new AbortController();
11413
11589
  const onOuterAbort = () => {
11414
11590
  runController.abort(exec.signal.reason);
@@ -11490,7 +11666,8 @@ function createRunCodeTool(registry, options) {
11490
11666
  while (logWork.size > 0) await Promise.allSettled([...logWork]);
11491
11667
  };
11492
11668
  const runOver = () => runController.signal.aborted;
11493
- const binding = (name) => async (rawArgs) => {
11669
+ const binding = (schema) => async (rawArgs) => {
11670
+ const { name } = schema;
11494
11671
  if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
11495
11672
  const normalized = jsonNormalizeArgs(rawArgs);
11496
11673
  const n = ++dispatches;
@@ -11499,6 +11676,7 @@ function createRunCodeTool(registry, options) {
11499
11676
  callId: subCallId,
11500
11677
  rootCallId: exec.rootCallId,
11501
11678
  name,
11679
+ schema,
11502
11680
  arguments: normalized.dispatched,
11503
11681
  ...exec.agent ? { agent: exec.agent } : {},
11504
11682
  parent: exec.token,
@@ -11533,6 +11711,7 @@ function createRunCodeTool(registry, options) {
11533
11711
  name,
11534
11712
  arguments: normalized.logged,
11535
11713
  isError: result.isError,
11714
+ ...result.error?.info === void 0 ? {} : { error: result.error.info },
11536
11715
  content: logged
11537
11716
  });
11538
11717
  })().finally(() => {
@@ -11603,13 +11782,13 @@ function createRunCodeTool(registry, options) {
11603
11782
  if (schema.name === "run_code") continue;
11604
11783
  Object.defineProperty(functions, schema.name, {
11605
11784
  enumerable: true,
11606
- value: binding(schema.name)
11785
+ value: binding(deepFreeze(schema))
11607
11786
  });
11608
11787
  }
11609
11788
  try {
11610
11789
  let result;
11611
11790
  try {
11612
- result = await runtime.run({
11791
+ result = await runtime.run(runtime.resolve({
11613
11792
  program: args.code,
11614
11793
  bindings: [{
11615
11794
  global: "tools",
@@ -11619,18 +11798,23 @@ function createRunCodeTool(registry, options) {
11619
11798
  memberNameProperty: "toolName"
11620
11799
  }
11621
11800
  }],
11622
- signal: runController.signal
11623
- });
11801
+ signal: runController.signal,
11802
+ ...exec.agent?.session.header.cwd !== void 0 ? { cwd: exec.agent.session.header.cwd } : {},
11803
+ ...policy !== void 0 ? { sandboxPolicy: policy } : {},
11804
+ ...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
11805
+ }));
11624
11806
  } finally {
11625
11807
  runController.abort("run_code settled");
11626
11808
  await drainDispatches();
11627
11809
  }
11628
11810
  if (result.error) {
11629
11811
  const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
11630
- throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`);
11812
+ const sandboxText = result.sandbox === void 0 ? "" : `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === void 0 ? "" : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? "; operation denied" : ""}.`;
11813
+ throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
11631
11814
  }
11632
11815
  return {
11633
11816
  logs: result.logs,
11817
+ ...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
11634
11818
  ...result.value !== void 0 ? { result: result.value } : {}
11635
11819
  };
11636
11820
  } finally {
@@ -11646,7 +11830,11 @@ function createRunCodeTool(registry, options) {
11646
11830
  });
11647
11831
  Object.defineProperty(definition, "description", {
11648
11832
  enumerable: true,
11649
- get: () => resolveFlavor(peekRuntime).description
11833
+ get: () => {
11834
+ const runtime = peekRuntime();
11835
+ const instructions = runtime?.executionInstructions;
11836
+ return resolveFlavor(peekRuntime).description + (instructions ? ` ${instructions}` : "") + (runtime === void 0 ? "" : " The working directory is the Session's current directory.") + escalationGuidance(runtime);
11837
+ }
11650
11838
  });
11651
11839
  Object.defineProperty(definition, "parameters", {
11652
11840
  enumerable: true,
@@ -11660,7 +11848,8 @@ function createRunCodeTool(registry, options) {
11660
11848
  type: "string",
11661
11849
  required: true,
11662
11850
  description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
11663
- }
11851
+ },
11852
+ ...controlParameters(peekRuntime())
11664
11853
  })
11665
11854
  });
11666
11855
  return definition;
@@ -11945,7 +12134,7 @@ function renderToolsSdk(schemas) {
11945
12134
  * PTC mode codegen — Python flavor. The pure projection from registered tool schemas to the
11946
12135
  * Python SDK text the model programs against under `runtime.language === 'python'`. Sibling of
11947
12136
  * {@link ./ts-types.ts | ts-types.ts}; the two files are two projections of the same registry
11948
- * store, keyed by the loaded {@link @deepseek-ai/dsh-code-runtime#CodeRuntime.language | code
12137
+ * store, keyed by the loaded {@link @deepseek-ai/dsh-ptc-runtime#PtcRuntime.language | PTC
11949
12138
  * runtime's language}.
11950
12139
  *
11951
12140
  * Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
@@ -12632,17 +12821,17 @@ function renderToolsSdkPy(schemas) {
12632
12821
  */
12633
12822
  /**
12634
12823
  * Language → SDK-section renderer. The registry looks up the loaded
12635
- * `ctx.codeRuntime.language` in this table when assembling the `tools:sdk`
12824
+ * `ctx.ptcRuntime.language` in this table when assembling the `tools:sdk`
12636
12825
  * section under a non-native mode; a runtime whose language is not a key
12637
12826
  * fails the assembly loudly (same idiom as `toolOrder` violations). Adding a
12638
- * new backend language is three parallel edits — a {@link CodeSdkLanguage}
12827
+ * new backend language is three parallel edits — a {@link PtcSdkLanguage}
12639
12828
  * member, an entry here, and a `RUN_CODE_FLAVORS` entry in `ptc.ts` for
12640
12829
  * its `run_code` schema strings — plus the renderer function this table points
12641
12830
  * at. The `satisfies` clause pins this table's key set to that union, which
12642
12831
  * the flavor table is checked against too, so any of the three left out is a
12643
12832
  * typecheck failure. What no check reaches is the prose that names the values
12644
- * instead of deriving them: the seam's `dsh-code-runtime` README pair, its
12645
- * `CodeRuntime.language` JSDoc, and `docs/subsystems/code-runtime.md`
12833
+ * instead of deriving them: the seam's `dsh-ptc-runtime` README pair, its
12834
+ * `PtcRuntime.language` JSDoc, and `docs/subsystems/ptc-runtime.md`
12646
12835
  * with its zh pair, plus this package's own README pair and the
12647
12836
  * {@link Config.mode} JSDoc.
12648
12837
  */
@@ -12881,12 +13070,13 @@ var ToolRuntime = class extends Service {
12881
13070
  return {
12882
13071
  name: "tools:sdk",
12883
13072
  order: this.ctx.systemPrompt.getSectionOrder("TOOLS_SDK"),
13073
+ interpolate: false,
12884
13074
  text: (context) => {
12885
13075
  const mode = this.modeFor(context.scope);
12886
13076
  if (mode === "native") return "";
12887
- const runtime = this.requireCodeRuntime(mode);
13077
+ const runtime = this.requirePtcRuntime(mode);
12888
13078
  const render = SDK_RENDERERS[runtime.language];
12889
- /* v8 ignore next -- requireCodeRuntime rejects an unknown language before this runs. */
13079
+ /* v8 ignore next -- requirePtcRuntime rejects an unknown language before this runs. */
12890
13080
  if (render === void 0) throw new Error(`dsh-tools: no SDK renderer for ${runtime.language}`);
12891
13081
  return render(this.sdkSchemas(context.scope));
12892
13082
  }
@@ -12915,10 +13105,16 @@ var ToolRuntime = class extends Service {
12915
13105
  * and only for scopes whose mode actually presents it.
12916
13106
  * @returns the shared transport definition.
12917
13107
  */
12918
- requireCodeTransport() {
13108
+ requirePtcTransport() {
12919
13109
  this.ptcTransport ??= createRunCodeTool(this, {
12920
- requireRuntime: () => this.requireCodeRuntime(this.defaultMode),
12921
- peekRuntime: () => this.ctx.get("codeRuntime"),
13110
+ requireRuntime: () => this.requirePtcRuntime(this.defaultMode),
13111
+ peekApprover: () => this.ctx.get("approval"),
13112
+ resolveSandboxPolicy: (exec) => {
13113
+ const policy = this.ctx.get("sandboxPolicy");
13114
+ if (policy === void 0) throw new Error("dsh-tools: confined PTC runtime requires sandboxPolicy");
13115
+ return policy.resolve(exec.agent === void 0 ? {} : { session: exec.agent.session });
13116
+ },
13117
+ peekRuntime: () => this.ctx.get("ptcRuntime"),
12922
13118
  maxParallel: this.maxParallelSubCalls,
12923
13119
  shapeDispatchLog: (dispatch) => this.shapeDispatchLog(dispatch)
12924
13120
  });
@@ -12963,7 +13159,7 @@ var ToolRuntime = class extends Service {
12963
13159
  schemas: [...view.visible.values()].map((definition) => this.schemaOf(definition, false)),
12964
13160
  knownNames: [...view.knownNames]
12965
13161
  };
12966
- this.requireCodeRuntime(mode);
13162
+ this.requirePtcRuntime(mode);
12967
13163
  const schemas = [...view.visible.values()].map((definition) => this.schemaOf(definition, false));
12968
13164
  if (mode === "ptc") return {
12969
13165
  schemas: schemas.filter((schema) => schema.name === RUN_CODE_NAME),
@@ -12975,10 +13171,10 @@ var ToolRuntime = class extends Service {
12975
13171
  };
12976
13172
  }
12977
13173
  /**
12978
- * Resolve the code runtime or throw the actionable misconfiguration error.
13174
+ * Resolve the PTC runtime or throw the actionable misconfiguration error.
12979
13175
  * Read at use time (assembly / run_code execution), NOT via static
12980
13176
  * `inject`: an inject entry would hold `ctx.tools` — and every tool plugin
12981
- * behind it — hostage to a code runtime existing even under `mode:
13177
+ * behind it — hostage to a PTC runtime existing even under `mode:
12982
13178
  * 'native'`.
12983
13179
  *
12984
13180
  * Assembly and `run_code` execution read separately, so the language is not
@@ -12988,9 +13184,9 @@ var ToolRuntime = class extends Service {
12988
13184
  * other. Binding it is deferred until a second backend ships (the first
12989
13185
  * point it is testable).
12990
13186
  */
12991
- requireCodeRuntime(mode) {
12992
- const runtime = this.ctx.get("codeRuntime");
12993
- if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a code runtime — load a ctx.codeRuntime implementation (e.g. @deepseek-ai/dsh-code-runtime-worker-thread) or set tools mode to "native"`);
13187
+ requirePtcRuntime(mode) {
13188
+ const runtime = this.ctx.get("ptcRuntime");
13189
+ if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a PTC runtime — load a ctx.ptcRuntime implementation (e.g. @deepseek-ai/dsh-ptc-runtime-node) or set tools mode to "native"`);
12994
13190
  if (!Object.hasOwn(SDK_RENDERERS, runtime.language)) {
12995
13191
  const known = Object.keys(SDK_RENDERERS).map((name) => JSON.stringify(name)).join(", ");
12996
13192
  throw new Error(`dsh-tools: no SDK renderer registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`);
@@ -13104,7 +13300,7 @@ var ToolRuntime = class extends Service {
13104
13300
  knownNames.add(name);
13105
13301
  visible.set(name, definition);
13106
13302
  }
13107
- if (this.modeFor(scope) !== "native") visible.set(RUN_CODE_NAME, this.requireCodeTransport());
13303
+ if (this.modeFor(scope) !== "native") visible.set(RUN_CODE_NAME, this.requirePtcTransport());
13108
13304
  return {
13109
13305
  visible,
13110
13306
  knownNames,
@@ -13275,6 +13471,7 @@ var ToolRuntime = class extends Service {
13275
13471
  signal,
13276
13472
  ...agent !== void 0 ? { agent } : {},
13277
13473
  ...parent !== void 0 ? { parent } : {},
13474
+ ...exec.schema !== void 0 ? { schema: exec.schema } : {},
13278
13475
  deferContext(context) {
13279
13476
  deferredContexts.push(context);
13280
13477
  },
@@ -13357,7 +13554,13 @@ var ToolRuntime = class extends Service {
13357
13554
  exec,
13358
13555
  result: toolAbortedBeforeDispatchResult()
13359
13556
  });
13557
+ if (decision.kind === "cancel") return await next({
13558
+ kind: "post-result",
13559
+ exec,
13560
+ result: toolAbortedBeforeDispatchResult()
13561
+ });
13360
13562
  const denialReason = decision.kind === "allow" ? this.guardReason(exec) : decision.reason;
13563
+ const denialInfo = decision.kind === "deny" ? decision.info : void 0;
13361
13564
  if (denialReason !== void 0) return await next({
13362
13565
  kind: "post-result",
13363
13566
  exec,
@@ -13367,7 +13570,10 @@ var ToolRuntime = class extends Service {
13367
13570
  text: `Error: ${denialReason}`
13368
13571
  }],
13369
13572
  isError: true,
13370
- error: { message: denialReason }
13573
+ error: {
13574
+ message: denialReason,
13575
+ ...denialInfo === void 0 ? {} : { info: denialInfo }
13576
+ }
13371
13577
  })
13372
13578
  });
13373
13579
  if (this.callerCancelled(exec)) return await next({
@@ -13818,7 +14024,7 @@ function toolAbortedBeforeDispatchResult(prior) {
13818
14024
  };
13819
14025
  }
13820
14026
  //#endregion
13821
- //#region node_modules/.pnpm/@deepseek-ai+dsh-agent@0.1.5-alpha.2_cfc1690176e5c008b05d52ad11161b75/node_modules/@deepseek-ai/dsh-agent/lib/index.js
14027
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-agent@0.1.6-alpha.2_44110910133b3cc261ea9dfdcaed39ee/node_modules/@deepseek-ai/dsh-agent/lib/index.js
13822
14028
  /**
13823
14029
  * Agent-scoped model selection shared by runtime entry points.
13824
14030
  * @module @deepseek-ai/dsh-agent/model-selection
@@ -14057,8 +14263,10 @@ function registerCreate(env) {
14057
14263
  ...model !== "" ? { model } : {},
14058
14264
  ...reasoningEffort === void 0 ? {} : { reasoningEffort }
14059
14265
  },
14060
- setup: (agentCtx) => {
14061
- if (caller !== void 0) env.ctx.agentPresets.composeFrom(agentCtx, caller.ctx);
14266
+ setup: async (agentCtx) => {
14267
+ const requestedPreset = typeof args.agentPreset === "string" && args.agentPreset.trim() !== "" ? args.agentPreset.trim() : void 0;
14268
+ if (requestedPreset !== void 0) await env.ctx.agentPresets.mount(agentCtx, requestedPreset);
14269
+ else if (caller !== void 0) env.ctx.agentPresets.composeFrom(agentCtx, caller.ctx);
14062
14270
  if (installModelSelection$1 !== void 0) {
14063
14271
  installModelSelection$1(agentCtx, selection);
14064
14272
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-session-bridge",
3
- "version": "0.3.1",
3
+ "version": "0.3.2-alpha.2",
4
4
  "description": "会话桥(dsh-session-bridge):通过提示词创建新的主会话(顶层 UI 会话)、向任意会话发送消息、等待会话的下一条回复、读取会话消息,并按会话名或 id 跨工作区查找会话;此外支持监控/调度主任务与归档会话。",
5
5
  "private": false,
6
6
  "type": "module",
@@ -52,28 +52,28 @@
52
52
  },
53
53
  "license": "MIT",
54
54
  "peerDependencies": {
55
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
56
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
57
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
58
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
59
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
60
- "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
61
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
62
- "@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
55
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
56
+ "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
57
+ "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
58
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
59
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
60
+ "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
61
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
62
+ "@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
63
63
  "cordis": "^4.0.0-rc.7"
64
64
  },
65
65
  "devDependencies": {
66
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
67
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
68
- "@deepseek-ai/dsh-brand": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
69
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
70
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
71
- "@deepseek-ai/dsh-scope": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
72
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
73
- "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
74
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
75
- "@deepseek-ai/dsh-util-values": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
76
- "@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
66
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
67
+ "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
68
+ "@deepseek-ai/dsh-brand": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
69
+ "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
70
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
71
+ "@deepseek-ai/dsh-scope": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
72
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
73
+ "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
74
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
75
+ "@deepseek-ai/dsh-util-values": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
76
+ "@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0 || ^0.1.6-0",
77
77
  "@types/node": "^24.0.0",
78
78
  "cordis": "^4.0.0-rc.7",
79
79
  "tsdown": "^0.22.14",
@@ -82,6 +82,7 @@
82
82
  "scripts": {
83
83
  "build": "bash scripts/build.sh",
84
84
  "build:client": "tsdown",
85
+ "check:compat": "node scripts/check-dsh-compat.mjs",
85
86
  "typecheck": "tsc -p tsconfig.json --noEmit"
86
87
  }
87
88
  }
package/scripts/build.sh CHANGED
@@ -14,6 +14,24 @@ if [ -z "$CHECKOUT" ] || [ ! -d "$CHECKOUT/packages" ]; then
14
14
  exit 1
15
15
  fi
16
16
 
17
+ # The type-check below runs against $DSH_CHECKOUT, which commonly lags the dsh
18
+ # that actually loads this plugin — a stale checkout type-checks green while the
19
+ # running harness has already moved on. Warn loudly rather than implying support.
20
+ read_pkg_version() {
21
+ node -e "try{process.stdout.write(String(require(process.argv[1]).version))}catch(e){process.stdout.write('unknown')}" "$1" 2>/dev/null || echo unknown
22
+ }
23
+ CHECKOUT_VERSION="$(read_pkg_version "$CHECKOUT/package.json")"
24
+ INSTALLED_VERSION="unknown"
25
+ GLOBAL_ROOT="$(npm root -g 2>/dev/null | head -n1 || true)"
26
+ if [ -n "$GLOBAL_ROOT" ] && [ -f "$GLOBAL_ROOT/@deepseek-ai/dsh/package.json" ]; then
27
+ INSTALLED_VERSION="$(read_pkg_version "$GLOBAL_ROOT/@deepseek-ai/dsh/package.json")"
28
+ fi
29
+ if [ "$INSTALLED_VERSION" != "unknown" ] && [ "$CHECKOUT_VERSION" != "$INSTALLED_VERSION" ]; then
30
+ echo "build: WARNING — checkout is $CHECKOUT_VERSION but the installed dsh is $INSTALLED_VERSION." >&2
31
+ echo "build: the type-check below therefore does NOT cover the running harness;" >&2
32
+ echo "build: run 'npm run check:compat' to type-check against the installed dsh." >&2
33
+ fi
34
+
17
35
  TSC="$CHECKOUT/node_modules/.bin/tsc"
18
36
  if [ ! -x "$TSC" ]; then
19
37
  echo "build: tsc not found at $TSC" >&2
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Type-check src/ against the DSH packages that are ACTUALLY INSTALLED,
4
+ * rather than against $DSH_CHECKOUT.
5
+ *
6
+ * Why this exists: scripts/build.sh type-checks against a DSH *source* checkout.
7
+ * That checkout routinely lags the harness the plugin is loaded into (e.g. the
8
+ * checkout sat at 0.1.5-alpha.2 while the running harness was 0.1.6-alpha.1), so
9
+ * a green `build.sh` silently proves nothing about the running version. This
10
+ * script reads the `lib/types/*.d.ts` that ship inside the installed DSH npm
11
+ * package, i.e. the exact API surface the plugin loads against — so an upstream
12
+ * break shows up here even when the checkout is stale.
13
+ *
14
+ * Usage:
15
+ * node scripts/check-dsh-compat.mjs [--dsh <path>]
16
+ *
17
+ * <path> may be either the DSH package directory or the `@deepseek-ai` scope
18
+ * directory that holds dsh-session, dsh-agent, ... . When omitted, the installed
19
+ * location is probed (DSH_INSTALLED_MODULES, then `npm root -g`).
20
+ *
21
+ * Exits 0 when the plugin's src/ type-checks against those types, 1 otherwise.
22
+ */
23
+ import { execFileSync, execSync } from 'node:child_process'
24
+ import { existsSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
25
+ import { dirname, join, resolve } from 'node:path'
26
+ import { fileURLToPath } from 'node:url'
27
+
28
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
29
+
30
+ /** Read a JSON file, or return undefined when it is absent/unreadable. */
31
+ function readJson(file) {
32
+ try { return JSON.parse(readFileSync(file, 'utf8')) } catch { return undefined }
33
+ }
34
+
35
+ /** True when `dir` looks like the `@deepseek-ai` scope directory. */
36
+ function isScopeDir(dir) {
37
+ return dir !== undefined && existsSync(join(dir, 'dsh-session', 'package.json'))
38
+ }
39
+
40
+ /**
41
+ * Resolve the `@deepseek-ai` scope directory holding the installed DSH packages.
42
+ * Accepts an explicit path (package dir or scope dir) and otherwise probes.
43
+ */
44
+ function resolveScopeDir(argv) {
45
+ const flagAt = argv.indexOf('--dsh')
46
+ const explicit = flagAt !== -1 ? argv[flagAt + 1] : (process.env.DSH_INSTALLED_MODULES ?? undefined)
47
+ const candidates = []
48
+ if (explicit !== undefined && explicit !== '') {
49
+ const base = resolve(explicit)
50
+ // Accept the scope dir itself, the dsh package dir, or a node_modules tree.
51
+ candidates.push(base, join(base, 'node_modules', '@deepseek-ai'), join(base, '@deepseek-ai'))
52
+ }
53
+
54
+ // Anchor 1: the global install root. execSync (string command) rather than
55
+ // execFileSync, because npm is a .cmd shim on Windows that cannot be spawned
56
+ // without a shell.
57
+ let globalRoot
58
+ try {
59
+ globalRoot = execSync('npm root -g', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
60
+ } catch { globalRoot = undefined }
61
+ if (globalRoot !== undefined && globalRoot !== '') {
62
+ // npm layout, then the common "dsh nests its own deps" layout.
63
+ candidates.push(join(globalRoot, '@deepseek-ai'), join(globalRoot, '@deepseek-ai', 'dsh', 'node_modules', '@deepseek-ai'))
64
+ }
65
+
66
+ // Anchor 2: walk up from the `dsh` executable itself. Its path is usually a
67
+ // symlink (scoop/volta), so resolve it first or the walk misses the real tree.
68
+ try {
69
+ const found = execSync(process.platform === 'win32' ? 'where dsh' : 'which dsh', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
70
+ .split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== '')
71
+ for (const line of found) {
72
+ let dir
73
+ try { dir = dirname(realpathSync(line)) } catch { dir = dirname(line) }
74
+ for (let hop = 0; hop < 4 && dir !== dirname(dir); hop++) {
75
+ candidates.push(join(dir, 'node_modules', '@deepseek-ai', 'dsh', 'node_modules', '@deepseek-ai'))
76
+ candidates.push(join(dir, 'node_modules', '@deepseek-ai'))
77
+ dir = dirname(dir)
78
+ }
79
+ }
80
+ } catch { /* dsh not on PATH; the other anchors decide */ }
81
+
82
+ for (const candidate of candidates) if (isScopeDir(candidate)) return candidate
83
+ return undefined
84
+ }
85
+
86
+ /** Resolve a package's types entry point inside the scope directory. */
87
+ function typesEntry(scope, pkg) {
88
+ const dir = join(scope, pkg)
89
+ const manifest = readJson(join(dir, 'package.json'))
90
+ if (manifest === undefined) return undefined
91
+ const declared = typeof manifest.types === 'string' ? manifest.types : undefined
92
+ const candidates = [
93
+ ...(declared !== undefined ? [join(dir, declared)] : []),
94
+ join(dir, 'lib', 'types', 'index.d.ts'),
95
+ join(dir, 'index.d.ts'),
96
+ ]
97
+ return candidates.find((c) => existsSync(c))
98
+ }
99
+
100
+ function fail(message) {
101
+ console.error('check-dsh-compat: ' + message)
102
+ process.exit(1)
103
+ }
104
+
105
+ const scope = resolveScopeDir(process.argv.slice(2))
106
+ if (scope === undefined) {
107
+ fail('could not locate the installed DSH packages. Pass --dsh <path> (the DSH package dir or its @deepseek-ai scope dir) or set DSH_INSTALLED_MODULES.')
108
+ }
109
+
110
+ /** Read the version of the `dsh` package that owns this scope directory. */
111
+ function dshVersionOf(scopeDir) {
112
+ // Nested layout puts dsh two levels above its own scope dir; a flat global
113
+ // install puts it beside the other packages.
114
+ for (const candidate of [join(scopeDir, 'dsh', 'package.json'), join(scopeDir, '..', '..', 'package.json')]) {
115
+ const manifest = readJson(candidate)
116
+ if (manifest?.name === '@deepseek-ai/dsh' && typeof manifest.version === 'string') return manifest.version
117
+ }
118
+ return undefined
119
+ }
120
+
121
+ /**
122
+ * Inspect the built tsdown bundle's source map for the DSH version it INLINED.
123
+ *
124
+ * `check:compat` type-checks src/ against the installed harness, but src/ is not
125
+ * what loads: lib/index.js is a self-contained bundle that inlines every
126
+ * @deepseek-ai/* import. A stale checkout therefore type-checks green while the
127
+ * artifact ships old DSH code (observed: checkout 0.1.5-alpha.2 / host
128
+ * 0.1.6-alpha.2). This reads the map's sources — a checkout path names the root
129
+ * whose package.json carries the version; a .pnpm path encodes it.
130
+ *
131
+ * @returns { versions: Set<string>, roots: Set<string> } or undefined when no map exists.
132
+ */
133
+ function artifactProvenance() {
134
+ const mapPath = join(ROOT, 'lib', 'index.js.map')
135
+ if (!existsSync(mapPath)) return undefined
136
+ let map
137
+ try { map = JSON.parse(readFileSync(mapPath, 'utf8')) } catch { return undefined }
138
+ const versions = new Set()
139
+ const roots = new Set()
140
+ for (const source of Array.isArray(map.sources) ? map.sources : []) {
141
+ if (typeof source !== 'string') continue
142
+ // Source checkout: <root>/packages/... or <root>/vendor/... (relative to the map in lib/).
143
+ const checkout = source.match(/^(.*?)[/\\](?:packages|vendor)[/\\]/)
144
+ if (checkout !== null) { roots.add(resolve(ROOT, 'lib', checkout[1])); continue }
145
+ // Registry install: .../.pnpm/@deepseek-ai+<name>@<version>[_hash]/node_modules/...
146
+ const registry = source.match(/[\\/]\.pnpm[\\/]@deepseek-ai\+[a-z0-9-]+@([^\\/]+)[\\/]node_modules/)
147
+ if (registry !== null) {
148
+ const version = registry[1].replace(/_.*$/, '')
149
+ if (/^\d/.test(version)) versions.add(version)
150
+ }
151
+ }
152
+ for (const root of roots) {
153
+ const manifest = readJson(join(root, 'package.json'))
154
+ if (typeof manifest?.version === 'string') versions.add(manifest.version)
155
+ }
156
+ return { versions, roots }
157
+ }
158
+
159
+ const dshVersion = dshVersionOf(scope) ?? '(unknown)'
160
+
161
+ // Every DSH package src/ imports, plus the plain `cordis` specifier that
162
+ // package.json declares as a peer.
163
+ const manifest = readJson(join(ROOT, 'package.json')) ?? {}
164
+ const declared = [...Object.keys(manifest.peerDependencies ?? {}), ...Object.keys(manifest.devDependencies ?? {})]
165
+ const wanted = new Set(declared.filter((name) => name.startsWith('@deepseek-ai/')))
166
+ wanted.add('@deepseek-ai/cordis')
167
+ wanted.add('@deepseek-ai/schemastery')
168
+
169
+ const paths = {}
170
+ const missing = []
171
+ for (const name of [...wanted].sort()) {
172
+ const entry = typesEntry(scope, name.slice('@deepseek-ai/'.length))
173
+ if (entry === undefined) { missing.push(name); continue }
174
+ paths[name] = [entry]
175
+ }
176
+ // tsconfig.json maps the bare `cordis` specifier; keep that working under the
177
+ // installed layout too, where cordis is published scoped.
178
+ if (paths['@deepseek-ai/cordis'] !== undefined) paths['cordis'] = paths['@deepseek-ai/cordis']
179
+
180
+ if (missing.length > 0) {
181
+ fail('installed DSH is missing the packages this plugin imports: ' + missing.join(', ') + '\n (looked in ' + scope + ')')
182
+ }
183
+
184
+ // The generated config must live in the repo root: tsconfig.json's `include`
185
+ // is relative to the config that declares it, so a config placed elsewhere
186
+ // would look for src/ in the wrong directory. Every mapped path is absolute,
187
+ // so only `include`/`extends` depend on the location. Removed again below.
188
+ const configPath = join(ROOT, '.dsh-compat.tsconfig.json')
189
+ writeFileSync(configPath, JSON.stringify({ extends: './tsconfig.json', compilerOptions: { paths } }, null, 2))
190
+
191
+ // Run tsc's JS entry through the current node binary. Spawning the .bin shell
192
+ // shim instead would need a shell on Windows (npm .cmd/.bat shims cannot be
193
+ // spawned directly), and this way the exit code and output stay unambiguous.
194
+ const tsc = join(ROOT, 'node_modules', 'typescript', 'bin', 'tsc')
195
+ if (!existsSync(tsc)) { rmSync(configPath, { force: true }); fail('local typescript not found at ' + tsc + ' (run the package manager install first)') }
196
+
197
+ console.log('check-dsh-compat: type-checking src/ against installed dsh ' + dshVersion)
198
+ console.log('check-dsh-compat: scope ' + scope)
199
+ let failure
200
+ try {
201
+ execFileSync(process.execPath, [tsc, '-p', configPath, '--noEmit'], { cwd: ROOT, stdio: 'inherit' })
202
+ } catch (error) {
203
+ failure = error
204
+ } finally {
205
+ rmSync(configPath, { force: true })
206
+ }
207
+ if (failure !== undefined) {
208
+ const status = typeof failure.status === 'number' ? ' (tsc exit ' + failure.status + ')' : ''
209
+ fail('src/ does NOT type-check against installed dsh ' + dshVersion + status + ' — this is a real incompatibility with the running harness.')
210
+ }
211
+ console.log('check-dsh-compat: OK — src/ type-checks against installed dsh ' + dshVersion)
212
+
213
+ const provenance = artifactProvenance()
214
+ if (provenance === undefined) {
215
+ console.log('check-dsh-compat: no lib/index.js.map — skipping bundle provenance check (run pnpm build:client to enable it)')
216
+ } else if (provenance.versions.size === 0) {
217
+ console.log('check-dsh-compat: bundle provenance not resolvable from the source map — skipped')
218
+ } else {
219
+ const inlined = [...provenance.versions].sort().join(', ')
220
+ console.log('check-dsh-compat: lib/index.js inlined DSH ' + inlined + ' (from ' + provenance.roots.size + ' checkout(s) / registry entries)')
221
+ if (dshVersion !== '(unknown)' && !(provenance.versions.size === 1 && provenance.versions.has(dshVersion))) {
222
+ fail('the built lib/index.js inlines DSH ' + inlined + ' but the installed harness is ' + dshVersion
223
+ + ' — rebuild the bundle against the running harness (a stale checkout type-checks green while shipping old DSH code).')
224
+ }
225
+ }
package/src/core.ts CHANGED
@@ -64,11 +64,19 @@ export interface BridgeFindItem {
64
64
 
65
65
  /**
66
66
  * 兼容读取不同 dsh-session 版本上的会话事件:
67
- * - 旧版 `Session` 暴露 `get events(): readonly SessionEvent[]`;
68
- * - 新版(如宿主实际运行的 0.1.2-rc.x)将 `get events` 改为
69
- * `snapshotEvents(fromSeq?, toSeqExclusive?)` 方法——`.events` 直接读取会
70
- * 得到 `undefined`,对 for...of 迭代即抛 `events is not iterable`。
67
+ * - 旧版 \`Session\` 暴露 \`get events(): readonly SessionEvent[]\`;
68
+ * - 新版将 \`get events\` 改为 \`snapshotEvents(fromSeq?, toSeqExclusive?)\` 方法——
69
+ * \`.events\` 直接读取会得到 \`undefined\`,对 for...of 迭代即抛
70
+ * \`events is not iterable\`。
71
71
  * 两者都读不到(或 session 不存在)时回退为空数组,绝不抛迭代错误。
72
+ *
73
+ * 注意:dsh 0.1.6-alpha.1 起 \`snapshotEvents()\`(连同 \`eventAt()\` /
74
+ * \`ownEvents()\`)已标记 @deprecated —— 官方策略是"现有逻辑可暂不迁移,但禁止
75
+ * 新增调用";其替代不是同步读,而是(a)恢复后读取 Session 投影/派生状态,或
76
+ * (b)按需异步分页读取历史窗口(见 dsh 决策
77
+ * 2026-09-09-deprecate-synchronous-session-event-reads)。本函数的调用方
78
+ * (foldMessages / segmentsSince / wait 基线)仍依赖完整同步快照,故按该决定暂缓
79
+ * 迁移;将来官方补齐分页读后,只需改造这一个入口。
72
80
  */
73
81
  export function sessionEvents(session: unknown): readonly SessionEvent[] {
74
82
  const s = session as {
package/src/tools.ts CHANGED
@@ -194,8 +194,16 @@ function registerCreate(env: BridgeEnv): void {
194
194
  ...(model !== '' ? { model } : {}),
195
195
  ...(reasoningEffort === undefined ? {} : { reasoningEffort }),
196
196
  },
197
- setup: (agentCtx: Context) => {
198
- if (caller !== undefined) {
197
+ setup: async (agentCtx: Context) => {
198
+ const requestedPreset = typeof args.agentPreset === 'string' && args.agentPreset.trim() !== '' ? args.agentPreset.trim() : undefined
199
+ if (requestedPreset !== undefined) {
200
+ // An explicit agentPreset composes THAT preset. Falling through to the
201
+ // caller-join below would silently ignore the request: the id would
202
+ // still reach the session header, while the agent kept running on the
203
+ // caller's standing composition and its tools.
204
+ const presets = env.ctx.agentPresets as unknown as { mount(agentCtx: Context, id: string): Promise<unknown> }
205
+ await presets.mount(agentCtx, requestedPreset)
206
+ } else if (caller !== undefined) {
199
207
  const presets = env.ctx.agentPresets as unknown as { composeFrom(agentCtx: Context, parentCtx: Context): string | undefined }
200
208
  presets.composeFrom(agentCtx, caller.ctx)
201
209
  }