dsh-sun-space 0.2.5 → 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 +54 -28
- package/lib/router-tools.mjs +43 -19
- package/lib/worker-vault.mjs +36 -12
- package/package.json +1 -1
- package/src/client.js +40 -9
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
4
5
|
import { execFile } from "node:child_process";
|
|
@@ -19,30 +20,57 @@ var __exportAll = (all, no_symbols) => {
|
|
|
19
20
|
*
|
|
20
21
|
* 解析优先级:
|
|
21
22
|
* ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
|
|
22
|
-
* ②
|
|
23
|
+
* ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
|
|
23
24
|
* ③ 默认约定 ~/Workspace/sun-vault
|
|
24
25
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
|
|
27
|
+
* 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
|
|
28
|
+
* 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
+
* 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
|
|
31
|
+
* 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
|
|
30
32
|
*/
|
|
31
33
|
const DEFAULT_VAULT_DIRNAME = "sun-vault";
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
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;
|
|
35
39
|
try {
|
|
36
|
-
const cfg = JSON.parse(
|
|
40
|
+
const cfg = JSON.parse(configJson ?? "");
|
|
37
41
|
if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
|
|
38
42
|
} catch {}
|
|
39
43
|
return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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()) {
|
|
44
68
|
try {
|
|
45
|
-
|
|
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");
|
|
46
74
|
} catch {}
|
|
47
75
|
}
|
|
48
76
|
// src/dws.ts
|
|
@@ -154,7 +182,6 @@ var source_exports = /* @__PURE__ */ __exportAll({
|
|
|
154
182
|
CREDENTIAL_ALERT_COOLDOWN_MS: () => CREDENTIAL_ALERT_COOLDOWN_MS,
|
|
155
183
|
CREDENTIAL_ALERT_STREAK: () => 2,
|
|
156
184
|
TRIGGER_TTL_MS: () => TRIGGER_TTL_MS,
|
|
157
|
-
VAULT: () => VAULT,
|
|
158
185
|
apply: () => apply$2,
|
|
159
186
|
inject: () => inject$2,
|
|
160
187
|
parseTriggerTs: () => parseTriggerTs,
|
|
@@ -164,8 +191,8 @@ var source_exports = /* @__PURE__ */ __exportAll({
|
|
|
164
191
|
const inject$2 = ["timer"];
|
|
165
192
|
/** 轮询周期(毫秒):纯程序层,可调密。 */
|
|
166
193
|
const POLL_MS = 18e5;
|
|
167
|
-
const
|
|
168
|
-
const
|
|
194
|
+
const triggerDir$1 = () => `${vaultPath()}/90-Meta/triggers`;
|
|
195
|
+
const snapshotFile = () => `${vaultPath()}/90-Meta/mock-data/last-snapshot.json`;
|
|
169
196
|
/** trigger 文件 TTL:超过即视为已消费/过期,轮询成功路径顺带清理。 */
|
|
170
197
|
const TRIGGER_TTL_MS = 6048e5;
|
|
171
198
|
const CREDENTIAL_ALERT_COOLDOWN_MS = 36e5;
|
|
@@ -211,7 +238,7 @@ function schedulePolling(ctx) {
|
|
|
211
238
|
const snap = await snapshot();
|
|
212
239
|
const prev = await readPrev();
|
|
213
240
|
if (!prev) {
|
|
214
|
-
await writeFile(
|
|
241
|
+
await writeFile(snapshotFile(), JSON.stringify(snap), "utf8");
|
|
215
242
|
return;
|
|
216
243
|
}
|
|
217
244
|
const fresh = diff(prev, snap);
|
|
@@ -223,9 +250,9 @@ function schedulePolling(ctx) {
|
|
|
223
250
|
if (fresh.newApprovals.length) parts.push(`新审批 ${fresh.newApprovals.length} 件`);
|
|
224
251
|
await notifySelf(uid, `[Sun·info] ${parts.join("、")},已入触发队列(${(/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN")})`);
|
|
225
252
|
}
|
|
226
|
-
await writeFile(
|
|
253
|
+
await writeFile(snapshotFile(), JSON.stringify(snap), "utf8");
|
|
227
254
|
health.fails = 0;
|
|
228
|
-
await pruneTriggers(
|
|
255
|
+
await pruneTriggers(triggerDir$1()).catch(() => {});
|
|
229
256
|
} catch (e) {
|
|
230
257
|
ctx.logger?.warn?.(`[sun-space] poll failed: ${String(e.message).slice(0, 150)}`);
|
|
231
258
|
health.fails++;
|
|
@@ -256,15 +283,15 @@ function schedulePolling(ctx) {
|
|
|
256
283
|
}
|
|
257
284
|
async function readPrev() {
|
|
258
285
|
try {
|
|
259
|
-
return JSON.parse(await readFile(
|
|
286
|
+
return JSON.parse(await readFile(snapshotFile(), "utf8"));
|
|
260
287
|
} catch {
|
|
261
288
|
return null;
|
|
262
289
|
}
|
|
263
290
|
}
|
|
264
291
|
async function writeTrigger(label, payload) {
|
|
265
|
-
await mkdir(
|
|
292
|
+
await mkdir(triggerDir$1(), { recursive: true });
|
|
266
293
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
267
|
-
await writeFile(join(
|
|
294
|
+
await writeFile(join(triggerDir$1(), `${ts}-${label}.json`), JSON.stringify({
|
|
268
295
|
label,
|
|
269
296
|
payload
|
|
270
297
|
}, null, 2), "utf8");
|
|
@@ -307,7 +334,6 @@ async function writeTrigger(label, payload) {
|
|
|
307
334
|
var worker_watchdog_exports = /* @__PURE__ */ __exportAll({
|
|
308
335
|
DEFAULT_STALL_MS: () => DEFAULT_STALL_MS,
|
|
309
336
|
DEFAULT_TICK_MS: () => DEFAULT_TICK_MS,
|
|
310
|
-
VAULT: () => VAULT,
|
|
311
337
|
apply: () => apply$1,
|
|
312
338
|
inject: () => inject$1,
|
|
313
339
|
isDelegatedChildHeader: () => isDelegatedChildHeader,
|
|
@@ -318,7 +344,7 @@ var worker_watchdog_exports = /* @__PURE__ */ __exportAll({
|
|
|
318
344
|
});
|
|
319
345
|
const name$1 = "worker-watchdog";
|
|
320
346
|
const inject$1 = ["timer"];
|
|
321
|
-
const
|
|
347
|
+
const triggerDir = () => `${vaultPath()}/90-Meta/triggers`;
|
|
322
348
|
/** 默认停滞阈值 15 分钟:长思考/长命令完成后即重置计时,真卡死撑不到这么久。 */
|
|
323
349
|
const DEFAULT_STALL_MS = 9e5;
|
|
324
350
|
/** 巡检间隔 30 秒。 */
|
|
@@ -375,9 +401,9 @@ function apply$1(ctx) {
|
|
|
375
401
|
const idleMin = Math.round((Date.now() - entry.at) / 6e4);
|
|
376
402
|
log("warn", `stalled: child=${entry.id} idle=${idleMin}min — interrupting & notifying`);
|
|
377
403
|
try {
|
|
378
|
-
await mkdir(
|
|
404
|
+
await mkdir(triggerDir(), { recursive: true });
|
|
379
405
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
380
|
-
await writeFile(join(
|
|
406
|
+
await writeFile(join(triggerDir(), `${ts}-watchdog-stall.json`), JSON.stringify({
|
|
381
407
|
label: "watchdog-stall",
|
|
382
408
|
payload: {
|
|
383
409
|
childId: entry.id,
|
|
@@ -461,7 +487,7 @@ function apply$1(ctx) {
|
|
|
461
487
|
const name = "sun-space";
|
|
462
488
|
const inject = ["timer", "tools"];
|
|
463
489
|
function apply(ctx) {
|
|
464
|
-
|
|
490
|
+
publishVaultState();
|
|
465
491
|
ctx.plugin(source_exports);
|
|
466
492
|
ctx.plugin(worker_watchdog_exports);
|
|
467
493
|
}
|
package/lib/router-tools.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promise
|
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
5
6
|
// src/vault.ts
|
|
6
7
|
/**
|
|
7
8
|
* vault 访问:路径守卫 + frontmatter 解析/序列化。纯函数为主,可单测。
|
|
@@ -190,26 +191,49 @@ async function dingSelf(userId, content, type = "app") {
|
|
|
190
191
|
*
|
|
191
192
|
* 解析优先级:
|
|
192
193
|
* ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
|
|
193
|
-
* ②
|
|
194
|
+
* ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
|
|
194
195
|
* ③ 默认约定 ~/Workspace/sun-vault
|
|
195
196
|
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
197
|
+
* 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
|
|
198
|
+
* 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
|
|
199
|
+
* 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
|
|
199
200
|
*
|
|
200
|
-
*
|
|
201
|
+
* 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
|
|
202
|
+
* 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
|
|
201
203
|
*/
|
|
202
204
|
const DEFAULT_VAULT_DIRNAME = "sun-vault";
|
|
203
|
-
/**
|
|
204
|
-
|
|
205
|
-
|
|
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;
|
|
206
210
|
try {
|
|
207
|
-
const cfg = JSON.parse(
|
|
211
|
+
const cfg = JSON.parse(configJson ?? "");
|
|
208
212
|
if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
|
|
209
213
|
} catch {}
|
|
210
214
|
return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
|
|
211
215
|
}
|
|
212
|
-
|
|
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();
|
|
213
237
|
// src/router-tools.ts
|
|
214
238
|
const inject = ["tools", "subagents"];
|
|
215
239
|
const text = (s) => [{
|
|
@@ -290,7 +314,7 @@ function apply(ctx) {
|
|
|
290
314
|
}, async (args) => {
|
|
291
315
|
try {
|
|
292
316
|
if (args.what === "triggers") {
|
|
293
|
-
const dir = resolveSafe(
|
|
317
|
+
const dir = resolveSafe(vaultPath(), "90-Meta/triggers");
|
|
294
318
|
try {
|
|
295
319
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, 10);
|
|
296
320
|
const triggers = [];
|
|
@@ -322,7 +346,7 @@ function apply(ctx) {
|
|
|
322
346
|
}
|
|
323
347
|
if (args.what === "journal") {
|
|
324
348
|
const d = args.date ?? localDate();
|
|
325
|
-
const p = resolveRead(
|
|
349
|
+
const p = resolveRead(vaultPath(), `40-Journal/${d}.md`);
|
|
326
350
|
const raw = await readFile(p, "utf8");
|
|
327
351
|
if (args.full) return {
|
|
328
352
|
ok: true,
|
|
@@ -337,7 +361,7 @@ function apply(ctx) {
|
|
|
337
361
|
};
|
|
338
362
|
}
|
|
339
363
|
if (args.what === "tasks") {
|
|
340
|
-
const dir = resolveSafe(
|
|
364
|
+
const dir = resolveSafe(vaultPath(), "50-Tasks");
|
|
341
365
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".md"));
|
|
342
366
|
const tasks = [];
|
|
343
367
|
for (const f of files) {
|
|
@@ -360,7 +384,7 @@ function apply(ctx) {
|
|
|
360
384
|
};
|
|
361
385
|
}
|
|
362
386
|
if (args.what === "runbook" || args.what === "memory") {
|
|
363
|
-
const p = resolveSafe(
|
|
387
|
+
const p = resolveSafe(vaultPath(), `90-Meta/${args.what}.md`);
|
|
364
388
|
return {
|
|
365
389
|
ok: true,
|
|
366
390
|
data: { text: (await readFile(p, "utf8")).slice(0, 8e3) }
|
|
@@ -371,7 +395,7 @@ function apply(ctx) {
|
|
|
371
395
|
ok: false,
|
|
372
396
|
note: "note 需要 path"
|
|
373
397
|
};
|
|
374
|
-
const raw = await readFile(resolveRead(
|
|
398
|
+
const raw = await readFile(resolveRead(vaultPath(), args.path), "utf8");
|
|
375
399
|
if (args.full) return {
|
|
376
400
|
ok: true,
|
|
377
401
|
data: { text: raw.slice(0, 8e3) }
|
|
@@ -415,7 +439,7 @@ function apply(ctx) {
|
|
|
415
439
|
required: ["path", "content"]
|
|
416
440
|
}, async (args) => {
|
|
417
441
|
try {
|
|
418
|
-
const p = resolveSafe(
|
|
442
|
+
const p = resolveSafe(vaultPath(), args.path);
|
|
419
443
|
await mkdir(join(p, ".."), { recursive: true });
|
|
420
444
|
if (args.mode === "append") await appendFile(p, args.content, "utf8");
|
|
421
445
|
else await writeFile(p, args.content, "utf8");
|
|
@@ -511,7 +535,7 @@ function apply(ctx) {
|
|
|
511
535
|
required: ["path", "fields"]
|
|
512
536
|
}, async (args) => {
|
|
513
537
|
try {
|
|
514
|
-
const p = resolveSafe(
|
|
538
|
+
const p = resolveSafe(vaultPath(), args.path);
|
|
515
539
|
const updated = updateFrontmatter(await readFile(p, "utf8"), args.fields);
|
|
516
540
|
await writeFile(p, updated, "utf8");
|
|
517
541
|
return {
|
|
@@ -585,7 +609,7 @@ function apply(ctx) {
|
|
|
585
609
|
};
|
|
586
610
|
}
|
|
587
611
|
try {
|
|
588
|
-
const p = resolveSafe(
|
|
612
|
+
const p = resolveSafe(vaultPath(), `50-Tasks/${args.task_id}.md`);
|
|
589
613
|
const raw = await readFile(p, "utf8");
|
|
590
614
|
await writeFile(p, updateFrontmatter(raw, { session: String(activation.childId ?? "") }), "utf8");
|
|
591
615
|
} catch {}
|
|
@@ -597,4 +621,4 @@ function apply(ctx) {
|
|
|
597
621
|
}));
|
|
598
622
|
ctx.logger?.info?.("[sun-space] router tools registered: sun_read sun_vault_write sun_vault_meta sun_notify sun_route(subagents)");
|
|
599
623
|
}
|
|
600
|
-
export {
|
|
624
|
+
export { apply, inject, lossless };
|
package/lib/worker-vault.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
4
5
|
// src/vault.ts
|
|
5
6
|
/**
|
|
6
7
|
* vault 访问:路径守卫 + frontmatter 解析/序列化。纯函数为主,可单测。
|
|
@@ -33,26 +34,49 @@ function resolveSafe(vaultRoot, rel) {
|
|
|
33
34
|
*
|
|
34
35
|
* 解析优先级:
|
|
35
36
|
* ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
|
|
36
|
-
* ②
|
|
37
|
+
* ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
|
|
37
38
|
* ③ 默认约定 ~/Workspace/sun-vault
|
|
38
39
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
|
|
41
|
+
* 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
|
|
42
|
+
* 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
|
|
42
43
|
*
|
|
43
|
-
*
|
|
44
|
+
* 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
|
|
45
|
+
* 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
|
|
44
46
|
*/
|
|
45
47
|
const DEFAULT_VAULT_DIRNAME = "sun-vault";
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
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;
|
|
49
53
|
try {
|
|
50
|
-
const cfg = JSON.parse(
|
|
54
|
+
const cfg = JSON.parse(configJson ?? "");
|
|
51
55
|
if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
|
|
52
56
|
} catch {}
|
|
53
57
|
return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
|
|
54
58
|
}
|
|
55
|
-
|
|
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();
|
|
56
80
|
// src/worker-vault.ts
|
|
57
81
|
const inject = ["tools"];
|
|
58
82
|
const TASK_ID = /^T-\d{8}-\d{2}$/;
|
|
@@ -104,7 +128,7 @@ function apply(ctx) {
|
|
|
104
128
|
const { task_id: taskId, filename, content } = args ?? {};
|
|
105
129
|
try {
|
|
106
130
|
const rel = artifactPath(String(taskId), String(filename));
|
|
107
|
-
const abs = resolveSafe(
|
|
131
|
+
const abs = resolveSafe(vaultPath(), rel);
|
|
108
132
|
await mkdir(join(abs, ".."), { recursive: true });
|
|
109
133
|
await writeFile(abs, String(content ?? ""), "utf8");
|
|
110
134
|
return {
|
|
@@ -125,4 +149,4 @@ function apply(ctx) {
|
|
|
125
149
|
});
|
|
126
150
|
ctx.logger?.info?.("[sun-space] worker artifact tool registered: sun_artifact_write");
|
|
127
151
|
}
|
|
128
|
-
export {
|
|
152
|
+
export { apply, artifactPath, inject };
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -22,7 +22,7 @@ window.__ModuleLoader__.load({
|
|
|
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' },
|
|
@@ -103,14 +103,19 @@ window.__ModuleLoader__.load({
|
|
|
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
|
-
// ①
|
|
107
|
-
|
|
108
|
-
const raw = await readVaultFile(scope, 'sun-space.json', 4)
|
|
109
|
-
const cfg = JSON.parse(raw)
|
|
110
|
-
if (cfg?.vault && cfg.vault.startsWith('/')) return cfg.vault
|
|
111
|
-
} catch { /* 无发现文件 → 走约定探测 */ }
|
|
112
|
-
// ② 约定布局探测(默认 ~/Workspace/sun-vault;自定义 vault 路径由①覆盖)
|
|
106
|
+
// ① 插件状态目录(HOME 从会话 cwd 推导,绝对路径读取):
|
|
107
|
+
// config.json 优先(运行期事实源——引导初始化/用户手写即时生效),state.json 兜底(启动期快照)
|
|
113
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 路径由①覆盖)
|
|
114
119
|
const candidates = [...new Set(homes.map(h => `${h}/Workspace/${VAULT_DIRNAME}`))]
|
|
115
120
|
for (const cand of candidates) {
|
|
116
121
|
try {
|
|
@@ -262,6 +267,17 @@ window.__ModuleLoader__.load({
|
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
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
|
+
|
|
265
281
|
// 创建 Router 会话 → 自登记 state.json → 跳转 → 重检(InitPanel 与 OnboardPanel 共用)
|
|
266
282
|
async function createAndBootstrap(checks, onRetry) {
|
|
267
283
|
try {
|
|
@@ -384,10 +400,11 @@ window.__ModuleLoader__.load({
|
|
|
384
400
|
|
|
385
401
|
// 新用户三步启用引导(主理鱼同款编号步骤卡;替代旧的一锅炖检查面板)
|
|
386
402
|
function OnboardPanel({ checks, currentSessionId, onRetry }) {
|
|
403
|
+
const [initBusy, setInitBusy] = React.useState(false)
|
|
387
404
|
const steps = [
|
|
388
405
|
{ n: 1, ok: checks.presets, title: '确认依赖插件', desc: checks.presets ? '智能体协作 / 子智能体插件已启用' : '打开插件面板,启用 sun-space / 智能体协作相关插件后重启实例', action: checks.presets ? null : 'plugins' },
|
|
389
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 },
|
|
390
|
-
{ 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' },
|
|
391
408
|
]
|
|
392
409
|
const allOk = steps.every(x => x.ok)
|
|
393
410
|
return h('div', null, [
|
|
@@ -401,6 +418,20 @@ window.__ModuleLoader__.load({
|
|
|
401
418
|
]),
|
|
402
419
|
st.action === 'plugins' ? h('button', { key: 'a', onClick: () => { try { hostCtx.layout.selectPanel('plugins') } catch (e) { D.errors.push(String(e)) } }, style: btnStyle }, '打开插件面板') : null,
|
|
403
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,
|
|
404
435
|
])),
|
|
405
436
|
allOk ? h('p', { key: 'done', style: { color: '#57b26a', fontSize: 13 } }, '✓ 全部就绪——到「定时任务」用模板创建晨报等四条生产任务,即可开始使用。') : null,
|
|
406
437
|
h('button', { key: 'retry', onClick: onRetry, style: { ...btnStyle, marginTop: 10 } }, '↻ 重新检查'),
|