dsh-sun-space 0.2.6 → 0.2.8

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
@@ -31,8 +31,9 @@ var __exportAll = (all, no_symbols) => {
31
31
  * 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
32
32
  */
33
33
  const DEFAULT_VAULT_DIRNAME = "sun-vault";
34
- /** 插件自有状态目录(~/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。 */
35
- const STATE_DIR = join(homedir(), ".dsh", "state", "sun-space");
34
+ /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
35
+ * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
36
+ const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
36
37
  /** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
37
38
  function computeVaultPath(envValue, configJson, home = homedir()) {
38
39
  if (envValue && envValue.trim() !== "") return envValue;
@@ -49,9 +50,10 @@ let cached = {
49
50
  };
50
51
  /** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
51
52
  function vaultPath(now = Date.now()) {
53
+ const dir = stateDir();
52
54
  let configJson;
53
55
  try {
54
- configJson = readFileSync(join(STATE_DIR, "config.json"), "utf8");
56
+ configJson = readFileSync(join(dir, "config.json"), "utf8");
55
57
  } catch {}
56
58
  if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
57
59
  const value = computeVaultPath(process.env.SUN_VAULT, configJson);
@@ -66,8 +68,9 @@ vaultPath();
66
68
  /** 把解析结果写回状态目录的 state.json(apply 时调用,尽力而为),供客户端发现。 */
67
69
  function publishVaultState(now = /* @__PURE__ */ new Date()) {
68
70
  try {
69
- mkdirSync(STATE_DIR, { recursive: true });
70
- writeFileSync(join(STATE_DIR, "state.json"), JSON.stringify({
71
+ const dir = stateDir();
72
+ mkdirSync(dir, { recursive: true });
73
+ writeFileSync(join(dir, "state.json"), JSON.stringify({
71
74
  vault: vaultPath(now.getTime()),
72
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")}`
73
76
  }, null, 2), "utf8");
@@ -202,8 +202,9 @@ async function dingSelf(userId, content, type = "app") {
202
202
  * 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
203
203
  */
204
204
  const DEFAULT_VAULT_DIRNAME = "sun-vault";
205
- /** 插件自有状态目录(~/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。 */
206
- const STATE_DIR = join(homedir(), ".dsh", "state", "sun-space");
205
+ /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
206
+ * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
207
+ const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
207
208
  /** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
208
209
  function computeVaultPath(envValue, configJson, home = homedir()) {
209
210
  if (envValue && envValue.trim() !== "") return envValue;
@@ -220,9 +221,10 @@ let cached = {
220
221
  };
221
222
  /** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
222
223
  function vaultPath(now = Date.now()) {
224
+ const dir = stateDir();
223
225
  let configJson;
224
226
  try {
225
- configJson = readFileSync(join(STATE_DIR, "config.json"), "utf8");
227
+ configJson = readFileSync(join(dir, "config.json"), "utf8");
226
228
  } catch {}
227
229
  if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
228
230
  const value = computeVaultPath(process.env.SUN_VAULT, configJson);
@@ -45,8 +45,9 @@ function resolveSafe(vaultRoot, rel) {
45
45
  * 本文件不携带任何个人路径;个人 vault 位置只存在于①/②(均不入库)。
46
46
  */
47
47
  const DEFAULT_VAULT_DIRNAME = "sun-vault";
48
- /** 插件自有状态目录(~/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。 */
49
- const STATE_DIR = join(homedir(), ".dsh", "state", "sun-space");
48
+ /** 插件自有状态目录(<DSH_HOME 或 ~/>/.dsh/state/sun-space/),沿用官方 state/<插件名> 约定。
49
+ * 跟随 DSH_HOME:多实例(隔离 HOME 的测试环境)之间状态互不串扰。 */
50
+ const stateDir = () => join(process.env.DSH_HOME ?? homedir(), ".dsh", "state", "sun-space");
50
51
  /** 纯函数:给定 env 与 config 内容时的解析结果(测试与 vaultPath 共用)。 */
51
52
  function computeVaultPath(envValue, configJson, home = homedir()) {
52
53
  if (envValue && envValue.trim() !== "") return envValue;
@@ -63,9 +64,10 @@ let cached = {
63
64
  };
64
65
  /** 运行期解析:每次调用重读 config.json(60s 微缓存防抖),"以当前目录初始化"写入后即时生效。 */
65
66
  function vaultPath(now = Date.now()) {
67
+ const dir = stateDir();
66
68
  let configJson;
67
69
  try {
68
- configJson = readFileSync(join(STATE_DIR, "config.json"), "utf8");
70
+ configJson = readFileSync(join(dir, "config.json"), "utf8");
69
71
  } catch {}
70
72
  if (now - cached.at < 6e4 && cached.key === (configJson ?? "")) return cached.value;
71
73
  const value = computeVaultPath(process.env.SUN_VAULT, configJson);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sun-space",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "description": "Sun Space:DeepSeek Harness 个人超智能体插件——Router/Worker 双预设工具白名单、钉钉待办轮询、Obsidian 知识库落库、会话工作台与定时任务管理。",
6
6
  "exports": {
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 = 'v16' // 根节点 data-wb-rev 版本标记:判别浏览器是否吃到旧 client.js(immutable 缓存坑,HANDOFF 踩坑 1)
25
+ const WB_REV = 'v17' // 根节点 data-wb-rev 版本标记:判别浏览器是否吃到旧 client.js(immutable 缓存坑,HANDOFF 踩坑 1)
26
26
 
27
27
  const STATUS_META = {
28
28
  proposed: { label: '待确认派发', color: '#9e9e9e' },
@@ -151,7 +151,7 @@ window.__ModuleLoader__.load({
151
151
 
152
152
  // ===== 初始化检查(在任何会话的 tab 上都能跑;① 用当前会话作 workspaceFiles scope)=====
153
153
  async function runInitChecks(currentSessionId) {
154
- const c = { vault: false, presets: false, routerSession: null, vaultPath: null, notes: [] }
154
+ const c = { vault: false, presets: false, routerSession: null, vaultPath: null, currentCwd: null, notes: [] }
155
155
  // ① 知识库:vault 路径动态发现 + runbook 探测
156
156
  const vault = await resolveVault()
157
157
  c.vaultPath = vault
@@ -176,8 +176,11 @@ window.__ModuleLoader__.load({
176
176
  c.presets = false
177
177
  c.notes.push('预设服务不可达 → 确认 Harness 实例在线、插件已启用')
178
178
  }
179
- // ③ Router 会话:动态解析(state.json → 最近活跃)
179
+ // ③ Router 会话:动态解析(state.json → 最近活跃);顺带记录当前会话 cwd(一键初始化用它作 vault 根/工作区)
180
180
  try {
181
+ const sl = await gw('session/list', {})
182
+ const items = Array.isArray(sl?.items) ? sl.items : Object.values(sl?.items ?? {})
183
+ c.currentCwd = (items.find(s => s.sessionId === currentSessionId) ?? {}).cwd ?? null
181
184
  c.routerSession = vault ? await resolveRouterSession(vault) : null
182
185
  if (!c.routerSession) c.notes.push('Router 会话不存在 → 点下方「创建 Router 会话」(sun-router 预设,随当前工作区),创建后自动登记 state.json')
183
186
  } catch {
@@ -186,31 +189,12 @@ window.__ModuleLoader__.load({
186
189
  return c
187
190
  }
188
191
 
189
- async function createRouterSession() {
190
- // 不传 cwd:会话随当前工作区分桶(与生产会话同桶);cwd=vault 反而会分裂出独立桶(踩坑 5)
191
- return gw('session/create', { agentPreset: 'sun-router' })
192
+ async function createRouterSession(cwd) {
193
+ // cwd 可选:一键初始化时传当前会话的工作区目录——Router 会话落在用户选的工作区
194
+ //(session/create 带 cwd 还会自动注册该工作区);不传则随宿主项目目录(踩坑 5)
195
+ return gw('session/create', cwd ? { cwd, agentPreset: 'sun-router' } : { agentPreset: 'sun-router' })
192
196
  }
193
197
 
194
- // 创建后引导新会话自登记 state.json(workspaceFiles 无写通道,sun_vault_write 是唯一写路径)
195
- async function bootstrapRouterSession(sid, vault) {
196
- const text = '初始化自登记:用 sun_vault_write(mode=overwrite)写 90-Meta/system/state.json,内容 ' +
197
- JSON.stringify({
198
- routerSession: sid,
199
- vault: vault ?? '(vault 绝对路径,用你环境里 sun 工具实际使用的知识库路径)',
200
- updatedAt: new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Shanghai' }),
201
- }) +
202
- ';完成后一句话报告。'
203
- try {
204
- await gw('session/prompt', {
205
- requestId: 'sun-boot-' + Date.now(),
206
- sessionId: sid,
207
- mode: 'queue',
208
- content: [{ type: 'text', text }],
209
- clientTimeZone: 'Asia/Shanghai',
210
- })
211
- return true
212
- } catch (e) { D.errors.push('bootstrapRouterSession: ' + String(e)); return false }
213
- }
214
198
 
215
199
  async function loadTasks(routerSession, vault) {
216
200
  if (!routerSession || !vault) return []
@@ -267,27 +251,50 @@ window.__ModuleLoader__.load({
267
251
  }
268
252
  }
269
253
 
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
-
281
- // 创建 Router 会话 → 自登记 state.json → 跳转 → 重检(InitPanel 与 OnboardPanel 共用)
282
- async function createAndBootstrap(checks, onRetry) {
254
+ // 一键初始化:创建 Router 会话 → 下发合并指令(90-Meta 骨架 + 自登记 state.json)。
255
+ // 全程走 sun_vault_write(vault 白名单内,零审批弹窗)——Router 是 sun-router 预设,
256
+ // 插件运行期动态解析(vaultPath())保证它写入"当前生效"的知识库路径。
257
+ async function createRouterAndInit(checks, onRetry) {
283
258
  try {
284
- const v = await createRouterSession()
259
+ const cwd = checks.currentCwd
260
+ const v = await createRouterSession(cwd)
285
261
  const sid = typeof v === 'string' ? v : (v?.sessionId ?? v)
286
- if (sid) {
287
- await bootstrapRouterSession(sid, checks.vaultPath) // 自登记 state.json(含 vault 字段;失败不阻断——下次检查由最近活跃兜底)
288
- try { hostCtx.uiWorkspace.openSession(sid) } catch (e) { D.errors.push('openSession: ' + String(e)) }
289
- onRetry() // 新会话的 tab 即完整工作台(state.json 已指向它)
290
- }
262
+ if (!sid) return
263
+ await gw('session/prompt', {
264
+ requestId: 'sun-init-' + Date.now(),
265
+ sessionId: sid,
266
+ mode: 'queue',
267
+ 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 = 你的工作目录):
270
+ - 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
+ - 90-Meta/runbook.md:初始版(# Sun 行为手册 + v0.1 版本行 + §1 角色:Router 只分派不执行 + §2 沟通规范:IM ≤100 字结论先行 + §3 打扰分级:info 攒批/ask 立即/urgent 加 DING)
272
+ - 90-Meta/memory.md:初始版(# 关于用户,待填充)
273
+ 3) 用 sun_vault_write 写 90-Meta/system/state.json:{"routerSession": "${sid}", "vault": "<你的工作目录绝对路径>", "updatedAt": "<本地时间 YYYY-MM-DDTHH:mm>"}(自登记,供孙学空间发现与轮换机制使用)
274
+ 4) 完成后列出生成的文件清单与知识库路径。` }],
275
+ clientTimeZone: 'Asia/Shanghai',
276
+ })
277
+ try { hostCtx.uiWorkspace.openSession(sid) } catch (e) { D.errors.push('openSession: ' + String(e)) }
278
+ onRetry()
279
+ } catch (e) { D.errors.push(String(e)) }
280
+ }
281
+
282
+ // Router 已在、知识库缺失时的骨架补建指令(发给现有 Router 会话)
283
+ async function initSkeletonOnRouter(routerSession, onRetry) {
284
+ try {
285
+ await gw('session/prompt', {
286
+ requestId: 'sun-skel-' + Date.now(),
287
+ sessionId: routerSession,
288
+ mode: 'queue',
289
+ content: [{ type: 'text', text: `【知识库骨架补建】用 sun_vault_write 在你的 vault 路径下创建:
290
+ - 90-Meta/schemas.md:frontmatter 契约最小集(通用字段 type/id/status/created/updated;写入纪律同 runbook)
291
+ - 90-Meta/runbook.md:若缺失则写初始版(# Sun 行为手册 + v0.1)
292
+ - 90-Meta/memory.md:若缺失则写初始版(# 关于用户)
293
+ - 90-Meta/system/state.json:{"routerSession": "${routerSession}", "vault": "<你的 vault 绝对路径>", "updatedAt": "<本地时间>"}(已有的话合并更新 routerSession/vault 字段)
294
+ 完成后报告清单。` }],
295
+ clientTimeZone: 'Asia/Shanghai',
296
+ })
297
+ onRetry()
291
298
  } catch (e) { D.errors.push(String(e)) }
292
299
  }
293
300
 
@@ -306,7 +313,7 @@ window.__ModuleLoader__.load({
306
313
  h('div', { key: 'd', style: { fontSize: 12, opacity: 0.7, lineHeight: 1.6 } }, r.desc),
307
314
  ]),
308
315
  r.action === 'plugins' ? h('button', { key: 'a', onClick: () => { try { hostCtx.layout.selectPanel('plugins') } catch (e) { D.errors.push(String(e)) } }, style: btnStyle }, '打开插件面板') : null,
309
- r.action === 'create-router' ? h('button', { key: 'a', onClick: () => createAndBootstrap(checks, onRetry), style: btnStyle }, '创建 Router 会话') : null,
316
+ r.action === 'create-router' ? h('button', { key: 'a', onClick: () => createRouterAndInit(checks, onRetry), style: btnStyle }, '创建 Router 会话') : null,
310
317
  ])),
311
318
  h('button', { key: 'retry', onClick: onRetry, style: { ...btnStyle, marginTop: 10 } }, '↻ 重新检查'),
312
319
  ])
@@ -400,11 +407,10 @@ window.__ModuleLoader__.load({
400
407
 
401
408
  // 新用户三步启用引导(主理鱼同款编号步骤卡;替代旧的一锅炖检查面板)
402
409
  function OnboardPanel({ checks, currentSessionId, onRetry }) {
403
- const [initBusy, setInitBusy] = React.useState(false)
404
410
  const steps = [
405
411
  { n: 1, ok: checks.presets, title: '确认依赖插件', desc: checks.presets ? '智能体协作 / 子智能体插件已启用' : '打开插件面板,启用 sun-space / 智能体协作相关插件后重启实例', action: checks.presets ? null : 'plugins' },
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 },
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' },
412
+ { n: 2, ok: !!checks.routerSession, title: '一键初始化(Router 会话 + 知识库)', desc: checks.routerSession ? `已就绪:${checks.routerSession.slice(0, 28)}…` : `一键完成:以当前工作区目录${checks.currentCwd ? `(${checks.currentCwd})` : ''}为知识库根 → 创建 Router 会话 → 写入 90-Meta 骨架 → 自登记(仅 ~/.dsh 登记需一次批准)`, action: checks.presets && !checks.routerSession ? 'create-router' : null },
413
+ { n: 3, ok: checks.vault, title: '知识库就绪', desc: checks.vault ? `已就绪:${checks.vaultPath}` : (checks.routerSession ? '知识库骨架缺失 → 点下方按钮让 Router 补建' : '完成第②步后自动创建'), action: checks.presets && checks.routerSession && !checks.vault ? 'init-skeleton' : null },
408
414
  ]
409
415
  const allOk = steps.every(x => x.ok)
410
416
  return h('div', null, [
@@ -417,21 +423,9 @@ window.__ModuleLoader__.load({
417
423
  h('div', { key: 'd', style: { fontSize: 12, opacity: 0.7, lineHeight: 1.6, marginTop: 2 } }, st.desc),
418
424
  ]),
419
425
  st.action === 'plugins' ? h('button', { key: 'a', onClick: () => { try { hostCtx.layout.selectPanel('plugins') } catch (e) { D.errors.push(String(e)) } }, style: btnStyle }, '打开插件面板') : null,
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,
426
+ st.action === 'create-router' ? h('button', { key: 'a', onClick: () => createRouterAndInit(checks, onRetry), style: btnStyle }, '一键初始化') : null,
427
+ st.action === 'init-skeleton' ? h('button', { key: 'a', onClick: () => initSkeletonOnRouter(checks.routerSession, onRetry), style: btnStyle }, '补建知识库骨架') : null,
428
+
435
429
  ])),
436
430
  allOk ? h('p', { key: 'done', style: { color: '#57b26a', fontSize: 13 } }, '✓ 全部就绪——到「定时任务」用模板创建晨报等四条生产任务,即可开始使用。') : null,
437
431
  h('button', { key: 'retry', onClick: onRetry, style: { ...btnStyle, marginTop: 10 } }, '↻ 重新检查'),