dsh-sun-space 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.mjs +74 -16
- package/lib/router-tools.mjs +61 -11
- package/lib/worker-vault.mjs +53 -3
- package/package.json +1 -1
- package/src/client.js +42 -4
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
4
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
5
|
import { execFile } from "node:child_process";
|
|
4
6
|
// \0rolldown/runtime.js
|
|
5
7
|
var __defProp = Object.defineProperty;
|
|
@@ -12,6 +14,65 @@ var __exportAll = (all, no_symbols) => {
|
|
|
12
14
|
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
15
|
return target;
|
|
14
16
|
};
|
|
17
|
+
// src/vault-path.ts
|
|
18
|
+
/**
|
|
19
|
+
* vault 路径解析——唯一事实源,全插件共享(source/router-tools/worker-vault/worker-watchdog)。
|
|
20
|
+
*
|
|
21
|
+
* 解析优先级:
|
|
22
|
+
* ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
|
|
23
|
+
* ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
|
|
24
|
+
* ③ 默认约定 ~/Workspace/sun-vault
|
|
25
|
+
*
|
|
26
|
+
* 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
|
|
27
|
+
* 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
|
|
28
|
+
* 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
|
|
29
|
+
*
|
|
30
|
+
* 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
|
|
31
|
+
* 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_VAULT_DIRNAME = "sun-vault";
|
|
34
|
+
/** 插件自有状态目录(~/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。 */
|
|
35
|
+
const STATE_DIR = join(homedir(), ".dsh", "state", "sun-space");
|
|
36
|
+
/** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
|
|
37
|
+
function computeVaultPath(envValue, configJson, home = homedir()) {
|
|
38
|
+
if (envValue && envValue.trim() !== "") return envValue;
|
|
39
|
+
try {
|
|
40
|
+
const cfg = JSON.parse(configJson ?? "");
|
|
41
|
+
if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
|
|
42
|
+
} catch {}
|
|
43
|
+
return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
|
|
44
|
+
}
|
|
45
|
+
let cached = {
|
|
46
|
+
at: 0,
|
|
47
|
+
key: "",
|
|
48
|
+
value: ""
|
|
49
|
+
};
|
|
50
|
+
/** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
|
|
51
|
+
function vaultPath(now = Date.now()) {
|
|
52
|
+
let configJson;
|
|
53
|
+
try {
|
|
54
|
+
configJson = readFileSync(join(STATE_DIR, "config.json"), "utf8");
|
|
55
|
+
} catch {}
|
|
56
|
+
if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
|
|
57
|
+
const value = computeVaultPath(process.env.SUN_VAULT, configJson);
|
|
58
|
+
cached = {
|
|
59
|
+
at: now,
|
|
60
|
+
key: configJson ?? "",
|
|
61
|
+
value
|
|
62
|
+
};
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
vaultPath();
|
|
66
|
+
/** 把解析结果写回状态目录的 state.json(apply 时调用,尽力而为),供客户端发现。 */
|
|
67
|
+
function publishVaultState(now = /* @__PURE__ */ new Date()) {
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
70
|
+
writeFileSync(join(STATE_DIR, "state.json"), JSON.stringify({
|
|
71
|
+
vault: vaultPath(now.getTime()),
|
|
72
|
+
updatedAt: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}T${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`
|
|
73
|
+
}, null, 2), "utf8");
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
15
76
|
// src/dws.ts
|
|
16
77
|
/**
|
|
17
78
|
* dws 执行器:单次命令调用(无长连接)。供 sun_notify / Source 轮询使用。
|
|
@@ -121,7 +182,6 @@ var source_exports = /* @__PURE__ */ __exportAll({
|
|
|
121
182
|
CREDENTIAL_ALERT_COOLDOWN_MS: () => CREDENTIAL_ALERT_COOLDOWN_MS,
|
|
122
183
|
CREDENTIAL_ALERT_STREAK: () => 2,
|
|
123
184
|
TRIGGER_TTL_MS: () => TRIGGER_TTL_MS,
|
|
124
|
-
VAULT: () => VAULT$1,
|
|
125
185
|
apply: () => apply$2,
|
|
126
186
|
inject: () => inject$2,
|
|
127
187
|
parseTriggerTs: () => parseTriggerTs,
|
|
@@ -129,11 +189,10 @@ var source_exports = /* @__PURE__ */ __exportAll({
|
|
|
129
189
|
shouldAlertCredential: () => shouldAlertCredential
|
|
130
190
|
});
|
|
131
191
|
const inject$2 = ["timer"];
|
|
132
|
-
const VAULT$1 = process.env.SUN_VAULT ?? `${process.env.HOME}/Workspace/云顶天宫`;
|
|
133
192
|
/** 轮询周期(毫秒):纯程序层,可调密。 */
|
|
134
193
|
const POLL_MS = 18e5;
|
|
135
|
-
const
|
|
136
|
-
const
|
|
194
|
+
const triggerDir$1 = () => `${vaultPath()}/90-Meta/triggers`;
|
|
195
|
+
const snapshotFile = () => `${vaultPath()}/90-Meta/mock-data/last-snapshot.json`;
|
|
137
196
|
/** trigger 文件 TTL:超过即视为已消费/过期,轮询成功路径顺带清理。 */
|
|
138
197
|
const TRIGGER_TTL_MS = 6048e5;
|
|
139
198
|
const CREDENTIAL_ALERT_COOLDOWN_MS = 36e5;
|
|
@@ -179,7 +238,7 @@ function schedulePolling(ctx) {
|
|
|
179
238
|
const snap = await snapshot();
|
|
180
239
|
const prev = await readPrev();
|
|
181
240
|
if (!prev) {
|
|
182
|
-
await writeFile(
|
|
241
|
+
await writeFile(snapshotFile(), JSON.stringify(snap), "utf8");
|
|
183
242
|
return;
|
|
184
243
|
}
|
|
185
244
|
const fresh = diff(prev, snap);
|
|
@@ -191,9 +250,9 @@ function schedulePolling(ctx) {
|
|
|
191
250
|
if (fresh.newApprovals.length) parts.push(`新审批 ${fresh.newApprovals.length} 件`);
|
|
192
251
|
await notifySelf(uid, `[Sun·info] ${parts.join("、")},已入触发队列(${(/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN")})`);
|
|
193
252
|
}
|
|
194
|
-
await writeFile(
|
|
253
|
+
await writeFile(snapshotFile(), JSON.stringify(snap), "utf8");
|
|
195
254
|
health.fails = 0;
|
|
196
|
-
await pruneTriggers(
|
|
255
|
+
await pruneTriggers(triggerDir$1()).catch(() => {});
|
|
197
256
|
} catch (e) {
|
|
198
257
|
ctx.logger?.warn?.(`[sun-space] poll failed: ${String(e.message).slice(0, 150)}`);
|
|
199
258
|
health.fails++;
|
|
@@ -224,15 +283,15 @@ function schedulePolling(ctx) {
|
|
|
224
283
|
}
|
|
225
284
|
async function readPrev() {
|
|
226
285
|
try {
|
|
227
|
-
return JSON.parse(await readFile(
|
|
286
|
+
return JSON.parse(await readFile(snapshotFile(), "utf8"));
|
|
228
287
|
} catch {
|
|
229
288
|
return null;
|
|
230
289
|
}
|
|
231
290
|
}
|
|
232
291
|
async function writeTrigger(label, payload) {
|
|
233
|
-
await mkdir(
|
|
292
|
+
await mkdir(triggerDir$1(), { recursive: true });
|
|
234
293
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
235
|
-
await writeFile(join(
|
|
294
|
+
await writeFile(join(triggerDir$1(), `${ts}-${label}.json`), JSON.stringify({
|
|
236
295
|
label,
|
|
237
296
|
payload
|
|
238
297
|
}, null, 2), "utf8");
|
|
@@ -275,7 +334,6 @@ async function writeTrigger(label, payload) {
|
|
|
275
334
|
var worker_watchdog_exports = /* @__PURE__ */ __exportAll({
|
|
276
335
|
DEFAULT_STALL_MS: () => DEFAULT_STALL_MS,
|
|
277
336
|
DEFAULT_TICK_MS: () => DEFAULT_TICK_MS,
|
|
278
|
-
VAULT: () => VAULT,
|
|
279
337
|
apply: () => apply$1,
|
|
280
338
|
inject: () => inject$1,
|
|
281
339
|
isDelegatedChildHeader: () => isDelegatedChildHeader,
|
|
@@ -286,8 +344,7 @@ var worker_watchdog_exports = /* @__PURE__ */ __exportAll({
|
|
|
286
344
|
});
|
|
287
345
|
const name$1 = "worker-watchdog";
|
|
288
346
|
const inject$1 = ["timer"];
|
|
289
|
-
const
|
|
290
|
-
const TRIGGER_DIR = `${VAULT}/90-Meta/triggers`;
|
|
347
|
+
const triggerDir = () => `${vaultPath()}/90-Meta/triggers`;
|
|
291
348
|
/** 默认停滞阈值 15 分钟:长思考/长命令完成后即重置计时,真卡死撑不到这么久。 */
|
|
292
349
|
const DEFAULT_STALL_MS = 9e5;
|
|
293
350
|
/** 巡检间隔 30 秒。 */
|
|
@@ -344,9 +401,9 @@ function apply$1(ctx) {
|
|
|
344
401
|
const idleMin = Math.round((Date.now() - entry.at) / 6e4);
|
|
345
402
|
log("warn", `stalled: child=${entry.id} idle=${idleMin}min — interrupting & notifying`);
|
|
346
403
|
try {
|
|
347
|
-
await mkdir(
|
|
404
|
+
await mkdir(triggerDir(), { recursive: true });
|
|
348
405
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
349
|
-
await writeFile(join(
|
|
406
|
+
await writeFile(join(triggerDir(), `${ts}-watchdog-stall.json`), JSON.stringify({
|
|
350
407
|
label: "watchdog-stall",
|
|
351
408
|
payload: {
|
|
352
409
|
childId: entry.id,
|
|
@@ -430,6 +487,7 @@ function apply$1(ctx) {
|
|
|
430
487
|
const name = "sun-space";
|
|
431
488
|
const inject = ["timer", "tools"];
|
|
432
489
|
function apply(ctx) {
|
|
490
|
+
publishVaultState();
|
|
433
491
|
ctx.plugin(source_exports);
|
|
434
492
|
ctx.plugin(worker_watchdog_exports);
|
|
435
493
|
}
|
package/lib/router-tools.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
4
6
|
// src/vault.ts
|
|
5
7
|
/**
|
|
6
8
|
* vault 访问:路径守卫 + frontmatter 解析/序列化。纯函数为主,可单测。
|
|
@@ -183,9 +185,57 @@ async function dingSelf(userId, content, type = "app") {
|
|
|
183
185
|
]);
|
|
184
186
|
return { ok: data?.ok === true || data?.success === true || data?.data?.success === true };
|
|
185
187
|
}
|
|
188
|
+
// src/vault-path.ts
|
|
189
|
+
/**
|
|
190
|
+
* vault 路径解析——唯一事实源,全插件共享(source/router-tools/worker-vault/worker-watchdog)。
|
|
191
|
+
*
|
|
192
|
+
* 解析优先级:
|
|
193
|
+
* ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
|
|
194
|
+
* ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
|
|
195
|
+
* ③ 默认约定 ~/Workspace/sun-vault
|
|
196
|
+
*
|
|
197
|
+
* 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
|
|
198
|
+
* 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
|
|
199
|
+
* 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
|
|
200
|
+
*
|
|
201
|
+
* 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
|
|
202
|
+
* 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
|
|
203
|
+
*/
|
|
204
|
+
const DEFAULT_VAULT_DIRNAME = "sun-vault";
|
|
205
|
+
/** 插件自有状态目录(~/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。 */
|
|
206
|
+
const STATE_DIR = join(homedir(), ".dsh", "state", "sun-space");
|
|
207
|
+
/** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
|
|
208
|
+
function computeVaultPath(envValue, configJson, home = homedir()) {
|
|
209
|
+
if (envValue && envValue.trim() !== "") return envValue;
|
|
210
|
+
try {
|
|
211
|
+
const cfg = JSON.parse(configJson ?? "");
|
|
212
|
+
if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
|
|
213
|
+
} catch {}
|
|
214
|
+
return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
|
|
215
|
+
}
|
|
216
|
+
let cached = {
|
|
217
|
+
at: 0,
|
|
218
|
+
key: "",
|
|
219
|
+
value: ""
|
|
220
|
+
};
|
|
221
|
+
/** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
|
|
222
|
+
function vaultPath(now = Date.now()) {
|
|
223
|
+
let configJson;
|
|
224
|
+
try {
|
|
225
|
+
configJson = readFileSync(join(STATE_DIR, "config.json"), "utf8");
|
|
226
|
+
} catch {}
|
|
227
|
+
if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
|
|
228
|
+
const value = computeVaultPath(process.env.SUN_VAULT, configJson);
|
|
229
|
+
cached = {
|
|
230
|
+
at: now,
|
|
231
|
+
key: configJson ?? "",
|
|
232
|
+
value
|
|
233
|
+
};
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
vaultPath();
|
|
186
237
|
// src/router-tools.ts
|
|
187
238
|
const inject = ["tools", "subagents"];
|
|
188
|
-
const VAULT = process.env.SUN_VAULT ?? `${process.env.HOME}/Workspace/云顶天宫`;
|
|
189
239
|
const text = (s) => [{
|
|
190
240
|
type: "text",
|
|
191
241
|
text: s
|
|
@@ -264,7 +314,7 @@ function apply(ctx) {
|
|
|
264
314
|
}, async (args) => {
|
|
265
315
|
try {
|
|
266
316
|
if (args.what === "triggers") {
|
|
267
|
-
const dir = resolveSafe(
|
|
317
|
+
const dir = resolveSafe(vaultPath(), "90-Meta/triggers");
|
|
268
318
|
try {
|
|
269
319
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, 10);
|
|
270
320
|
const triggers = [];
|
|
@@ -296,7 +346,7 @@ function apply(ctx) {
|
|
|
296
346
|
}
|
|
297
347
|
if (args.what === "journal") {
|
|
298
348
|
const d = args.date ?? localDate();
|
|
299
|
-
const p = resolveRead(
|
|
349
|
+
const p = resolveRead(vaultPath(), `40-Journal/${d}.md`);
|
|
300
350
|
const raw = await readFile(p, "utf8");
|
|
301
351
|
if (args.full) return {
|
|
302
352
|
ok: true,
|
|
@@ -311,7 +361,7 @@ function apply(ctx) {
|
|
|
311
361
|
};
|
|
312
362
|
}
|
|
313
363
|
if (args.what === "tasks") {
|
|
314
|
-
const dir = resolveSafe(
|
|
364
|
+
const dir = resolveSafe(vaultPath(), "50-Tasks");
|
|
315
365
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".md"));
|
|
316
366
|
const tasks = [];
|
|
317
367
|
for (const f of files) {
|
|
@@ -334,7 +384,7 @@ function apply(ctx) {
|
|
|
334
384
|
};
|
|
335
385
|
}
|
|
336
386
|
if (args.what === "runbook" || args.what === "memory") {
|
|
337
|
-
const p = resolveSafe(
|
|
387
|
+
const p = resolveSafe(vaultPath(), `90-Meta/${args.what}.md`);
|
|
338
388
|
return {
|
|
339
389
|
ok: true,
|
|
340
390
|
data: { text: (await readFile(p, "utf8")).slice(0, 8e3) }
|
|
@@ -345,7 +395,7 @@ function apply(ctx) {
|
|
|
345
395
|
ok: false,
|
|
346
396
|
note: "note 需要 path"
|
|
347
397
|
};
|
|
348
|
-
const raw = await readFile(resolveRead(
|
|
398
|
+
const raw = await readFile(resolveRead(vaultPath(), args.path), "utf8");
|
|
349
399
|
if (args.full) return {
|
|
350
400
|
ok: true,
|
|
351
401
|
data: { text: raw.slice(0, 8e3) }
|
|
@@ -389,7 +439,7 @@ function apply(ctx) {
|
|
|
389
439
|
required: ["path", "content"]
|
|
390
440
|
}, async (args) => {
|
|
391
441
|
try {
|
|
392
|
-
const p = resolveSafe(
|
|
442
|
+
const p = resolveSafe(vaultPath(), args.path);
|
|
393
443
|
await mkdir(join(p, ".."), { recursive: true });
|
|
394
444
|
if (args.mode === "append") await appendFile(p, args.content, "utf8");
|
|
395
445
|
else await writeFile(p, args.content, "utf8");
|
|
@@ -485,7 +535,7 @@ function apply(ctx) {
|
|
|
485
535
|
required: ["path", "fields"]
|
|
486
536
|
}, async (args) => {
|
|
487
537
|
try {
|
|
488
|
-
const p = resolveSafe(
|
|
538
|
+
const p = resolveSafe(vaultPath(), args.path);
|
|
489
539
|
const updated = updateFrontmatter(await readFile(p, "utf8"), args.fields);
|
|
490
540
|
await writeFile(p, updated, "utf8");
|
|
491
541
|
return {
|
|
@@ -519,7 +569,7 @@ function apply(ctx) {
|
|
|
519
569
|
},
|
|
520
570
|
cwd: {
|
|
521
571
|
type: "string",
|
|
522
|
-
description: "可选:Worker 的工作目录(绝对路径)。不传则随当前工作区。代码开发等 vault 外任务必传(如
|
|
572
|
+
description: "可选:Worker 的工作目录(绝对路径)。不传则随当前工作区。代码开发等 vault 外任务必传(如 ~/Workspace/<repo>)"
|
|
523
573
|
}
|
|
524
574
|
},
|
|
525
575
|
required: ["task_id", "prompt"]
|
|
@@ -559,7 +609,7 @@ function apply(ctx) {
|
|
|
559
609
|
};
|
|
560
610
|
}
|
|
561
611
|
try {
|
|
562
|
-
const p = resolveSafe(
|
|
612
|
+
const p = resolveSafe(vaultPath(), `50-Tasks/${args.task_id}.md`);
|
|
563
613
|
const raw = await readFile(p, "utf8");
|
|
564
614
|
await writeFile(p, updateFrontmatter(raw, { session: String(activation.childId ?? "") }), "utf8");
|
|
565
615
|
} catch {}
|
|
@@ -571,4 +621,4 @@ function apply(ctx) {
|
|
|
571
621
|
}));
|
|
572
622
|
ctx.logger?.info?.("[sun-space] router tools registered: sun_read sun_vault_write sun_vault_meta sun_notify sun_route(subagents)");
|
|
573
623
|
}
|
|
574
|
-
export {
|
|
624
|
+
export { apply, inject, lossless };
|
package/lib/worker-vault.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
3
5
|
// src/vault.ts
|
|
4
6
|
/**
|
|
5
7
|
* vault 访问:路径守卫 + frontmatter 解析/序列化。纯函数为主,可单测。
|
|
@@ -26,9 +28,57 @@ function resolveWithin(vaultRoot, rel, roots, kind) {
|
|
|
26
28
|
function resolveSafe(vaultRoot, rel) {
|
|
27
29
|
return resolveWithin(vaultRoot, rel, WRITABLE_ROOTS, "writable");
|
|
28
30
|
}
|
|
31
|
+
// src/vault-path.ts
|
|
32
|
+
/**
|
|
33
|
+
* vault 路径解析——唯一事实源,全插件共享(source/router-tools/worker-vault/worker-watchdog)。
|
|
34
|
+
*
|
|
35
|
+
* 解析优先级:
|
|
36
|
+
* ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
|
|
37
|
+
* ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
|
|
38
|
+
* ③ 默认约定 ~/Workspace/sun-vault
|
|
39
|
+
*
|
|
40
|
+
* 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
|
|
41
|
+
* 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
|
|
42
|
+
* 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
|
|
43
|
+
*
|
|
44
|
+
* 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
|
|
45
|
+
* 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_VAULT_DIRNAME = "sun-vault";
|
|
48
|
+
/** 插件自有状态目录(~/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。 */
|
|
49
|
+
const STATE_DIR = join(homedir(), ".dsh", "state", "sun-space");
|
|
50
|
+
/** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
|
|
51
|
+
function computeVaultPath(envValue, configJson, home = homedir()) {
|
|
52
|
+
if (envValue && envValue.trim() !== "") return envValue;
|
|
53
|
+
try {
|
|
54
|
+
const cfg = JSON.parse(configJson ?? "");
|
|
55
|
+
if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
|
|
56
|
+
} catch {}
|
|
57
|
+
return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
|
|
58
|
+
}
|
|
59
|
+
let cached = {
|
|
60
|
+
at: 0,
|
|
61
|
+
key: "",
|
|
62
|
+
value: ""
|
|
63
|
+
};
|
|
64
|
+
/** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
|
|
65
|
+
function vaultPath(now = Date.now()) {
|
|
66
|
+
let configJson;
|
|
67
|
+
try {
|
|
68
|
+
configJson = readFileSync(join(STATE_DIR, "config.json"), "utf8");
|
|
69
|
+
} catch {}
|
|
70
|
+
if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
|
|
71
|
+
const value = computeVaultPath(process.env.SUN_VAULT, configJson);
|
|
72
|
+
cached = {
|
|
73
|
+
at: now,
|
|
74
|
+
key: configJson ?? "",
|
|
75
|
+
value
|
|
76
|
+
};
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
vaultPath();
|
|
29
80
|
// src/worker-vault.ts
|
|
30
81
|
const inject = ["tools"];
|
|
31
|
-
const VAULT = process.env.SUN_VAULT ?? `${process.env.HOME}/Workspace/云顶天宫`;
|
|
32
82
|
const TASK_ID = /^T-\d{8}-\d{2}$/;
|
|
33
83
|
/** 纯函数:拼装受控产物路径;非法输入抛 VaultPathError。可单测。 */
|
|
34
84
|
function artifactPath(taskId, filename) {
|
|
@@ -78,7 +128,7 @@ function apply(ctx) {
|
|
|
78
128
|
const { task_id: taskId, filename, content } = args ?? {};
|
|
79
129
|
try {
|
|
80
130
|
const rel = artifactPath(String(taskId), String(filename));
|
|
81
|
-
const abs = resolveSafe(
|
|
131
|
+
const abs = resolveSafe(vaultPath(), rel);
|
|
82
132
|
await mkdir(join(abs, ".."), { recursive: true });
|
|
83
133
|
await writeFile(abs, String(content ?? ""), "utf8");
|
|
84
134
|
return {
|
|
@@ -99,4 +149,4 @@ function apply(ctx) {
|
|
|
99
149
|
});
|
|
100
150
|
ctx.logger?.info?.("[sun-space] worker artifact tool registered: sun_artifact_write");
|
|
101
151
|
}
|
|
102
|
-
export {
|
|
152
|
+
export { apply, artifactPath, inject };
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -17,12 +17,12 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
let hostCtx = null
|
|
18
18
|
|
|
19
19
|
const PANEL_ID = 'sun-space'
|
|
20
|
-
const VAULT_DIRNAME = '
|
|
20
|
+
const VAULT_DIRNAME = 'sun-vault' // vault 目录名约定;绝对路径动态发现,见 resolveVault
|
|
21
21
|
// 架构 §4.1 不变量:呈现层不得持有会话 id / vault 绝对路径常量(Router 会话会轮换,机器会换)。
|
|
22
22
|
// 会话解析链:session/list(sun-router 会话)→ 90-Meta/system/state.json 裁决 → updatedAt 最新兜底。
|
|
23
23
|
// vault 解析链:会话 cwd 推 HOME → 约定布局探测 state.json → state.json 的 vault 字段裁决(跨机自愈)。
|
|
24
24
|
// 写回:① 创建会话后经 session/prompt 引导自登记(含 vault 字段);② 晨报自检发现缺失/不一致自动修正。
|
|
25
|
-
const WB_REV = '
|
|
25
|
+
const WB_REV = 'v16' // 根节点 data-wb-rev 版本标记:判别浏览器是否吃到旧 client.js(immutable 缓存坑,HANDOFF 踩坑 1)
|
|
26
26
|
|
|
27
27
|
const STATUS_META = {
|
|
28
28
|
proposed: { label: '待确认派发', color: '#9e9e9e' },
|
|
@@ -96,14 +96,26 @@ window.__ModuleLoader__.load({
|
|
|
96
96
|
}).then(v => v.text || '')
|
|
97
97
|
|
|
98
98
|
// ===== vault 绝对路径发现(跨机器;呈现层不持常量,架构 §4.1 同款不变量)=====
|
|
99
|
-
// 链:会话 cwd 推 HOME → 约定布局 <HOME>/Workspace
|
|
99
|
+
// 链:会话 cwd 推 HOME → 约定布局 <HOME>/Workspace/sun-vault 探测 state.json → state.json.vault 字段裁决。
|
|
100
100
|
// state.json.vault 由 Router 自登记/晨报自检维护(server 端 VAULT 是唯一事实源)。
|
|
101
101
|
async function resolveVault() {
|
|
102
102
|
const v = await gw('session/list', {})
|
|
103
103
|
const items = Array.isArray(v?.items) ? v.items : Object.values(v?.items ?? v ?? {})
|
|
104
104
|
const scope = items.find(s => s?.projections?.values?.agentPreset === 'sun-router')?.sessionId ?? items[0]?.sessionId
|
|
105
105
|
if (!scope) return null
|
|
106
|
+
// ① 插件状态目录(HOME 从会话 cwd 推导,绝对路径读取):
|
|
107
|
+
// config.json 优先(运行期事实源——引导初始化/用户手写即时生效),state.json 兜底(启动期快照)
|
|
106
108
|
const homes = [...new Set(items.map(s => String(s.cwd || '')).filter(c => c.startsWith('/')).map(c => c.split('/').slice(0, 3).join('/')))]
|
|
109
|
+
for (const home of homes) {
|
|
110
|
+
for (const file of ['config.json', 'state.json']) {
|
|
111
|
+
try {
|
|
112
|
+
const raw = await readVaultFile(scope, `${home}/.dsh/state/sun-space/${file}`, 8)
|
|
113
|
+
const st = JSON.parse(raw)
|
|
114
|
+
if (st?.vault && st.vault.startsWith('/')) return st.vault
|
|
115
|
+
} catch { /* 该文件不存在 → 下一个 */ }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ② 约定布局探测(默认 ~/Workspace/sun-vault;自定义 vault 路径由①覆盖)
|
|
107
119
|
const candidates = [...new Set(homes.map(h => `${h}/Workspace/${VAULT_DIRNAME}`))]
|
|
108
120
|
for (const cand of candidates) {
|
|
109
121
|
try {
|
|
@@ -255,6 +267,17 @@ window.__ModuleLoader__.load({
|
|
|
255
267
|
}
|
|
256
268
|
}
|
|
257
269
|
|
|
270
|
+
// 知识库初始化指令(下发到当前会话执行——标准预设有完整文件工具;
|
|
271
|
+
// 第 6 步写 ~/.dsh 超出工作区,会在对话中弹一次审批卡,由用户批准)
|
|
272
|
+
const VAULT_INIT_PROMPT = `【孙学空间知识库初始化】以当前工作目录为知识库根(vault):
|
|
273
|
+
1) 创建目录:90-Meta/system、40-Journal、50-Tasks/artifacts、00-Inbox、60-Archive
|
|
274
|
+
2) 写 90-Meta/schemas.md:frontmatter 契约最小集——通用字段 type/id/status/created/updated;task 的 status 枚举(proposed|running|need-confirm|done|blocked|dropped);写入纪律(Journal 由 Router 独占;日期一律本地时间 YYYY-MM-DDTHH:mm 不带时区后缀;结论必注出处)
|
|
275
|
+
3) 写 90-Meta/runbook.md:初始版(# Sun 行为手册 + v0.1 版本行 + §1 角色:Router 只分派不执行 + §2 沟通规范:IM ≤100 字结论先行 + §3 打扰分级:info 攒批/ask 立即/urgent 加 DING)
|
|
276
|
+
4) 写 90-Meta/memory.md:初始版(# 关于用户,待填充)
|
|
277
|
+
5) 创建目录 90-Meta/system
|
|
278
|
+
6) 把知识库绝对路径登记到 ~/.dsh/state/sun-space/config.json:内容 {"vault": "<当前目录绝对路径>"}(目录不存在则创建)
|
|
279
|
+
7) 完成后列出生成的文件清单。`
|
|
280
|
+
|
|
258
281
|
// 创建 Router 会话 → 自登记 state.json → 跳转 → 重检(InitPanel 与 OnboardPanel 共用)
|
|
259
282
|
async function createAndBootstrap(checks, onRetry) {
|
|
260
283
|
try {
|
|
@@ -377,10 +400,11 @@ window.__ModuleLoader__.load({
|
|
|
377
400
|
|
|
378
401
|
// 新用户三步启用引导(主理鱼同款编号步骤卡;替代旧的一锅炖检查面板)
|
|
379
402
|
function OnboardPanel({ checks, currentSessionId, onRetry }) {
|
|
403
|
+
const [initBusy, setInitBusy] = React.useState(false)
|
|
380
404
|
const steps = [
|
|
381
405
|
{ n: 1, ok: checks.presets, title: '确认依赖插件', desc: checks.presets ? '智能体协作 / 子智能体插件已启用' : '打开插件面板,启用 sun-space / 智能体协作相关插件后重启实例', action: checks.presets ? null : 'plugins' },
|
|
382
406
|
{ n: 2, ok: !!checks.routerSession, title: '创建 Router 会话', desc: checks.routerSession ? `已就绪:${checks.routerSession.slice(0, 28)}…` : '一键创建 sun-router 预设会话并自动登记(孙学空间与定时任务都挂在这上面)', action: checks.presets && !checks.routerSession ? 'create-router' : null },
|
|
383
|
-
{ n: 3, ok: checks.vault, title: '
|
|
407
|
+
{ n: 3, ok: checks.vault, title: '知识库(以当前目录初始化)', desc: checks.vault ? `已就绪:${checks.vaultPath}` : (currentSessionId ? '以当前目录为 vault 根创建 90-Meta 骨架,并把路径登记到 ~/.dsh/state/sun-space/config.json——会在对话中请求一次该写入的批准' : '先打开一个会话再来执行此步'), action: checks.vault || !currentSessionId || initBusy ? null : 'init-vault' },
|
|
384
408
|
]
|
|
385
409
|
const allOk = steps.every(x => x.ok)
|
|
386
410
|
return h('div', null, [
|
|
@@ -394,6 +418,20 @@ window.__ModuleLoader__.load({
|
|
|
394
418
|
]),
|
|
395
419
|
st.action === 'plugins' ? h('button', { key: 'a', onClick: () => { try { hostCtx.layout.selectPanel('plugins') } catch (e) { D.errors.push(String(e)) } }, style: btnStyle }, '打开插件面板') : null,
|
|
396
420
|
st.action === 'create-router' ? h('button', { key: 'a', onClick: () => createAndBootstrap(checks, onRetry), style: btnStyle }, '创建 Router 会话') : null,
|
|
421
|
+
st.action === 'init-vault' ? h('button', { key: 'a', onClick: async () => {
|
|
422
|
+
setInitBusy(true)
|
|
423
|
+
try {
|
|
424
|
+
await gw('session/prompt', {
|
|
425
|
+
requestId: 'sun-init-' + Date.now(),
|
|
426
|
+
sessionId: currentSessionId,
|
|
427
|
+
mode: 'queue',
|
|
428
|
+
content: [{ type: 'text', text: VAULT_INIT_PROMPT }],
|
|
429
|
+
clientTimeZone: 'Asia/Shanghai',
|
|
430
|
+
})
|
|
431
|
+
checks.notes.push('初始化任务已下发到当前会话——请在对话中批准 ~/.dsh 写入并等待完成,然后点「↻ 重新检查」')
|
|
432
|
+
} catch (e) { D.errors.push('init-vault: ' + String(e)) }
|
|
433
|
+
setInitBusy(false)
|
|
434
|
+
}, style: btnStyle }, '以当前目录初始化知识库') : null,
|
|
397
435
|
])),
|
|
398
436
|
allOk ? h('p', { key: 'done', style: { color: '#57b26a', fontSize: 13 } }, '✓ 全部就绪——到「定时任务」用模板创建晨报等四条生产任务,即可开始使用。') : null,
|
|
399
437
|
h('button', { key: 'retry', onClick: onRetry, style: { ...btnStyle, marginTop: 10 } }, '↻ 重新检查'),
|