pi-web-ui 0.63.0 → 0.63.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.
@@ -1,164 +1,164 @@
1
- #!/usr/bin/env node
2
- /**
3
- * pi-web-ui DSH runtime launcher (DshEngine的运行时子进程).
4
- *
5
- * 组合 = 全局/本地 dsh 运行时树里的 dsh-base bundle patch + 本文件同目录的
6
- * override patch(挂 stdio JSON-RPC 服务插件 + pin 会话持久化/人设/沙箱)。
7
- * 与官方 dsh CLI 的 profile 机制无关 —— 直接以 boot() 组合静态入口,绕开
8
- * $DSH_HOME/profiles 初始化。
9
- *
10
- * 运行时树解析顺序(实现见 runtime-root.mjs,支持 flat / dsh 嵌套两种布局):
11
- * 1. $PI_WEB_DSH_RUNTIME — 显式指定 node_modules 根(含 @deepseek-ai/dsh-base)
12
- * 2. 本包 node_modules — 若 pi-web-ui 完整安装了 dsh 依赖树
13
- * 3. execPath 邻近 node_modules — fnm / 独立 node 的稳定布局
14
- * 4. `npm root -g` — 全局 dsh 安装(`npm i -g @deepseek-ai/dsh`)
15
- *
16
- * JSON-RPC 服务插件(dsh-sdk-jsonrpc-server)按项目依赖解析:override patch
17
- * 里的 name 由 $PI_WEB_DSH_JSONRPC_ENTRY 用 !!js 求值成绝对路径(指向项目
18
- * node_modules),其 peer 依赖从项目树自洽解析 —— cordis loader 的裸包解析
19
- * 只有单一 base,所以混树只能走绝对路径挂载。
20
- *
21
- * 协议:newline-delimited JSON-RPC 2.0 于 stdio(stdout 只承载协议帧)。
22
- */
23
- import { dirname, join, resolve } from "node:path";
24
- import { readdirSync } from "node:fs";
25
- import { fileURLToPath, pathToFileURL } from "node:url";
26
- import { resolveRuntimeBase } from "./runtime-root.mjs";
27
-
28
- const HERE = dirname(fileURLToPath(import.meta.url));
29
- const BIN_NAME = "pi-web-ui-dsh";
30
-
31
- const runtimeBase = await resolveRuntimeBase();
32
- if (!runtimeBase) {
33
- console.error(
34
- `[${BIN_NAME}] 找不到 DSH 运行时树(含 @deepseek-ai/dsh-base 的 node_modules)。` +
35
- "请先执行 npm i -g @deepseek-ai/dsh(或设置 PI_WEB_DSH_RUNTIME 指向其 node_modules)。",
36
- );
37
- process.exit(1);
38
- }
39
-
40
- const runConfig = process.env.DSH_CORDIS_CONFIG ?? join(HERE, "cordis.yml");
41
- const baseBundlePatch = join(runtimeBase, "@deepseek-ai", "dsh-base", "cordis.patch.yml");
42
- const overridePatch = join(HERE, "override.patch.yml");
43
-
44
- // 用户 patch 层:<dataDir>/dsh-patches/*.yml(按文件名序,在 override 之后)。
45
- // 引擎(DshClientSession)负责在重启运行时前创建目录;launcher 只负责加载。
46
- const userPatchDir = process.env.PI_WEB_DSH_DATA_DIR
47
- ? join(process.env.PI_WEB_DSH_DATA_DIR, "dsh-patches")
48
- : (process.env.PI_WEB_DSH_PATCH_DIR ?? null);
49
- const userPatchFiles = [];
50
- if (userPatchDir) {
51
- try {
52
- for (const name of readdirSync(userPatchDir)) {
53
- if (/^[^.]/u.test(name) && /\.ya?ml$/iu.test(name)) {
54
- userPatchFiles.push(join(userPatchDir, name));
55
- }
56
- }
57
- } catch {
58
- // 目录不存在/不可读 = 无用户 patch,静默跳过。
59
- }
60
- }
61
- userPatchFiles.sort((a, b) => a.localeCompare(b));
62
-
63
- // ---------------------------------------------------------------------------
64
- // boot
65
- // ---------------------------------------------------------------------------
66
-
67
- const { boot, installFailLoud, loadOverlayPatches } = await import(
68
- pathToFileURL(join(runtimeBase, "@deepseek-ai", "dsh-app-boot", "lib", "index.js")).href
69
- );
70
-
71
- // cordis loader 只对 entry 的 config 做 !!js 插值,name 字段不支持。
72
- // sdk-jsonrpc 走本地扩展插件 goal-rpc.mjs(官方 server 类 + goal RPC 方法);
73
- // 官方入口(base 类)通过 env PI_WEB_DSH_JSONRPC_ENTRY 传给 wrapper。
74
- const jsonrpcWrapper = join(HERE, "goal-rpc.mjs");
75
- const _jsonrpcEntry = process.env.PI_WEB_DSH_JSONRPC_ENTRY
76
- ? resolve(process.env.PI_WEB_DSH_JSONRPC_ENTRY)
77
- : join(
78
- resolve(HERE, "..", "..", "..", ".."),
79
- "node_modules",
80
- "@deepseek-ai",
81
- "dsh-sdk-jsonrpc-server",
82
- "lib",
83
- "index.js",
84
- );
85
-
86
- const overrideList = loadOverlayPatches(BIN_NAME, overridePatch);
87
- // cordis loader 只对 entry 的 config 做 !!js 插值,name 字段不支持——
88
- // 插件绝对路径在这里用 JS 写回(sdk-jsonrpc → 本地 goal-rpc wrapper;
89
- // 用户 patch 若用 id 覆盖 sdk-jsonrpc 或 insert 同名 entry 也拿到 wrapper)。
90
- const fixJsonrpcName = (patch) => {
91
- if (patch.id === "sdk-jsonrpc") {
92
- patch.name = jsonrpcWrapper;
93
- }
94
- for (const entry of patch.insert ?? []) {
95
- if (entry.id === "sdk-jsonrpc") entry.name = jsonrpcWrapper;
96
- }
97
- };
98
- for (const patch of overrideList) fixJsonrpcName(patch);
99
- const userPatchLists = userPatchFiles.map((file) => {
100
- try {
101
- return loadOverlayPatches(BIN_NAME, file);
102
- } catch (err) {
103
- process.stderr.write(`[${BIN_NAME}] 跳过用户 patch ${file}: ${err?.message ?? String(err)}\n`);
104
- return [];
105
- }
106
- });
107
- for (const list of userPatchLists) for (const patch of list) fixJsonrpcName(patch);
108
- // patches 参数必须是扁平列表(boot → mountRootInclude → Include.applyPatches →
109
- // applyEntryPatches 逐个消费;数组嵌套会被当无 id 的 patch 跳过)。
110
- const patches = [
111
- ...loadOverlayPatches(BIN_NAME, baseBundlePatch),
112
- ...overrideList,
113
- // 用户 patch:同样按文件展开为扁平 patch entry 列表(一个文件可含多 entry)。
114
- ...userPatchLists.flat(),
115
- ];
116
-
117
- let ctx;
118
- try {
119
- ctx = await boot(
120
- BIN_NAME,
121
- runConfig,
122
- patches,
123
- undefined,
124
- // 裸包名(dsh-base 组合行)锚定到运行时树;jsonrpc 行走绝对路径。
125
- pathToFileURL(runtimeBase + "/").href,
126
- );
127
- } catch (err) {
128
- const seen = new Set();
129
- const walk = (e, depth) => {
130
- if (!e || seen.has(e)) return;
131
- seen.add(e);
132
- const msg = e?.message ?? String(e);
133
- const indent = " " + " ".repeat(Math.min(depth, 6));
134
- process.stderr.write(`${indent}${msg}\n`);
135
- const kids = e?.aggregateErrors ?? e?.errors ?? (e?.cause ? [e.cause] : []);
136
- for (const k of kids) walk(k, depth + 1);
137
- };
138
- process.stderr.write(`[${BIN_NAME}] boot 失败: ${err?.message ?? String(err)}\n runtime base: ${runtimeBase}\n`);
139
- walk(err, 0);
140
- process.exit(1);
141
- }
142
-
143
- let releasing = false;
144
- const release = () => {
145
- if (releasing) return;
146
- releasing = true;
147
- void (async () => {
148
- try {
149
- await ctx.fiber.dispose();
150
- } catch (err) {
151
- console.error(`[${BIN_NAME}] teardown 错误: ${err?.message ?? String(err)}`);
152
- }
153
- process.exit(0);
154
- })();
155
- return true;
156
- };
157
- installFailLoud(BIN_NAME, process, release);
158
-
159
- // stdin EOF = 客户端消失 → 有序释放后退出。
160
- process.stdin.resume();
161
- process.stdin.on("end", release);
162
- process.stdin.on("close", release);
163
- process.on("SIGTERM", release);
164
- process.on("SIGINT", () => process.exit(130));
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pi-web-ui DSH runtime launcher (DshEngine的运行时子进程).
4
+ *
5
+ * 组合 = 全局/本地 dsh 运行时树里的 dsh-base bundle patch + 本文件同目录的
6
+ * override patch(挂 stdio JSON-RPC 服务插件 + pin 会话持久化/人设/沙箱)。
7
+ * 与官方 dsh CLI 的 profile 机制无关 —— 直接以 boot() 组合静态入口,绕开
8
+ * $DSH_HOME/profiles 初始化。
9
+ *
10
+ * 运行时树解析顺序(实现见 runtime-root.mjs,支持 flat / dsh 嵌套两种布局):
11
+ * 1. $PI_WEB_DSH_RUNTIME — 显式指定 node_modules 根(含 @deepseek-ai/dsh-base)
12
+ * 2. 本包 node_modules — 若 pi-web-ui 完整安装了 dsh 依赖树
13
+ * 3. execPath 邻近 node_modules — fnm / 独立 node 的稳定布局
14
+ * 4. `npm root -g` — 全局 dsh 安装(`npm i -g @deepseek-ai/dsh`)
15
+ *
16
+ * JSON-RPC 服务插件(dsh-sdk-jsonrpc-server)按项目依赖解析:override patch
17
+ * 里的 name 由 $PI_WEB_DSH_JSONRPC_ENTRY 用 !!js 求值成绝对路径(指向项目
18
+ * node_modules),其 peer 依赖从项目树自洽解析 —— cordis loader 的裸包解析
19
+ * 只有单一 base,所以混树只能走绝对路径挂载。
20
+ *
21
+ * 协议:newline-delimited JSON-RPC 2.0 于 stdio(stdout 只承载协议帧)。
22
+ */
23
+ import { dirname, join, resolve } from "node:path";
24
+ import { readdirSync } from "node:fs";
25
+ import { fileURLToPath, pathToFileURL } from "node:url";
26
+ import { resolveRuntimeBase } from "./runtime-root.mjs";
27
+
28
+ const HERE = dirname(fileURLToPath(import.meta.url));
29
+ const BIN_NAME = "pi-web-ui-dsh";
30
+
31
+ const runtimeBase = await resolveRuntimeBase();
32
+ if (!runtimeBase) {
33
+ console.error(
34
+ `[${BIN_NAME}] 找不到 DSH 运行时树(含 @deepseek-ai/dsh-base 的 node_modules)。` +
35
+ "请先执行 npm i -g @deepseek-ai/dsh(或设置 PI_WEB_DSH_RUNTIME 指向其 node_modules)。",
36
+ );
37
+ process.exit(1);
38
+ }
39
+
40
+ const runConfig = process.env.DSH_CORDIS_CONFIG ?? join(HERE, "cordis.yml");
41
+ const baseBundlePatch = join(runtimeBase, "@deepseek-ai", "dsh-base", "cordis.patch.yml");
42
+ const overridePatch = join(HERE, "override.patch.yml");
43
+
44
+ // 用户 patch 层:<dataDir>/dsh-patches/*.yml(按文件名序,在 override 之后)。
45
+ // 引擎(DshClientSession)负责在重启运行时前创建目录;launcher 只负责加载。
46
+ const userPatchDir = process.env.PI_WEB_DSH_DATA_DIR
47
+ ? join(process.env.PI_WEB_DSH_DATA_DIR, "dsh-patches")
48
+ : (process.env.PI_WEB_DSH_PATCH_DIR ?? null);
49
+ const userPatchFiles = [];
50
+ if (userPatchDir) {
51
+ try {
52
+ for (const name of readdirSync(userPatchDir)) {
53
+ if (/^[^.]/u.test(name) && /\.ya?ml$/iu.test(name)) {
54
+ userPatchFiles.push(join(userPatchDir, name));
55
+ }
56
+ }
57
+ } catch {
58
+ // 目录不存在/不可读 = 无用户 patch,静默跳过。
59
+ }
60
+ }
61
+ userPatchFiles.sort((a, b) => a.localeCompare(b));
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // boot
65
+ // ---------------------------------------------------------------------------
66
+
67
+ const { boot, installFailLoud, loadOverlayPatches } = await import(
68
+ pathToFileURL(join(runtimeBase, "@deepseek-ai", "dsh-app-boot", "lib", "index.js")).href
69
+ );
70
+
71
+ // cordis loader 只对 entry 的 config 做 !!js 插值,name 字段不支持。
72
+ // sdk-jsonrpc 走本地扩展插件 goal-rpc.mjs(官方 server 类 + goal RPC 方法);
73
+ // 官方入口(base 类)通过 env PI_WEB_DSH_JSONRPC_ENTRY 传给 wrapper。
74
+ const jsonrpcWrapper = join(HERE, "goal-rpc.mjs");
75
+ const _jsonrpcEntry = process.env.PI_WEB_DSH_JSONRPC_ENTRY
76
+ ? resolve(process.env.PI_WEB_DSH_JSONRPC_ENTRY)
77
+ : join(
78
+ resolve(HERE, "..", "..", "..", ".."),
79
+ "node_modules",
80
+ "@deepseek-ai",
81
+ "dsh-sdk-jsonrpc-server",
82
+ "lib",
83
+ "index.js",
84
+ );
85
+
86
+ const overrideList = loadOverlayPatches(BIN_NAME, overridePatch);
87
+ // cordis loader 只对 entry 的 config 做 !!js 插值,name 字段不支持——
88
+ // 插件绝对路径在这里用 JS 写回(sdk-jsonrpc → 本地 goal-rpc wrapper;
89
+ // 用户 patch 若用 id 覆盖 sdk-jsonrpc 或 insert 同名 entry 也拿到 wrapper)。
90
+ const fixJsonrpcName = (patch) => {
91
+ if (patch.id === "sdk-jsonrpc") {
92
+ patch.name = jsonrpcWrapper;
93
+ }
94
+ for (const entry of patch.insert ?? []) {
95
+ if (entry.id === "sdk-jsonrpc") entry.name = jsonrpcWrapper;
96
+ }
97
+ };
98
+ for (const patch of overrideList) fixJsonrpcName(patch);
99
+ const userPatchLists = userPatchFiles.map((file) => {
100
+ try {
101
+ return loadOverlayPatches(BIN_NAME, file);
102
+ } catch (err) {
103
+ process.stderr.write(`[${BIN_NAME}] 跳过用户 patch ${file}: ${err?.message ?? String(err)}\n`);
104
+ return [];
105
+ }
106
+ });
107
+ for (const list of userPatchLists) for (const patch of list) fixJsonrpcName(patch);
108
+ // patches 参数必须是扁平列表(boot → mountRootInclude → Include.applyPatches →
109
+ // applyEntryPatches 逐个消费;数组嵌套会被当无 id 的 patch 跳过)。
110
+ const patches = [
111
+ ...loadOverlayPatches(BIN_NAME, baseBundlePatch),
112
+ ...overrideList,
113
+ // 用户 patch:同样按文件展开为扁平 patch entry 列表(一个文件可含多 entry)。
114
+ ...userPatchLists.flat(),
115
+ ];
116
+
117
+ let ctx;
118
+ try {
119
+ ctx = await boot(
120
+ BIN_NAME,
121
+ runConfig,
122
+ patches,
123
+ undefined,
124
+ // 裸包名(dsh-base 组合行)锚定到运行时树;jsonrpc 行走绝对路径。
125
+ pathToFileURL(runtimeBase + "/").href,
126
+ );
127
+ } catch (err) {
128
+ const seen = new Set();
129
+ const walk = (e, depth) => {
130
+ if (!e || seen.has(e)) return;
131
+ seen.add(e);
132
+ const msg = e?.message ?? String(e);
133
+ const indent = " " + " ".repeat(Math.min(depth, 6));
134
+ process.stderr.write(`${indent}${msg}\n`);
135
+ const kids = e?.aggregateErrors ?? e?.errors ?? (e?.cause ? [e.cause] : []);
136
+ for (const k of kids) walk(k, depth + 1);
137
+ };
138
+ process.stderr.write(`[${BIN_NAME}] boot 失败: ${err?.message ?? String(err)}\n runtime base: ${runtimeBase}\n`);
139
+ walk(err, 0);
140
+ process.exit(1);
141
+ }
142
+
143
+ let releasing = false;
144
+ const release = () => {
145
+ if (releasing) return;
146
+ releasing = true;
147
+ void (async () => {
148
+ try {
149
+ await ctx.fiber.dispose();
150
+ } catch (err) {
151
+ console.error(`[${BIN_NAME}] teardown 错误: ${err?.message ?? String(err)}`);
152
+ }
153
+ process.exit(0);
154
+ })();
155
+ return true;
156
+ };
157
+ installFailLoud(BIN_NAME, process, release);
158
+
159
+ // stdin EOF = 客户端消失 → 有序释放后退出。
160
+ process.stdin.resume();
161
+ process.stdin.on("end", release);
162
+ process.stdin.on("close", release);
163
+ process.on("SIGTERM", release);
164
+ process.on("SIGINT", () => process.exit(130));
@@ -1,71 +1,71 @@
1
- # pi-web-ui DSH runtime overrides — applies AFTER the dsh-base bundle patch
2
- # (loaded from the runtime tree). Rows that restate a base row use id-targeted
3
- # REPLACE (last write wins per row); new rows come in a plain insert block.
4
- # !!js 表达式由 Cordis Loader 在挂载时求值(boot 暴露 dshHomePath,process.env
5
- # 直读)。
6
-
7
- # 会话持久化根 —— 覆盖 base 的 dshHomePath('sessions'),让 pi-web-ui 指向自己的数据目录。
8
- - id: session-persistence-jsonl
9
- name: "@deepseek-ai/dsh-session-persistence-jsonl"
10
- config:
11
- root: !!js process.env.DSH_SESSION_ROOT ?? dshHomePath('sessions')
12
-
13
- # Agent 默认模型 —— initialize() 每次都带 provider/model,这里只兜底。
14
- - id: agent-default-model
15
- name: "@deepseek-ai/dsh-agent-default-model"
16
- config:
17
- provider: !!js process.env.DSH_DEFAULT_PROVIDER ?? 'deepseek-official'
18
- model: !!js process.env.DSH_DEFAULT_MODEL ?? 'deepseek-v4-flash'
19
-
20
- # 部署人设(默认空;pi-web-ui 设置面板可注入)。
21
- - id: system-prompt
22
- name: "@deepseek-ai/dsh-system-prompt"
23
- config:
24
- persona: !!js process.env.DSH_PERSONA ?? ''
25
-
26
- # 沙箱工作区根(workspace-write 权限模式的护栏边界)。
27
- - id: sandbox-policy
28
- name: "@deepseek-ai/dsh-sandbox-policy"
29
- config:
30
- mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
31
- workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
32
-
33
- # 无头 JSON-RPC 服务:没有交互用户来应答审批弹窗;工具在 workspace-write
34
- # 沙箱内直接执行,用户的 Stop 按钮就是控制手段。
35
- - id: approval
36
- name: "@deepseek-ai/dsh-user-approval"
37
- config:
38
- policy: "never"
39
-
40
- # 权限预设表 —— 与 approval=never 组合匹配。patch 语义:整行 config 全量替换,
41
- # 必须重述 base 的全部预设并补上 (workspace-write, never) 组合,显式 defaultPreset。
42
- - id: permission
43
- name: "@deepseek-ai/dsh-permission-presets"
44
- config:
45
- presets:
46
- read-only:
47
- sandbox: read-only
48
- approval: ask
49
- workspace-write:
50
- sandbox: workspace-write
51
- approval: ask
52
- workspace-write-never:
53
- sandbox: workspace-write
54
- approval: never
55
- danger-full-access:
56
- sandbox: danger-full-access
57
- approval: never
58
- defaultPreset: workspace-write-never
59
-
60
- # ── 新行(plain insert)─────────────────────────────────────────────────────
61
- - insert:
62
- # 模型提问工具:ask_user_question → ctx.userQuestions(base 已挂 user-questions
63
- # 服务)→ goal-rpc wrapper 注册的 provider 桥到浏览器对话框。
64
- - id: tool-ask-user
65
- name: "@deepseek-ai/dsh-tool-ask-user"
66
- # Stdio JSON-RPC 服务插件(inject: agents)。name 用 !!js 求值为绝对路径
67
- # (指向项目 node_modules 里的 dsh-sdk-jsonrpc-server),因为 cordis loader
68
- # 的裸包解析只认 bareModuleBaseUrl(运行时树),混树挂载必须走绝对路径;
69
- # 该插件的 peer 依赖从项目树自洽解析。
70
- - id: sdk-jsonrpc
71
- name: !!js process.env.PI_WEB_DSH_JSONRPC_ENTRY
1
+ # pi-web-ui DSH runtime overrides — applies AFTER the dsh-base bundle patch
2
+ # (loaded from the runtime tree). Rows that restate a base row use id-targeted
3
+ # REPLACE (last write wins per row); new rows come in a plain insert block.
4
+ # !!js 表达式由 Cordis Loader 在挂载时求值(boot 暴露 dshHomePath,process.env
5
+ # 直读)。
6
+
7
+ # 会话持久化根 —— 覆盖 base 的 dshHomePath('sessions'),让 pi-web-ui 指向自己的数据目录。
8
+ - id: session-persistence-jsonl
9
+ name: "@deepseek-ai/dsh-session-persistence-jsonl"
10
+ config:
11
+ root: !!js process.env.DSH_SESSION_ROOT ?? dshHomePath('sessions')
12
+
13
+ # Agent 默认模型 —— initialize() 每次都带 provider/model,这里只兜底。
14
+ - id: agent-default-model
15
+ name: "@deepseek-ai/dsh-agent-default-model"
16
+ config:
17
+ provider: !!js process.env.DSH_DEFAULT_PROVIDER ?? 'deepseek-official'
18
+ model: !!js process.env.DSH_DEFAULT_MODEL ?? 'deepseek-v4-flash'
19
+
20
+ # 部署人设(默认空;pi-web-ui 设置面板可注入)。
21
+ - id: system-prompt
22
+ name: "@deepseek-ai/dsh-system-prompt"
23
+ config:
24
+ persona: !!js process.env.DSH_PERSONA ?? ''
25
+
26
+ # 沙箱工作区根(workspace-write 权限模式的护栏边界)。
27
+ - id: sandbox-policy
28
+ name: "@deepseek-ai/dsh-sandbox-policy"
29
+ config:
30
+ mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
31
+ workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
32
+
33
+ # 无头 JSON-RPC 服务:没有交互用户来应答审批弹窗;工具在 workspace-write
34
+ # 沙箱内直接执行,用户的 Stop 按钮就是控制手段。
35
+ - id: approval
36
+ name: "@deepseek-ai/dsh-user-approval"
37
+ config:
38
+ policy: "never"
39
+
40
+ # 权限预设表 —— 与 approval=never 组合匹配。patch 语义:整行 config 全量替换,
41
+ # 必须重述 base 的全部预设并补上 (workspace-write, never) 组合,显式 defaultPreset。
42
+ - id: permission
43
+ name: "@deepseek-ai/dsh-permission-presets"
44
+ config:
45
+ presets:
46
+ read-only:
47
+ sandbox: read-only
48
+ approval: ask
49
+ workspace-write:
50
+ sandbox: workspace-write
51
+ approval: ask
52
+ workspace-write-never:
53
+ sandbox: workspace-write
54
+ approval: never
55
+ danger-full-access:
56
+ sandbox: danger-full-access
57
+ approval: never
58
+ defaultPreset: workspace-write-never
59
+
60
+ # ── 新行(plain insert)─────────────────────────────────────────────────────
61
+ - insert:
62
+ # 模型提问工具:ask_user_question → ctx.userQuestions(base 已挂 user-questions
63
+ # 服务)→ goal-rpc wrapper 注册的 provider 桥到浏览器对话框。
64
+ - id: tool-ask-user
65
+ name: "@deepseek-ai/dsh-tool-ask-user"
66
+ # Stdio JSON-RPC 服务插件(inject: agents)。name 用 !!js 求值为绝对路径
67
+ # (指向项目 node_modules 里的 dsh-sdk-jsonrpc-server),因为 cordis loader
68
+ # 的裸包解析只认 bareModuleBaseUrl(运行时树),混树挂载必须走绝对路径;
69
+ # 该插件的 peer 依赖从项目树自洽解析。
70
+ - id: sdk-jsonrpc
71
+ name: !!js process.env.PI_WEB_DSH_JSONRPC_ENTRY
@@ -1,86 +1,86 @@
1
- // Shared runtime-tree resolution for the pi-web-ui DSH runtime.
2
- // Used by launcher.mjs (boot-time) and probe-mixed.mjs (self-check).
3
- //
4
- // A "runtime tree" is a node_modules root that directly contains the dsh
5
- // runtime packages: `@deepseek-ai/dsh-base` (with its cordis.patch.yml
6
- // bundle) and `@deepseek-ai/dsh-app-boot`. Two layouts are accepted:
7
- //
8
- // flat: <root>/@deepseek-ai/dsh-base/cordis.patch.yml
9
- // (a node_modules root where the runtime packages live top-level)
10
- //
11
- // nested: <root>/@deepseek-ai/dsh/node_modules/@deepseek-ai/…
12
- // (`npm i -g @deepseek-ai/dsh` installs the CLI package with its
13
- // OWN nested runtime tree of ~196 packages; the global root itself
14
- // only holds the `dsh` package)
15
- //
16
- // The returned value is always the *bare-module base dir*: the node_modules
17
- // root that directly contains the runtime packages. That is what
18
- // boot(bareModuleBaseUrl) anchors bare package names (dsh-base rows) to.
19
- import { existsSync } from "node:fs";
20
- import { dirname, join, resolve } from "node:path";
21
- import { fileURLToPath } from "node:url";
22
-
23
- const HERE = dirname(fileURLToPath(import.meta.url));
24
-
25
- /** Given a candidate node_modules root, return the bare-module base dir, or null. */
26
- export function runtimeBaseFor(root) {
27
- if (root == null) return null;
28
- const flatScope = join(root, "@deepseek-ai");
29
- if (
30
- existsSync(join(flatScope, "dsh-base", "cordis.patch.yml")) &&
31
- existsSync(join(flatScope, "dsh-app-boot", "lib", "index.js"))
32
- ) {
33
- return resolve(root);
34
- }
35
- const nestedScope = join(root, "@deepseek-ai", "dsh", "node_modules", "@deepseek-ai");
36
- if (
37
- existsSync(join(nestedScope, "dsh-base", "cordis.patch.yml")) &&
38
- existsSync(join(nestedScope, "dsh-app-boot", "lib", "index.js"))
39
- ) {
40
- return resolve(join(root, "@deepseek-ai", "dsh", "node_modules"));
41
- }
42
- return null;
43
- }
44
-
45
- /**
46
- * Resolution order:
47
- * 1. $PI_WEB_DSH_RUNTIME — explicit node_modules root
48
- * 2. this package's node_modules — full local install scenario
49
- * 3. execPath-adjacent node_modules — fnm / standalone node stable layout
50
- * (<node.exe dir>/node_modules is a junction to the global tree)
51
- * 4. `npm root -g` — global install (win32 .cmd shim needs a shell)
52
- */
53
- export async function resolveRuntimeBase() {
54
- const explicit = process.env.PI_WEB_DSH_RUNTIME;
55
- if (explicit) {
56
- const base = runtimeBaseFor(explicit);
57
- if (base) return base;
58
- }
59
- const local = join(resolve(HERE, "..", "..", ".."), "node_modules");
60
- {
61
- const base = runtimeBaseFor(local);
62
- if (base) return base;
63
- }
64
- const adjacent = join(dirname(process.execPath), "node_modules");
65
- {
66
- const base = runtimeBaseFor(adjacent);
67
- if (base) return base;
68
- }
69
- try {
70
- const { spawnSync } = await import("node:child_process");
71
- const res = spawnSync(process.platform === "win32" ? "npm" : "npm", ["root", "-g"], {
72
- encoding: "utf8",
73
- timeout: 15_000,
74
- windowsHide: true,
75
- ...(process.platform === "win32" ? { shell: true } : {}),
76
- });
77
- const root = String(res.stdout ?? "").trim();
78
- if (root) {
79
- const base = runtimeBaseFor(root);
80
- if (base) return base;
81
- }
82
- } catch {
83
- /* fall through */
84
- }
85
- return null;
86
- }
1
+ // Shared runtime-tree resolution for the pi-web-ui DSH runtime.
2
+ // Used by launcher.mjs (boot-time) and probe-mixed.mjs (self-check).
3
+ //
4
+ // A "runtime tree" is a node_modules root that directly contains the dsh
5
+ // runtime packages: `@deepseek-ai/dsh-base` (with its cordis.patch.yml
6
+ // bundle) and `@deepseek-ai/dsh-app-boot`. Two layouts are accepted:
7
+ //
8
+ // flat: <root>/@deepseek-ai/dsh-base/cordis.patch.yml
9
+ // (a node_modules root where the runtime packages live top-level)
10
+ //
11
+ // nested: <root>/@deepseek-ai/dsh/node_modules/@deepseek-ai/…
12
+ // (`npm i -g @deepseek-ai/dsh` installs the CLI package with its
13
+ // OWN nested runtime tree of ~196 packages; the global root itself
14
+ // only holds the `dsh` package)
15
+ //
16
+ // The returned value is always the *bare-module base dir*: the node_modules
17
+ // root that directly contains the runtime packages. That is what
18
+ // boot(bareModuleBaseUrl) anchors bare package names (dsh-base rows) to.
19
+ import { existsSync } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+
23
+ const HERE = dirname(fileURLToPath(import.meta.url));
24
+
25
+ /** Given a candidate node_modules root, return the bare-module base dir, or null. */
26
+ export function runtimeBaseFor(root) {
27
+ if (root == null) return null;
28
+ const flatScope = join(root, "@deepseek-ai");
29
+ if (
30
+ existsSync(join(flatScope, "dsh-base", "cordis.patch.yml")) &&
31
+ existsSync(join(flatScope, "dsh-app-boot", "lib", "index.js"))
32
+ ) {
33
+ return resolve(root);
34
+ }
35
+ const nestedScope = join(root, "@deepseek-ai", "dsh", "node_modules", "@deepseek-ai");
36
+ if (
37
+ existsSync(join(nestedScope, "dsh-base", "cordis.patch.yml")) &&
38
+ existsSync(join(nestedScope, "dsh-app-boot", "lib", "index.js"))
39
+ ) {
40
+ return resolve(join(root, "@deepseek-ai", "dsh", "node_modules"));
41
+ }
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Resolution order:
47
+ * 1. $PI_WEB_DSH_RUNTIME — explicit node_modules root
48
+ * 2. this package's node_modules — full local install scenario
49
+ * 3. execPath-adjacent node_modules — fnm / standalone node stable layout
50
+ * (<node.exe dir>/node_modules is a junction to the global tree)
51
+ * 4. `npm root -g` — global install (win32 .cmd shim needs a shell)
52
+ */
53
+ export async function resolveRuntimeBase() {
54
+ const explicit = process.env.PI_WEB_DSH_RUNTIME;
55
+ if (explicit) {
56
+ const base = runtimeBaseFor(explicit);
57
+ if (base) return base;
58
+ }
59
+ const local = join(resolve(HERE, "..", "..", ".."), "node_modules");
60
+ {
61
+ const base = runtimeBaseFor(local);
62
+ if (base) return base;
63
+ }
64
+ const adjacent = join(dirname(process.execPath), "node_modules");
65
+ {
66
+ const base = runtimeBaseFor(adjacent);
67
+ if (base) return base;
68
+ }
69
+ try {
70
+ const { spawnSync } = await import("node:child_process");
71
+ const res = spawnSync(process.platform === "win32" ? "npm" : "npm", ["root", "-g"], {
72
+ encoding: "utf8",
73
+ timeout: 15_000,
74
+ windowsHide: true,
75
+ ...(process.platform === "win32" ? { shell: true } : {}),
76
+ });
77
+ const root = String(res.stdout ?? "").trim();
78
+ if (root) {
79
+ const base = runtimeBaseFor(root);
80
+ if (base) return base;
81
+ }
82
+ } catch {
83
+ /* fall through */
84
+ }
85
+ return null;
86
+ }