dsh-channel-memory 0.1.1 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +21 -2
  2. package/lib/index.js +31 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -116,14 +116,33 @@ keeper → 桥的 `--cwd`),例如:
116
116
 
117
117
  ## 验证
118
118
 
119
- `plugin/contract_test.mjs` 用**宿主自己的 `defineTool`** 跑契约测试(不是 stub):
119
+ `test/contract_test.mjs` 用**宿主自己的 `defineTool`** 跑契约测试(不是 stub):
120
120
  value-schema DSL 违规会当场抛。夹具强制宿主不变量(cwd 不符则 `attachSession` 抛),
121
121
  因此「外来会话被拒绝挂载」是被真实验证的,而不是被假 object 允许的。
122
122
 
123
+ 在插件目录里运行(`<APP>` 同上;Windows 桌面形态必须用 DSH 自带的 Electron 跑,
124
+ 因为宿主模块在 `app.asar` 里):
125
+
126
+ ```bash
127
+ set ELECTRON_RUN_AS_NODE=1
128
+ "<APP>\DeepSeek Harness.exe" --expose-internals test\contract_test.mjs
129
+ ```
130
+
131
+ 可选覆盖(默认指向本机安装布局):`DSH_TOOLS_ENTRY`、`DSH_PLUGIN_ENTRY`。
132
+
133
+ 实测输出:
134
+
123
135
  ```
124
- RESULT: PASS (5 个工具注册、1 个提示词分节、外来会话未被挂载、post-apply 写入反映进索引)
136
+ tools registered: 5
137
+ prompt sections : channel-memory:index@3200(text=function)
138
+ attached (channel session): ["sess-abc"]
139
+ after foreign session : ["sess-abc"] (must not contain sess-elsewhere)
140
+ index reflects post-apply write: true
141
+ RESULT: PASS
125
142
  ```
126
143
 
144
+ 测试会把 `DSH_HOME` 指到临时目录,不会碰你真实的 `~/.dsh`。
145
+
127
146
  权限面见 `PERMISSIONS.md`。
128
147
 
129
148
  ## License
package/lib/index.js CHANGED
@@ -303,9 +303,25 @@ export function apply(ctx, config) {
303
303
  },
304
304
  ]
305
305
 
306
- const diag = { toolsService: 'ok', defineTool: typeof defineTool, errors: [], registered: [] }
306
+ const diag = { toolsService: 'absent', defineTool: typeof defineTool, errors: [], registered: [] }
307
+
308
+ // Written before it is called: registration can land either synchronously (hosts
309
+ // that resolve `tools` eagerly) or from the inject callback, and both paths report
310
+ // through here. A single early snapshot would always show registered:0 on profiles
311
+ // where inject defers, which previously read as "plugin not installed".
312
+ const writeStatus = () => {
313
+ try {
314
+ writeJson(join(cfg.dataDir, 'status.json'), {
315
+ version: 1, at: new Date().toISOString(), registered: diag.registered.length, total: tools.length,
316
+ diag, workspace: state.workspaceId || null, memories: mem.items.length,
317
+ })
318
+ } catch { }
319
+ }
320
+
307
321
  const registerAll = (toolsSvc) => {
322
+ if (diag.registered.length) return // exactly once
308
323
  if (!toolsSvc || typeof toolsSvc.register !== 'function') { diag.toolsService = 'unavailable'; return }
324
+ diag.toolsService = 'ok'
309
325
  for (const t of tools) {
310
326
  try {
311
327
  toolsSvc.register(defineTool({ ...t, output: OUT() }))
@@ -316,29 +332,25 @@ export function apply(ctx, config) {
316
332
  ctx.logger?.warn?.(`channel-memory: tool ${msg}`)
317
333
  }
318
334
  }
335
+ writeStatus()
319
336
  }
320
337
 
321
- // inject 声明保证了 ctx.tools 可用;注册同步完成后才落快照,
322
- // 不会再出现 "toolsService absent / registered 0" 的时序假象。
323
- registerAll(ctx.tools)
338
+ // `tools` is requested through inject rather than read off the root context:
339
+ // on some profiles (observed under `--profile acp`, the path phone turns take)
340
+ // touching ctx.tools without declaring it throws
341
+ // `cannot get property "tools" without inject`, which would abort apply() and
342
+ // leave the plugin mounted but tool-less.
343
+ ctx.inject(['tools'], (host) => registerAll(host.tools))
324
344
 
325
- // Observability: without this, a silent registration failure is indistinguishable
326
- // from "plugin not installed" when looking at the profile from the outside.
327
- // Tool registration is synchronous above, but the workspace is created by an
328
- // async effect, so a single snapshot here always shows workspace:null. Re-write
329
- // after the effect has had time to land, otherwise the file contradicts
330
- // channel_workspace_info and looks like a mount failure.
331
- const writeStatus = () => {
332
- try {
333
- writeJson(join(cfg.dataDir, 'status.json'), {
334
- version: 1, at: new Date().toISOString(), registered: diag.registered.length, total: tools.length,
335
- diag, workspace: state.workspaceId || null, memories: mem.items.length,
336
- })
337
- } catch { }
345
+ // Hosts that already resolved the service call the callback synchronously; where
346
+ // they do not, keep the direct handle as a guarded fallback so registration can
347
+ // never silently no-op. registerAll ignores the second attempt.
348
+ if (!diag.registered.length) {
349
+ try { registerAll(ctx.tools) } catch (e) { diag.errors.push('direct: ' + String(e?.message ?? e).slice(0, 120)) }
338
350
  }
339
351
  writeStatus()
340
- setTimeout(writeStatus, 2000)
352
+ setTimeout(writeStatus, 2000) // after the workspace effect has had time to land
341
353
  setTimeout(writeStatus, 10000)
342
354
 
343
355
  ctx.logger?.info?.(`channel-memory ready: tools=${diag.registered.length}/${tools.length} memories=${mem.items.length} ws=${cfg.workspacePath}`)
344
- }
356
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-channel-memory",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "微信通道专属工作区 + 跨会话互通记忆:把手机发起的 DSH 会话归到一个独立工作区,并让所有会话共享一份可检索的长期记忆。",
5
5
  "license": "MIT",
6
6
  "type": "module",