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 +34 -1
- package/README.zh.md +27 -0
- package/dsh.plugin.json +2 -2
- package/lib/index.js +262 -54
- package/package.json +21 -20
- package/scripts/build.sh +18 -0
- package/scripts/check-dsh-compat.mjs +225 -0
- package/src/core.ts +12 -4
- package/src/tools.ts +10 -2
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
* - 旧版
|
|
9215
|
-
* -
|
|
9216
|
-
*
|
|
9217
|
-
*
|
|
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.
|
|
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-
|
|
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
|
|
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 `
|
|
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
|
-
* `
|
|
11216
|
-
* `SDK_RENDERERS` is the compiler's job ({@link
|
|
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 = (
|
|
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
|
|
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
|
-
|
|
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: () =>
|
|
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-
|
|
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.
|
|
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
|
|
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-
|
|
12645
|
-
* `
|
|
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.
|
|
13077
|
+
const runtime = this.requirePtcRuntime(mode);
|
|
12888
13078
|
const render = SDK_RENDERERS[runtime.language];
|
|
12889
|
-
/* v8 ignore next --
|
|
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
|
-
|
|
13108
|
+
requirePtcTransport() {
|
|
12919
13109
|
this.ptcTransport ??= createRunCodeTool(this, {
|
|
12920
|
-
requireRuntime: () => this.
|
|
12921
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
12992
|
-
const runtime = this.ctx.get("
|
|
12993
|
-
if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a
|
|
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.
|
|
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: {
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
* - 旧版
|
|
68
|
-
* -
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
-
|
|
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
|
}
|