dsh-sun-space 0.2.8 → 0.2.9

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
@@ -18,28 +18,32 @@ var __exportAll = (all, no_symbols) => {
18
18
  /**
19
19
  * vault 路径解析——唯一事实源,全插件共享(source/router-tools/worker-vault/worker-watchdog)。
20
20
  *
21
- * 解析优先级:
21
+ * 解析优先级(vaultPath() 每次调用重读,运行期变更即时生效):
22
22
  * ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
23
23
  * ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
24
- * ③ 默认约定 ~/Workspace/sun-vault
24
+ * ③ ~/.dsh/state/sun-space/state.json 的 vault 字段(Router 会话创建时插件自动登记 = 用户选定的工作区目录)
25
+ * ④ 默认约定 ~/Workspace/sun-vault
25
26
  *
26
- * 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
27
- * 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
28
- * 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
27
+ * ③ 的设计动机:用户的 vault 就是他在 DSH 里选定的那个目录——Router 会话创建时
28
+ * 插件自动把该会话 cwd 登记为 vault 根(registerVaultRootFromSession),无需任何
29
+ * 手工配置。①② 供自定义/覆盖;④ 仅在全新裸环境兜底。
29
30
  *
30
- * 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
31
- * 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
31
+ * 解析结果写回 state.json,作为浏览器端(孙学空间)的发现通道——浏览器经
32
+ * workspaceFiles/read 以绝对路径读取该文件(工作区外绝对路径读取被宿主允许)。
33
+ *
34
+ * 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(session-superagent 同款)。
35
+ * 本文件不携带任何个人路径;个人 vault 位置只存在于①②③(均不入库)。
32
36
  */
33
37
  const DEFAULT_VAULT_DIRNAME = "sun-vault";
34
38
  /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
35
39
  * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
36
40
  const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
37
- /** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
38
- function computeVaultPath(envValue, configJson, home = homedir()) {
41
+ /** 纯函数:四级优先解析(测试与 vaultPath 共用)。 */
42
+ function computeVaultPath(envValue, configJson, stateJson, home = homedir()) {
39
43
  if (envValue && envValue.trim() !== "") return envValue;
40
- try {
41
- const cfg = JSON.parse(configJson ?? "");
42
- if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
44
+ for (const raw of [configJson, stateJson]) try {
45
+ const parsed = JSON.parse(raw ?? "");
46
+ if (typeof parsed?.vault === "string" && parsed.vault.startsWith("/")) return parsed.vault;
43
47
  } catch {}
44
48
  return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
45
49
  }
@@ -48,32 +52,67 @@ let cached = {
48
52
  key: "",
49
53
  value: ""
50
54
  };
51
- /** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
55
+ /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。 */
52
56
  function vaultPath(now = Date.now()) {
53
57
  const dir = stateDir();
54
58
  let configJson;
59
+ let stateJson;
55
60
  try {
56
61
  configJson = readFileSync(join(dir, "config.json"), "utf8");
57
62
  } catch {}
58
- if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
59
- const value = computeVaultPath(process.env.SUN_VAULT, configJson);
63
+ try {
64
+ stateJson = readFileSync(join(dir, "state.json"), "utf8");
65
+ } catch {}
66
+ const key = `${configJson ?? ""}|${stateJson ?? ""}`;
67
+ if (now - cached.at < 6e4 && cached.key === key) return cached.value;
68
+ const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson);
60
69
  cached = {
61
70
  at: now,
62
- key: configJson ?? "",
71
+ key,
63
72
  value
64
73
  };
65
74
  return value;
66
75
  }
67
76
  vaultPath();
77
+ /**
78
+ * Router 会话创建时登记 vault 根("当前目录作为 vault"语义的落点)。
79
+ * 仅在更高优先级来源(env/config/state)均未设置时才登记首个值——
80
+ * 多 Router 并存或重复创建时第一个登记者生效。
81
+ * 尽力而为:任何失败静默退场(最坏是回退到约定路径)。
82
+ */
83
+ function registerVaultRootFromSession(cwd, now = /* @__PURE__ */ new Date()) {
84
+ if (!cwd || !cwd.startsWith("/")) return false;
85
+ if (process.env.SUN_VAULT?.trim()) return false;
86
+ try {
87
+ const dir = stateDir();
88
+ mkdirSync(dir, { recursive: true });
89
+ const p = join(dir, "state.json");
90
+ let state = {};
91
+ try {
92
+ state = JSON.parse(readFileSync(p, "utf8"));
93
+ } catch {}
94
+ if (state.vault && state.vault.startsWith("/")) return false;
95
+ state.vault = cwd;
96
+ 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")}`;
97
+ writeFileSync(p, JSON.stringify(state, null, 2), "utf8");
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
68
103
  /** 把解析结果写回状态目录的 state.json(apply 时调用,尽力而为),供客户端发现。 */
69
104
  function publishVaultState(now = /* @__PURE__ */ new Date()) {
70
105
  try {
71
106
  const dir = stateDir();
72
107
  mkdirSync(dir, { recursive: true });
73
- writeFileSync(join(dir, "state.json"), JSON.stringify({
74
- vault: vaultPath(now.getTime()),
75
- 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")}`
76
- }, null, 2), "utf8");
108
+ const p = join(dir, "state.json");
109
+ let state = {};
110
+ try {
111
+ state = JSON.parse(readFileSync(p, "utf8"));
112
+ } catch {}
113
+ state.vault = vaultPath(now.getTime());
114
+ 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
+ writeFileSync(p, JSON.stringify(state, null, 2), "utf8");
77
116
  } catch {}
78
117
  }
79
118
  // src/dws.ts
@@ -493,5 +532,14 @@ function apply(ctx) {
493
532
  publishVaultState();
494
533
  ctx.plugin(source_exports);
495
534
  ctx.plugin(worker_watchdog_exports);
535
+ try {
536
+ ctx.on("agent/created", (...args) => {
537
+ const agent = args[0]?.agent;
538
+ const header = agent?.session?.header;
539
+ if (header?.agentPreset !== "sun-router") return;
540
+ const cwd = header.cwd ?? agent?.cwd;
541
+ registerVaultRootFromSession(typeof cwd === "string" ? cwd : void 0);
542
+ });
543
+ } catch {}
496
544
  }
497
545
  export { apply, inject, name };
@@ -189,28 +189,32 @@ async function dingSelf(userId, content, type = "app") {
189
189
  /**
190
190
  * vault 路径解析——唯一事实源,全插件共享(source/router-tools/worker-vault/worker-watchdog)。
191
191
  *
192
- * 解析优先级:
192
+ * 解析优先级(vaultPath() 每次调用重读,运行期变更即时生效):
193
193
  * ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
194
194
  * ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
195
- * ③ 默认约定 ~/Workspace/sun-vault
195
+ * ③ ~/.dsh/state/sun-space/state.json 的 vault 字段(Router 会话创建时插件自动登记 = 用户选定的工作区目录)
196
+ * ④ 默认约定 ~/Workspace/sun-vault
196
197
  *
197
- * 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
198
- * 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
199
- * 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
198
+ * ③ 的设计动机:用户的 vault 就是他在 DSH 里选定的那个目录——Router 会话创建时
199
+ * 插件自动把该会话 cwd 登记为 vault 根(registerVaultRootFromSession),无需任何
200
+ * 手工配置。①② 供自定义/覆盖;④ 仅在全新裸环境兜底。
200
201
  *
201
- * 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
202
- * 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
202
+ * 解析结果写回 state.json,作为浏览器端(孙学空间)的发现通道——浏览器经
203
+ * workspaceFiles/read 以绝对路径读取该文件(工作区外绝对路径读取被宿主允许)。
204
+ *
205
+ * 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(session-superagent 同款)。
206
+ * 本文件不携带任何个人路径;个人 vault 位置只存在于①②③(均不入库)。
203
207
  */
204
208
  const DEFAULT_VAULT_DIRNAME = "sun-vault";
205
209
  /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
206
210
  * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
207
211
  const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
208
- /** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
209
- function computeVaultPath(envValue, configJson, home = homedir()) {
212
+ /** 纯函数:四级优先解析(测试与 vaultPath 共用)。 */
213
+ function computeVaultPath(envValue, configJson, stateJson, home = homedir()) {
210
214
  if (envValue && envValue.trim() !== "") return envValue;
211
- try {
212
- const cfg = JSON.parse(configJson ?? "");
213
- if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
215
+ for (const raw of [configJson, stateJson]) try {
216
+ const parsed = JSON.parse(raw ?? "");
217
+ if (typeof parsed?.vault === "string" && parsed.vault.startsWith("/")) return parsed.vault;
214
218
  } catch {}
215
219
  return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
216
220
  }
@@ -219,18 +223,23 @@ let cached = {
219
223
  key: "",
220
224
  value: ""
221
225
  };
222
- /** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
226
+ /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。 */
223
227
  function vaultPath(now = Date.now()) {
224
228
  const dir = stateDir();
225
229
  let configJson;
230
+ let stateJson;
226
231
  try {
227
232
  configJson = readFileSync(join(dir, "config.json"), "utf8");
228
233
  } catch {}
229
- if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
230
- const value = computeVaultPath(process.env.SUN_VAULT, configJson);
234
+ try {
235
+ stateJson = readFileSync(join(dir, "state.json"), "utf8");
236
+ } catch {}
237
+ const key = `${configJson ?? ""}|${stateJson ?? ""}`;
238
+ if (now - cached.at < 6e4 && cached.key === key) return cached.value;
239
+ const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson);
231
240
  cached = {
232
241
  at: now,
233
- key: configJson ?? "",
242
+ key,
234
243
  value
235
244
  };
236
245
  return value;
@@ -32,28 +32,32 @@ function resolveSafe(vaultRoot, rel) {
32
32
  /**
33
33
  * vault 路径解析——唯一事实源,全插件共享(source/router-tools/worker-vault/worker-watchdog)。
34
34
  *
35
- * 解析优先级:
35
+ * 解析优先级(vaultPath() 每次调用重读,运行期变更即时生效):
36
36
  * ① 环境变量 SUN_VAULT(启动命令注入,随进程生效)
37
37
  * ② ~/.dsh/state/sun-space/config.json 的 vault 字段(用户手写,持久化,不入库)
38
- * ③ 默认约定 ~/Workspace/sun-vault
38
+ * ③ ~/.dsh/state/sun-space/state.json 的 vault 字段(Router 会话创建时插件自动登记 = 用户选定的工作区目录)
39
+ * ④ 默认约定 ~/Workspace/sun-vault
39
40
  *
40
- * 解析结果写回 ~/.dsh/state/sun-space/state.json,作为浏览器端(孙学空间)的发现
41
- * 通道——浏览器读不到进程环境与插件 config,经 workspaceFiles/read 以绝对路径读取
42
- * 该文件(工作区外绝对路径读取被宿主允许,与 vault 本体读取同机制)。
41
+ * ③ 的设计动机:用户的 vault 就是他在 DSH 里选定的那个目录——Router 会话创建时
42
+ * 插件自动把该会话 cwd 登记为 vault 根(registerVaultRootFromSession),无需任何
43
+ * 手工配置。①② 供自定义/覆盖;④ 仅在全新裸环境兜底。
43
44
  *
44
- * 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(社区插件 session-superagent 同款)。
45
- * 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
45
+ * 解析结果写回 state.json,作为浏览器端(孙学空间)的发现通道——浏览器经
46
+ * workspaceFiles/read 以绝对路径读取该文件(工作区外绝对路径读取被宿主允许)。
47
+ *
48
+ * 目录约定沿用 DSH 官方先例:~/.dsh/state/<插件名>/(session-superagent 同款)。
49
+ * 本文件不携带任何个人路径;个人 vault 位置只存在于①②③(均不入库)。
46
50
  */
47
51
  const DEFAULT_VAULT_DIRNAME = "sun-vault";
48
52
  /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
49
53
  * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
50
54
  const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
51
- /** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
52
- function computeVaultPath(envValue, configJson, home = homedir()) {
55
+ /** 纯函数:四级优先解析(测试与 vaultPath 共用)。 */
56
+ function computeVaultPath(envValue, configJson, stateJson, home = homedir()) {
53
57
  if (envValue && envValue.trim() !== "") return envValue;
54
- try {
55
- const cfg = JSON.parse(configJson ?? "");
56
- if (typeof cfg?.vault === "string" && cfg.vault.startsWith("/")) return cfg.vault;
58
+ for (const raw of [configJson, stateJson]) try {
59
+ const parsed = JSON.parse(raw ?? "");
60
+ if (typeof parsed?.vault === "string" && parsed.vault.startsWith("/")) return parsed.vault;
57
61
  } catch {}
58
62
  return join(home, "Workspace", DEFAULT_VAULT_DIRNAME);
59
63
  }
@@ -62,18 +66,23 @@ let cached = {
62
66
  key: "",
63
67
  value: ""
64
68
  };
65
- /** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
69
+ /** 运行期解析:每次调用重读状态目录(60s 微缓存防抖),登记/手改后即时生效。 */
66
70
  function vaultPath(now = Date.now()) {
67
71
  const dir = stateDir();
68
72
  let configJson;
73
+ let stateJson;
69
74
  try {
70
75
  configJson = readFileSync(join(dir, "config.json"), "utf8");
71
76
  } catch {}
72
- if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
73
- const value = computeVaultPath(process.env.SUN_VAULT, configJson);
77
+ try {
78
+ stateJson = readFileSync(join(dir, "state.json"), "utf8");
79
+ } catch {}
80
+ const key = `${configJson ?? ""}|${stateJson ?? ""}`;
81
+ if (now - cached.at < 6e4 && cached.key === key) return cached.value;
82
+ const value = computeVaultPath(process.env.SUN_VAULT, configJson, stateJson);
74
83
  cached = {
75
84
  at: now,
76
- key: configJson ?? "",
85
+ key,
77
86
  value
78
87
  };
79
88
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sun-space",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "description": "Sun Space:DeepSeek Harness 个人超智能体插件——Router/Worker 双预设工具白名单、钉钉待办轮询、Obsidian 知识库落库、会话工作台与定时任务管理。",
6
6
  "exports": {
package/src/client.js CHANGED
@@ -130,9 +130,11 @@ window.__ModuleLoader__.load({
130
130
  }
131
131
 
132
132
  // ===== Router 会话解析(架构 §4.1 不变量:会话会轮换,呈现层只认 vault state.json + 动态发现)=====
133
- async function resolveRouterSession(vault) {
134
- const v = await gw('session/list', {})
135
- const items = Array.isArray(v?.items) ? v.items : Object.values(v?.items ?? v ?? {})
133
+ async function resolveRouterSession(vault, items) {
134
+ if (!items) {
135
+ const v = await gw('session/list', {})
136
+ items = Array.isArray(v?.items) ? v.items : Object.values(v?.items ?? v ?? {})
137
+ }
136
138
  // 注意:cwd 是"工作区分桶路径"(实例 boot cwd),不是 vault——过滤只看预设(踩坑 5 延伸)
137
139
  const candidates = items.filter(s => s?.projections?.values?.agentPreset === 'sun-router')
138
140
  if (!candidates.length) return null
@@ -176,12 +178,14 @@ window.__ModuleLoader__.load({
176
178
  c.presets = false
177
179
  c.notes.push('预设服务不可达 → 确认 Harness 实例在线、插件已启用')
178
180
  }
179
- // ③ Router 会话:动态解析(state.json → 最近活跃);顺带记录当前会话 cwd(一键初始化用它作 vault 根/工作区)
181
+ // ③ Router 会话:动态解析 + 首个 sun-router 会话创建时自动登记其 cwd 为 vault 根
182
+ // ("当前目录作为 vault"语义的插件层落点;env/config 更高优先级不受影响)
180
183
  try {
181
184
  const sl = await gw('session/list', {})
182
185
  const items = Array.isArray(sl?.items) ? sl.items : Object.values(sl?.items ?? {})
183
186
  c.currentCwd = (items.find(s => s.sessionId === currentSessionId) ?? {}).cwd ?? null
184
- c.routerSession = vault ? await resolveRouterSession(vault) : null
187
+ // vault 根登记由宿主插件在 sun-router 会话创建时自动完成(index.ts agent/created 钩子)
188
+ c.routerSession = vault ? await resolveRouterSession(vault, items) : null
185
189
  if (!c.routerSession) c.notes.push('Router 会话不存在 → 点下方「创建 Router 会话」(sun-router 预设,随当前工作区),创建后自动登记 state.json')
186
190
  } catch {
187
191
  c.notes.push('会话列表不可达 → 重试或检查实例')
@@ -265,13 +269,12 @@ window.__ModuleLoader__.load({
265
269
  sessionId: sid,
266
270
  mode: 'queue',
267
271
  content: [{ type: 'text', text: `【孙学空间初始化】你是新环境的 Sun Router(你的会话 id:${sid})。你的工作目录即知识库根(vault)。按顺序完成:
268
- 1) 把知识库根登记到状态目录 config.json(执行:mkdir -p "\${DSH_HOME:-\$HOME}/.dsh/state/sun-space" && printf '{"vault": "%s"}' "\$(pwd)" > "\${DSH_HOME:-\$HOME}/.dsh/state/sun-space/config.json"——此写在工作区之外,会弹一次审批,等待用户批准后再继续第 2 步)
269
- 2) 用 sun_vault_write 创建知识库骨架(此时插件已从 config.json 动态解析 vault = 你的工作目录):
272
+ 1) 用 sun_vault_write 创建知识库骨架(vault 即你的工作目录,已在会话创建时自动登记):
270
273
  - 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 不带时区后缀、结论必注出处)
271
274
  - 90-Meta/runbook.md:初始版(# Sun 行为手册 + v0.1 版本行 + §1 角色:Router 只分派不执行 + §2 沟通规范:IM ≤100 字结论先行 + §3 打扰分级:info 攒批/ask 立即/urgent 加 DING)
272
275
  - 90-Meta/memory.md:初始版(# 关于用户,待填充)
273
- 3) 用 sun_vault_write 写 90-Meta/system/state.json:{"routerSession": "${sid}", "vault": "<你的工作目录绝对路径>", "updatedAt": "<本地时间 YYYY-MM-DDTHH:mm>"}(自登记,供孙学空间发现与轮换机制使用)
274
- 4) 完成后列出生成的文件清单与知识库路径。` }],
276
+ 2) 用 sun_vault_write 写 90-Meta/system/state.json:{"routerSession": "${sid}", "vault": "<你的工作目录绝对路径>", "updatedAt": "<本地时间 YYYY-MM-DDTHH:mm>"}(自登记,供孙学空间发现与轮换机制使用)
277
+ 3) 完成后列出生成的文件清单与知识库路径。` }],
275
278
  clientTimeZone: 'Asia/Shanghai',
276
279
  })
277
280
  try { hostCtx.uiWorkspace.openSession(sid) } catch (e) { D.errors.push('openSession: ' + String(e)) }