dsh-sun-space 0.2.9 → 0.3.0

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 CHANGED
@@ -22,11 +22,12 @@ var __exportAll = (all, no_symbols) => {
22
22
  * ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
23
23
  * ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
24
24
  * ③ ~/.dsh/state/sun-space/state.json 的 vault 字段(Router 会话创建时插件自动登记 = 用户选定的工作区目录)
25
- * ④ 默认约定 ~/Workspace/sun-vault
25
+ * ④ 会话工作目录(调用方从 exec 提取传入——用户在哪个工作区开会话,vault 就解析到哪)
26
+ * ⑤ 默认约定 ~/Workspace/sun-vault
26
27
  *
27
28
  * ③ 的设计动机:用户的 vault 就是他在 DSH 里选定的那个目录——Router 会话创建时
28
29
  * 插件自动把该会话 cwd 登记为 vault 根(registerVaultRootFromSession),无需任何
29
- * 手工配置。①② 供自定义/覆盖;④ 仅在全新裸环境兜底。
30
+ * 手工配置。①② 供自定义/覆盖;⑤ 仅在全新裸环境兜底。
30
31
  *
31
32
  * 解析结果写回 state.json,作为浏览器端(孙学空间)的发现通道——浏览器经
32
33
  * workspaceFiles/read 以绝对路径读取该文件(工作区外绝对路径读取被宿主允许)。
@@ -38,13 +39,14 @@ const DEFAULT_VAULT_DIRNAME = "sun-vault";
38
39
  /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
39
40
  * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
40
41
  const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
41
- /** 纯函数:四级优先解析(测试与 vaultPath 共用)。 */
42
- function computeVaultPath(envValue, configJson, stateJson, home = homedir()) {
42
+ /** 纯函数:五级优先解析(测试与 vaultPath 共用)。 */
43
+ function computeVaultPath(envValue, configJson, stateJson, sessionCwd, home = homedir()) {
43
44
  if (envValue && envValue.trim() !== "") return envValue;
44
45
  for (const raw of [configJson, stateJson]) try {
45
46
  const parsed = JSON.parse(raw ?? "");
46
47
  if (typeof parsed?.vault === "string" && parsed.vault.startsWith("/")) return parsed.vault;
47
48
  } catch {}
49
+ if (sessionCwd && sessionCwd.startsWith("/")) return sessionCwd;
48
50
  return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
49
51
  }
50
52
  let cached = {
@@ -52,8 +54,9 @@ let cached = {
52
54
  key: "",
53
55
  value: ""
54
56
  };
55
- /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。 */
56
- function vaultPath(now = Date.now()) {
57
+ /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。
58
+ * sessionCwd:调用方从 exec 提取的会话工作目录(可选)——用户在哪个工作区开会话,vault 就解析到哪。 */
59
+ function vaultPath(sessionCwd, now = Date.now()) {
57
60
  const dir = stateDir();
58
61
  let configJson;
59
62
  let stateJson;
@@ -63,9 +66,9 @@ function vaultPath(now = Date.now()) {
63
66
  try {
64
67
  stateJson = readFileSync(join(dir, "state.json"), "utf8");
65
68
  } catch {}
66
- const key = `${configJson ?? ""}|${stateJson ?? ""}`;
69
+ const key = `${sessionCwd ?? ""}|${configJson ?? ""}|${stateJson ?? ""}`;
67
70
  if (now - cached.at < 6e4 && cached.key === key) return cached.value;
68
- const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson);
71
+ const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson, sessionCwd);
69
72
  cached = {
70
73
  at: now,
71
74
  key,
@@ -110,7 +113,7 @@ function publishVaultState(now = /* @__PURE__ */ new Date()) {
110
113
  try {
111
114
  state = JSON.parse(readFileSync(p, "utf8"));
112
115
  } catch {}
113
- state.vault = vaultPath(now.getTime());
116
+ state.vault = vaultPath(void 0, now.getTime());
114
117
  state.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")}`;
115
118
  writeFileSync(p, JSON.stringify(state, null, 2), "utf8");
116
119
  } catch {}
@@ -193,11 +193,12 @@ async function dingSelf(userId, content, type = "app") {
193
193
  * ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
194
194
  * ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
195
195
  * ③ ~/.dsh/state/sun-space/state.json 的 vault 字段(Router 会话创建时插件自动登记 = 用户选定的工作区目录)
196
- * ④ 默认约定 ~/Workspace/sun-vault
196
+ * ④ 会话工作目录(调用方从 exec 提取传入——用户在哪个工作区开会话,vault 就解析到哪)
197
+ * ⑤ 默认约定 ~/Workspace/sun-vault
197
198
  *
198
199
  * ③ 的设计动机:用户的 vault 就是他在 DSH 里选定的那个目录——Router 会话创建时
199
200
  * 插件自动把该会话 cwd 登记为 vault 根(registerVaultRootFromSession),无需任何
200
- * 手工配置。①② 供自定义/覆盖;④ 仅在全新裸环境兜底。
201
+ * 手工配置。①② 供自定义/覆盖;⑤ 仅在全新裸环境兜底。
201
202
  *
202
203
  * 解析结果写回 state.json,作为浏览器端(孙学空间)的发现通道——浏览器经
203
204
  * workspaceFiles/read 以绝对路径读取该文件(工作区外绝对路径读取被宿主允许)。
@@ -209,13 +210,14 @@ const DEFAULT_VAULT_DIRNAME = "sun-vault";
209
210
  /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
210
211
  * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
211
212
  const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
212
- /** 纯函数:四级优先解析(测试与 vaultPath 共用)。 */
213
- function computeVaultPath(envValue, configJson, stateJson, home = homedir()) {
213
+ /** 纯函数:五级优先解析(测试与 vaultPath 共用)。 */
214
+ function computeVaultPath(envValue, configJson, stateJson, sessionCwd, home = homedir()) {
214
215
  if (envValue && envValue.trim() !== "") return envValue;
215
216
  for (const raw of [configJson, stateJson]) try {
216
217
  const parsed = JSON.parse(raw ?? "");
217
218
  if (typeof parsed?.vault === "string" && parsed.vault.startsWith("/")) return parsed.vault;
218
219
  } catch {}
220
+ if (sessionCwd && sessionCwd.startsWith("/")) return sessionCwd;
219
221
  return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
220
222
  }
221
223
  let cached = {
@@ -223,8 +225,9 @@ let cached = {
223
225
  key: "",
224
226
  value: ""
225
227
  };
226
- /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。 */
227
- function vaultPath(now = Date.now()) {
228
+ /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。
229
+ * sessionCwd:调用方从 exec 提取的会话工作目录(可选)——用户在哪个工作区开会话,vault 就解析到哪。 */
230
+ function vaultPath(sessionCwd, now = Date.now()) {
228
231
  const dir = stateDir();
229
232
  let configJson;
230
233
  let stateJson;
@@ -234,9 +237,9 @@ function vaultPath(now = Date.now()) {
234
237
  try {
235
238
  stateJson = readFileSync(join(dir, "state.json"), "utf8");
236
239
  } catch {}
237
- const key = `${configJson ?? ""}|${stateJson ?? ""}`;
240
+ const key = `${sessionCwd ?? ""}|${configJson ?? ""}|${stateJson ?? ""}`;
238
241
  if (now - cached.at < 6e4 && cached.key === key) return cached.value;
239
- const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson);
242
+ const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson, sessionCwd);
240
243
  cached = {
241
244
  at: now,
242
245
  key,
@@ -247,6 +250,12 @@ function vaultPath(now = Date.now()) {
247
250
  vaultPath();
248
251
  // src/router-tools.ts
249
252
  const inject = ["tools", "subagents"];
253
+ /** 从工具执行上下文提取会话工作目录(用户在哪个工作区开会话,vault 就解析到哪)。防御式:形状未知时返回 undefined。 */
254
+ function sessionCwdOf(exec) {
255
+ const e = exec;
256
+ const c = e?.agent?.session?.cwd ?? e?.agent?.session?.header?.cwd ?? e?.session?.cwd;
257
+ return typeof c === "string" && c.startsWith("/") ? c : void 0;
258
+ }
250
259
  const text = (s) => [{
251
260
  type: "text",
252
261
  text: s
@@ -322,10 +331,10 @@ function apply(ctx) {
322
331
  }
323
332
  },
324
333
  required: ["what"]
325
- }, async (args) => {
334
+ }, async (args, exec) => {
326
335
  try {
327
336
  if (args.what === "triggers") {
328
- const dir = resolveSafe(vaultPath(), "90-Meta/triggers");
337
+ const dir = resolveSafe(vaultPath(sessionCwdOf(exec)), "90-Meta/triggers");
329
338
  try {
330
339
  const files = (await readdir(dir)).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, 10);
331
340
  const triggers = [];
@@ -357,7 +366,7 @@ function apply(ctx) {
357
366
  }
358
367
  if (args.what === "journal") {
359
368
  const d = args.date ?? localDate();
360
- const p = resolveRead(vaultPath(), `40-Journal/${d}.md`);
369
+ const p = resolveRead(vaultPath(sessionCwdOf(exec)), `40-Journal/${d}.md`);
361
370
  const raw = await readFile(p, "utf8");
362
371
  if (args.full) return {
363
372
  ok: true,
@@ -372,7 +381,7 @@ function apply(ctx) {
372
381
  };
373
382
  }
374
383
  if (args.what === "tasks") {
375
- const dir = resolveSafe(vaultPath(), "50-Tasks");
384
+ const dir = resolveSafe(vaultPath(sessionCwdOf(exec)), "50-Tasks");
376
385
  const files = (await readdir(dir)).filter((f) => f.endsWith(".md"));
377
386
  const tasks = [];
378
387
  for (const f of files) {
@@ -395,7 +404,7 @@ function apply(ctx) {
395
404
  };
396
405
  }
397
406
  if (args.what === "runbook" || args.what === "memory") {
398
- const p = resolveSafe(vaultPath(), `90-Meta/${args.what}.md`);
407
+ const p = resolveSafe(vaultPath(sessionCwdOf(exec)), `90-Meta/${args.what}.md`);
399
408
  return {
400
409
  ok: true,
401
410
  data: { text: (await readFile(p, "utf8")).slice(0, 8e3) }
@@ -406,7 +415,7 @@ function apply(ctx) {
406
415
  ok: false,
407
416
  note: "note 需要 path"
408
417
  };
409
- const raw = await readFile(resolveRead(vaultPath(), args.path), "utf8");
418
+ const raw = await readFile(resolveRead(vaultPath(sessionCwdOf(exec)), args.path), "utf8");
410
419
  if (args.full) return {
411
420
  ok: true,
412
421
  data: { text: raw.slice(0, 8e3) }
@@ -448,9 +457,9 @@ function apply(ctx) {
448
457
  }
449
458
  },
450
459
  required: ["path", "content"]
451
- }, async (args) => {
460
+ }, async (args, exec) => {
452
461
  try {
453
- const p = resolveSafe(vaultPath(), args.path);
462
+ const p = resolveSafe(vaultPath(sessionCwdOf(exec)), args.path);
454
463
  await mkdir(join(p, ".."), { recursive: true });
455
464
  if (args.mode === "append") await appendFile(p, args.content, "utf8");
456
465
  else await writeFile(p, args.content, "utf8");
@@ -544,9 +553,9 @@ function apply(ctx) {
544
553
  }
545
554
  },
546
555
  required: ["path", "fields"]
547
- }, async (args) => {
556
+ }, async (args, exec) => {
548
557
  try {
549
- const p = resolveSafe(vaultPath(), args.path);
558
+ const p = resolveSafe(vaultPath(sessionCwdOf(exec)), args.path);
550
559
  const updated = updateFrontmatter(await readFile(p, "utf8"), args.fields);
551
560
  await writeFile(p, updated, "utf8");
552
561
  return {
@@ -620,7 +629,7 @@ function apply(ctx) {
620
629
  };
621
630
  }
622
631
  try {
623
- const p = resolveSafe(vaultPath(), `50-Tasks/${args.task_id}.md`);
632
+ const p = resolveSafe(vaultPath(sessionCwdOf(exec)), `50-Tasks/${args.task_id}.md`);
624
633
  const raw = await readFile(p, "utf8");
625
634
  await writeFile(p, updateFrontmatter(raw, { session: String(activation.childId ?? "") }), "utf8");
626
635
  } catch {}
@@ -36,11 +36,12 @@ function resolveSafe(vaultRoot, rel) {
36
36
  * ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
37
37
  * ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
38
38
  * ③ ~/.dsh/state/sun-space/state.json 的 vault 字段(Router 会话创建时插件自动登记 = 用户选定的工作区目录)
39
- * ④ 默认约定 ~/Workspace/sun-vault
39
+ * ④ 会话工作目录(调用方从 exec 提取传入——用户在哪个工作区开会话,vault 就解析到哪)
40
+ * ⑤ 默认约定 ~/Workspace/sun-vault
40
41
  *
41
42
  * ③ 的设计动机:用户的 vault 就是他在 DSH 里选定的那个目录——Router 会话创建时
42
43
  * 插件自动把该会话 cwd 登记为 vault 根(registerVaultRootFromSession),无需任何
43
- * 手工配置。①② 供自定义/覆盖;④ 仅在全新裸环境兜底。
44
+ * 手工配置。①② 供自定义/覆盖;⑤ 仅在全新裸环境兜底。
44
45
  *
45
46
  * 解析结果写回 state.json,作为浏览器端(孙学空间)的发现通道——浏览器经
46
47
  * workspaceFiles/read 以绝对路径读取该文件(工作区外绝对路径读取被宿主允许)。
@@ -52,13 +53,14 @@ const DEFAULT_VAULT_DIRNAME = "sun-vault";
52
53
  /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
53
54
  * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
54
55
  const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
55
- /** 纯函数:四级优先解析(测试与 vaultPath 共用)。 */
56
- function computeVaultPath(envValue, configJson, stateJson, home = homedir()) {
56
+ /** 纯函数:五级优先解析(测试与 vaultPath 共用)。 */
57
+ function computeVaultPath(envValue, configJson, stateJson, sessionCwd, home = homedir()) {
57
58
  if (envValue && envValue.trim() !== "") return envValue;
58
59
  for (const raw of [configJson, stateJson]) try {
59
60
  const parsed = JSON.parse(raw ?? "");
60
61
  if (typeof parsed?.vault === "string" && parsed.vault.startsWith("/")) return parsed.vault;
61
62
  } catch {}
63
+ if (sessionCwd && sessionCwd.startsWith("/")) return sessionCwd;
62
64
  return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
63
65
  }
64
66
  let cached = {
@@ -66,8 +68,9 @@ let cached = {
66
68
  key: "",
67
69
  value: ""
68
70
  };
69
- /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。 */
70
- function vaultPath(now = Date.now()) {
71
+ /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。
72
+ * sessionCwd:调用方从 exec 提取的会话工作目录(可选)——用户在哪个工作区开会话,vault 就解析到哪。 */
73
+ function vaultPath(sessionCwd, now = Date.now()) {
71
74
  const dir = stateDir();
72
75
  let configJson;
73
76
  let stateJson;
@@ -77,9 +80,9 @@ function vaultPath(now = Date.now()) {
77
80
  try {
78
81
  stateJson = readFileSync(join(dir, "state.json"), "utf8");
79
82
  } catch {}
80
- const key = `${configJson ?? ""}|${stateJson ?? ""}`;
83
+ const key = `${sessionCwd ?? ""}|${configJson ?? ""}|${stateJson ?? ""}`;
81
84
  if (now - cached.at < 6e4 && cached.key === key) return cached.value;
82
- const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson);
85
+ const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson, sessionCwd);
83
86
  cached = {
84
87
  at: now,
85
88
  key,
@@ -90,6 +93,12 @@ function vaultPath(now = Date.now()) {
90
93
  vaultPath();
91
94
  // src/worker-vault.ts
92
95
  const inject = ["tools"];
96
+ /** 从工具执行上下文提取会话工作目录(同 router-tools)。 */
97
+ function sessionCwdOf(exec) {
98
+ const e = exec;
99
+ const c = e?.agent?.session?.cwd ?? e?.agent?.session?.header?.cwd ?? e?.session?.cwd;
100
+ return typeof c === "string" && c.startsWith("/") ? c : void 0;
101
+ }
93
102
  const TASK_ID = /^T-\d{8}-\d{2}$/;
94
103
  /** 纯函数:拼装受控产物路径;非法输入抛 VaultPathError。可单测。 */
95
104
  function artifactPath(taskId, filename) {
@@ -135,11 +144,11 @@ function apply(ctx) {
135
144
  text: JSON.stringify(value)
136
145
  }]
137
146
  },
138
- execute: async (args) => {
147
+ execute: async (args, exec) => {
139
148
  const { task_id: taskId, filename, content } = args ?? {};
140
149
  try {
141
150
  const rel = artifactPath(String(taskId), String(filename));
142
- const abs = resolveSafe(vaultPath(), rel);
151
+ const abs = resolveSafe(vaultPath(sessionCwdOf(exec)), rel);
143
152
  await mkdir(join(abs, ".."), { recursive: true });
144
153
  await writeFile(abs, String(content ?? ""), "utf8");
145
154
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sun-space",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Sun Space:DeepSeek Harness 个人超智能体插件——Router/Worker 双预设工具白名单、钉钉待办轮询、Obsidian 知识库落库、会话工作台与定时任务管理。",
6
6
  "exports": {
@@ -54,4 +54,4 @@
54
54
  "esbuild"
55
55
  ]
56
56
  }
57
- }
57
+ }
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 = 'v17' // 根节点 data-wb-rev 版本标记:判别浏览器是否吃到旧 client.js(immutable 缓存坑,HANDOFF 踩坑 1)
25
+ const WB_REV = 'v18' // 根节点 data-wb-rev 版本标记:判别浏览器是否吃到旧 client.js(immutable 缓存坑,HANDOFF 踩坑 1)
26
26
 
27
27
  const STATUS_META = {
28
28
  proposed: { label: '待确认派发', color: '#9e9e9e' },
@@ -115,7 +115,16 @@ window.__ModuleLoader__.load({
115
115
  } catch { /* 该文件不存在 → 下一个 */ }
116
116
  }
117
117
  }
118
- // ② 约定布局探测(默认 ~/Workspace/sun-vault;自定义 vault 路径由①覆盖)
118
+ // ② 会话 cwd 即 vault(用户"新建目录作为工作区并开会话"流:cwd 下有 90-Meta 即认定)
119
+ try {
120
+ const cwd = String((items.find(s => s.sessionId === scope) ?? {}).cwd ?? '')
121
+ if (cwd && cwd.startsWith('/')) {
122
+ await readVaultFile(scope, `${cwd}/90-Meta/runbook.md`, 2)
123
+ D.vaultFoundNoField = cwd
124
+ return cwd
125
+ }
126
+ } catch { /* 该 cwd 无 90-Meta → 约定兜底 */ }
127
+ // ③ 约定布局探测(默认 ~/Workspace/sun-vault;自定义 vault 路径由①覆盖)
119
128
  const candidates = [...new Set(homes.map(h => `${h}/Workspace/${VAULT_DIRNAME}`))]
120
129
  for (const cand of candidates) {
121
130
  try {
@@ -526,6 +535,7 @@ window.__ModuleLoader__.load({
526
535
  h('div', { key: 'tabs', style: { display: 'flex', gap: 3, marginBottom: 16, background: 'rgba(128,128,128,0.12)', borderRadius: 8, padding: 3 } }, [
527
536
  ['overview', '总览'], ['schedules', '定时任务'], ['setup', '设置'],
528
537
  ].map(([id, label]) => h('button', { key: id, onClick: () => setTab(id), style: { flex: 1, border: 'none', cursor: 'pointer', fontSize: 13, padding: '6px 0', borderRadius: 6, color: 'inherit', background: tab === id ? 'rgba(128,128,128,0.28)' : 'transparent', opacity: tab === id ? 1 : 0.6, transition: 'background 0.15s' } }, label))),
538
+ h('div', { key: 'vp', style: { fontSize: 12, opacity: 0.65, marginBottom: 12 } }, `📚 识别到的知识库:${vault ?? '(未定位——完成下方引导后自动登记)'}`),
529
539
  h('div', { key: 'cnt', style: { display: 'flex', gap: 18, marginBottom: 16 } },
530
540
  counters.map(([label, n, color]) => h('div', { key: label, style: { display: 'flex', alignItems: 'center', gap: 6 } }, [
531
541
  h('span', { key: 'd', style: { width: 8, height: 8, borderRadius: 4, background: color, display: 'inline-block' } }),