dsh-mattpocock-skills-deck 1.7.49 → 1.7.50

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- AUTO-GENERATED by scripts/build.mjs — DO NOT EDIT. Source: ../README.md @ v1.7.49 -->
1
+ <!-- AUTO-GENERATED by scripts/build.mjs — DO NOT EDIT. Source: ../README.md @ v1.7.50 -->
2
2
 
3
3
  <h1 align="center">dsh-mattpocock-skills-deck</h1>
4
4
 
@@ -34,10 +34,10 @@ npm install -g @deepseek-ai/dsh
34
34
  dsh plugin --profile web add dsh-mattpocock-skills-deck # 用自启 web 服务(dsh web)
35
35
  # 或者
36
36
  dsh plugin --profile desktop add dsh-mattpocock-skills-deck # 用 DSH Desktop 桌面应用
37
- # 锁定最新版更稳(当前 1.7.49):
38
- dsh plugin --profile web add dsh-mattpocock-skills-deck@1.7.49 --registry https://registry.npmjs.org
37
+ # 锁定最新版更稳(当前 1.7.50):
38
+ dsh plugin --profile web add dsh-mattpocock-skills-deck@1.7.50 --registry https://registry.npmjs.org
39
39
  # 或者
40
- dsh plugin --profile desktop add dsh-mattpocock-skills-deck@1.7.49 --registry https://registry.npmjs.org
40
+ dsh plugin --profile desktop add dsh-mattpocock-skills-deck@1.7.50 --registry https://registry.npmjs.org
41
41
  ```
42
42
 
43
43
  <div align="center">
package/lib/client.js CHANGED
@@ -208,7 +208,7 @@ const PortalOverlay = function (props, children) {
208
208
  return portalTop(h('div', props || {}, children))
209
209
  }
210
210
  // v1.3.3:面板版本号(tabs 行最右侧显示,便于核对已更新)
211
- const DSW_VERSION = 'v1.7.49'
211
+ const DSW_VERSION = 'v1.7.50'
212
212
  // #repo-link:版本号可点,新窗打开插件仓库主页;URL 构建期从 package/package.json 的 repository 字段
213
213
  // 注入(客户端源码零 URL 字面量,过硬编码门禁 F2;产物字面量已在门禁 RE_LICENSED 登记)。
214
214
  const DSW_REPO_URL = 'https://github.com/FeatherHunter/dsh-mattpocock-skills-deck'
package/lib/index.js CHANGED
@@ -90,7 +90,7 @@ export default {
90
90
  async function getRepoKey() { const h = await _repo(); return h.getRepoKey.apply(h, arguments) }
91
91
  // #723(T19):刷新机制在宿主里真的装起来(闸+账本+写事件订阅+视野模型+会话↔票处理链,实现整段在 ./refresh/wiring.js)。这个句柄是那套东西的唯一出口:_detectChain 从它取闸(3g),wf.chain 从它取界面读数。
92
92
  let _refreshWiringP = null, refreshGate = null
93
- try { _boot().catch(function(){}); _dispatchMeta().catch(function(){}); _refreshWiringP = import('./refresh/wiring.js').then(function (m) { return m.makeRefreshLoader({ ctx: ctx, logCtx: logCtx, canonicalKey: function () { return canonicalKey.apply(null, arguments) }, getCacheDir: function () { return getCacheDir.apply(null, arguments) }, getTrackerRegistry: function () { return getTrackerRegistry.apply(null, arguments) }, getDetectionService: function () { return getDetectionService.apply(null, arguments) }, getPlatform: function () { return getPlatform.apply(null, arguments) }, detectionExec: function () { return detectionExec.apply(null, arguments) }, setCache: setCache, getCache: getCache, readDiskCache: function () { return readDiskCache.apply(null, arguments) }, writeDiskCache: function () { return writeDiskCache.apply(null, arguments) }, runGh: function () { return runGh.apply(null, arguments) }, getRepoKey: function () { return getRepoKey.apply(null, arguments) }, fetchIssueIndex: function () { return fetchIssueIndex.apply(null, arguments) }, issueIndexFromSnapshot: function () { return issueIndexFromSnapshot.apply(null, arguments) }, getNaming: function () { return _naming() } })() }).then(function (w) { refreshGate = (w && w.gate) || null; return w }) } catch (e0) {}
93
+ try { _boot().catch(function(){}); _dispatchMeta().catch(function(){}); _refreshWiringP = import('./refresh/wiring.js').then(function (m) { return m.makeRefreshLoader({ ctx: ctx, logCtx: logCtx, getChoiceStore: function () { return getChoiceStore.apply(null, arguments) }, canonicalKey: function () { return canonicalKey.apply(null, arguments) }, getCacheDir: function () { return getCacheDir.apply(null, arguments) }, getTrackerRegistry: function () { return getTrackerRegistry.apply(null, arguments) }, getDetectionService: function () { return getDetectionService.apply(null, arguments) }, getPlatform: function () { return getPlatform.apply(null, arguments) }, detectionExec: function () { return detectionExec.apply(null, arguments) }, setCache: setCache, getCache: getCache, readDiskCache: function () { return readDiskCache.apply(null, arguments) }, writeDiskCache: function () { return writeDiskCache.apply(null, arguments) }, runGh: function () { return runGh.apply(null, arguments) }, getRepoKey: function () { return getRepoKey.apply(null, arguments) }, fetchIssueIndex: function () { return fetchIssueIndex.apply(null, arguments) }, issueIndexFromSnapshot: function () { return issueIndexFromSnapshot.apply(null, arguments) }, getNaming: function () { return _naming() } })() }).then(function (w) { refreshGate = (w && w.gate) || null; return w }) } catch (e0) {}
94
94
  try { _plat().then(function(pl){ try { pl.getTrackerRegistry().catch(function(){}) } catch (e1) {} }).catch(function(){}) } catch (e2) {}
95
95
  // ---- H2 #446 接线:3 新文件动态 import 加载(D7 禁止静态 import),依赖全显式传入;新文件之间不互引用 ---- 留守(行为零变化优先;调用方在 H4/H5/H6 的同步上下文里,动态加载给不出同步函数):computeLevels/groupTickets(H4 三处同步分组)、isRateLimitError(H5 三处同步判别)、issueIndexFromSnapshot/issueIndexChanged/rememberIssueIndex(H6 探测同步取值与同刻写表)。 这一组的拆分缘由见账本 §3。
96
96
  let _mapBodyP = null
@@ -35,6 +35,7 @@ import * as toolCost from '../../shared/refresh/tool-cost.js'
35
35
  // #724:闸那一侧的工作区钥匙(短散列)只有一份实现,住在共享层(宿主层文件之间不许互引,而链求值那侧也要用
36
36
  // 同一把钥匙)。本文件把它转出来,「活跃集合、写事件白名单、七个 deck_* 工具、行级增量、检查链记账」五处同源。
37
37
  import { hash8, workspaceKeyOf } from '../../shared/refresh-workspace-key.js'
38
+ import { parseWorkspaceFile, WORKSPACE_FILE_REL } from '../../shared/deck-tools/workspace-file.js'
38
39
  export { workspaceKeyOf } from '../../shared/refresh-workspace-key.js'
39
40
 
40
41
  /** 把一堆依赖包装成「取一次、以后复用」的惰性实例(模块加载失败不许把整个插件带崩)。 */
@@ -328,6 +329,10 @@ export function createRefreshWiring(deps) {
328
329
  handleFor: function (s) { try { const list = (typeof registry.allBindings === 'function') ? registry.allBindings() : []; const want = String((s && s.cwd) || ''); const wash = (p) => String(p || '').replace(/\\/g, '/').replace(/\/+$/, ''); const wantW = wash(want); for (let i = 0; i < list.length; i++) { const c = (list[i] && (list[i].cwd || (list[i].handle && list[i].handle.cwd))) || ''; if (String(c) === want || wash(c) === wantW) return list[i].handle || { cwd: want } } } catch (e) {} return { cwd: (s && s.cwd) || '' } },
329
330
  // 934:会话目录洗加锚根的宿主唯一异步出口(与绑定侧同一把钥匙);缺席时壳沿用原始目录。
330
331
  canonicalKey: (typeof d.canonicalKey === 'function') ? function (raw) { return d.canonicalKey(raw) } : undefined,
332
+ // 947 显式后两层读口:查不到一律 null(缺席即跳过,不猜)。
333
+ readChoice: function (rootCwd) { try { return (typeof d.getChoiceStore === 'function') ? d.getChoiceStore().then(function (cs) { return (cs && typeof cs.getWorkspace === 'function') ? cs.getWorkspace(rootCwd) : null }, function () { return null }).then(function (got) { return (got && got.found === true && got.backendId) ? { backendId: got.backendId, rev: got.rev || 0 } : null }) : Promise.resolve(null) } catch (e) { return Promise.resolve(null) } },
334
+ readWorkspaceFileText: function (rootCwd) { try { const fsSvc = backendObj && backendObj.fs; if (!fsSvc || typeof fsSvc.resolve !== 'function' || typeof fsSvc.readText !== 'function') return Promise.resolve(null); return Promise.resolve(fsSvc.resolve(WORKSPACE_FILE_REL, { cwd: rootCwd })).then(function (t) { return fsSvc.readText(t) }, function () { return null }).then(function (txt) { return (typeof txt === 'string' && txt) ? txt : null }, function () { return null }) } catch (e) { return Promise.resolve(null) } },
335
+ parseWorkspaceFile: parseWorkspaceFile,
331
336
  sandboxPolicyFor: createSandboxPolicyFor(d.ctx),
332
337
  backendCtx: function () { return backendObj },
333
338
  invalidate: function (info) { try { const root = (info && info.workspace && info.workspace.root) || (info && info.cwd); if (typeof d.setCache === 'function' && root) d.setCache({ ts: 0, snapshot: null, error: null, cwd: String(root) }) } catch (e) {} },
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 第一性原理(#150 Q4 + #149 9→契约映射 + #113 平台层):
5
5
  * - 唯一显式写路径 = `wf.bind(handle, backendId|null) → registry.bind`(Q4 不回写 issue-tracker.md)
6
- * - 本模块为「文件显式」:读 `docs/agents/issue-tracker.md` 经 `platform.fs`(零 OS 直碰),
6
+ * - 本模块为「文件显式」:读内存绑定、`docs/agents/workspace.json`、再读 `docs/agents/issue-tracker.md`,经 `platform.fs`(零 OS 直碰),
7
7
  * 产内存 `Selection(explicit)`;不写 `byHandle`,避免文件监听竞态(handoff-150 Q4 *不自动 bind* 推荐)
8
8
  * - 若解析出 `explicitBackendId` 且已注册 → 直接回 `Selection`,跳过 `registry.select` 的 matches 并行;
9
9
  * 否则返回 null 由调用方落到 `registry.select`(含 bound-explicit > matches > fallback + pending/multiHit)
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { parseIssueTracker, normalizeTrackerText } from './parseIssueTracker.js'
14
+ import { parseWorkspaceFile, WORKSPACE_FILE_REL } from '../../../shared/deck-tools/workspace-file.js'
14
15
 
15
16
  /**
16
17
  * 尝试显式分支:读主锚 → 结构化解析 → 已注册则产 Selection
@@ -21,11 +22,34 @@ import { parseIssueTracker, normalizeTrackerText } from './parseIssueTracker.js'
21
22
  */
22
23
  export async function detectExplicit(handle, ctx, registry) {
23
24
  const cwd = (handle && handle.cwd) || (ctx && ctx.cwd) || ''
25
+ // #947:内存绑定优先(与工具壳同一条:绑过即显式,不读文件)。空串是显式无后端逃生舱,如实透传不落锚文件。
26
+ try {
27
+ if (registry && typeof registry.bound === 'function') {
28
+ const boundId = registry.bound(handle)
29
+ if (boundId === null) return { selection: { backendId: null, source: 'explicit' }, raw: null, parsed: { explicitBackendId: null, rawHint: '', confidence: 'high', reason: 'memory-none' } }
30
+ if (typeof boundId === 'string' && boundId && typeof registry.has === 'function' && registry.has(boundId)) {
31
+ let ref = null
32
+ try { ref = registry.describe(handle, boundId) } catch (eD) { ref = { backend: boundId, refId: (handle && (handle.refId || handle.cwd)) || '', name: (handle && handle.cwd) || boundId, url: '' } }
33
+ return { selection: { backendId: boundId, source: 'explicit', ref: ref }, raw: null, parsed: { explicitBackendId: boundId, rawHint: '', confidence: 'high', reason: 'memory-bound' } }
34
+ }
35
+ }
36
+ } catch (eB) {}
24
37
  const platform = ctx && ctx.platform ? ctx.platform : null
25
38
  // 无平台则无法安全读(零 OS 直碰红线),回退 null
26
39
  if (!platform || !platform.fs || typeof platform.fs.resolve !== 'function' || typeof platform.fs.readText !== 'function') {
27
40
  return { selection: null, raw: null, parsed: { explicitBackendId: null, rawHint: '', confidence: 'none', reason: 'no-platform' } }
28
41
  }
42
+ // #947:工作区配置文件(人亲手选的默认值)。胜锚文件,输内存绑定与选择记忆 H(H 在检测主流程后判)。
43
+ try {
44
+ const target = await platform.fs.resolve(WORKSPACE_FILE_REL, { cwd })
45
+ const txt = await platform.fs.readText(target)
46
+ const parsed = parseWorkspaceFile(txt)
47
+ if (parsed && registry && typeof registry.has === 'function' && registry.has(parsed.backendId)) {
48
+ let ref = null
49
+ try { ref = registry.describe(handle, parsed.backendId) } catch (eD) { ref = { backend: parsed.backendId, refId: (handle && (handle.refId || handle.cwd)) || '', name: (handle && handle.cwd) || parsed.backendId, url: '' } }
50
+ return { selection: { backendId: parsed.backendId, source: 'explicit', ref: ref }, raw: txt, parsed: { explicitBackendId: parsed.backendId, rawHint: '', confidence: 'high', reason: 'workspace-file' } }
51
+ }
52
+ } catch (eF) {}
29
53
  let raw = null
30
54
  try {
31
55
  const target = await platform.fs.resolve('docs/agents/issue-tracker.md', { cwd })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mattpocock-skills-deck",
3
- "version": "1.7.49",
3
+ "version": "1.7.50",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/FeatherHunter/dsh-mattpocock-skills-deck.git"
@@ -64,38 +64,7 @@ export function limitsFromBudget(b) {
64
64
  }
65
65
  }
66
66
 
67
- /**
68
- * 从会话上下文里取工作区。只认「会话里带着的那个目录」,不接受调用方传进来的路径
69
- * (那样等于允许跨工作区写)。真实运行时的首选位置是 exec.agent.session.cwd;
70
- * 别的几个位置一起认,是因为会话对象在宿主不同版本里的形状不完全一样,
71
- * 认不出来时返回 ok:false,让工具如实说做不到,而不是猜一个进程目录。
72
- */
73
- export function sessionContextOf(exec, deps) {
74
- const e = exec || {}
75
- const d = deps || {}
76
- const agent = e.agent || {}
77
- const session = agent.session || e.session || null
78
- const cands = [
79
- { where: 'exec.agent.session.cwd', value: session && session.cwd },
80
- { where: 'exec.agent.session.workspaceRoot', value: session && session.workspaceRoot },
81
- { where: 'exec.agent.session.workspace.cwd', value: session && session.workspace && session.workspace.cwd },
82
- { where: 'exec.agent.cwd', value: agent.cwd },
83
- { where: 'exec.session.cwd', value: e.session && e.session.cwd },
84
- { where: 'exec.cwd', value: e.cwd },
85
- ]
86
- let cwd = ''
87
- let source = ''
88
- for (const c of cands) {
89
- if (typeof c.value === 'string' && c.value.trim()) { cwd = c.value.trim(); source = c.where; break }
90
- }
91
- const sessionId = str(session && (session.id || session.sessionId)) || str(agent.sessionId)
92
- if (!cwd) {
93
- return { ok: false, reason: REFUSAL_REASONS.NO_SESSION, sessionId: sessionId, text: '这次没拿到当前会话的工作区目录(会话上下文里没有可用的路径),所以我不动任何票。请换一个会话再来,或让维护者看一看会话对象的形状。' }
94
- }
95
- const key = typeof d.workspaceKeyOf === 'function' ? String(d.workspaceKeyOf(cwd)) : ('ws-' + hash8(cwd))
96
- return { ok: true, cwd: cwd, workspaceKey: key, sessionId: sessionId, source: source, text: '' }
97
- }
98
-
67
+ // 会话目录取法只有 session-resolve.js 一份(九个工具全走它的 Async 版);本文件曾有的同步复件已删,免得两份分叉。
99
68
  // 边的落点判定与逐项证据(landingOf / classifyEdgeLanding / childrenOf / statusOfItems)住在同目录的
100
69
  // edges.js:同层的文件不许互相 import,所以那边自带一份最小的字符串工具函数,拆开的理由见 edges.js 文件头。
101
70
  // 「一批里有的成、有的没成 → partial」这条总结论也在那边(statusOfItems),这里不再留第二份。
@@ -112,6 +81,9 @@ export function sessionContextOf(exec, deps) {
112
81
  * invalidate 可选,写后失效缓存该票条目:invalidate({ repo, keys, backendId })
113
82
  * ensureReading 可选,动手前保剩余额度读数:ensureReading(cwd, workspaceKey),缺席时跳过
114
83
  * resolveTimeoutMs 可选,仓库标识补全等待上限毫秒数(缺席 8000,测试可调小)
84
+ * readChoice 可选,读本机记忆H:(规范根) => {backendId, rev} | null(缺席跳过H层)
85
+ * readWorkspaceFileText 可选,读工作区文件原文:(规范根) => string | null(缺席跳过文件层)
86
+ * parseWorkspaceFile 可选,解析文件文本(与 readWorkspaceFileText 同给同缺)
115
87
  */
116
88
  export function createDeckShell(deps) {
117
89
  const d = deps || {}
@@ -137,8 +109,6 @@ export function createDeckShell(deps) {
137
109
  try { if (typeof d.noteGate === 'function') d.noteGate({ phase: phase, verdict: verdict, reason: reason }) } catch (eN) {}
138
110
  }
139
111
 
140
- function context(exec) { return sessionContextOf(exec, { workspaceKeyOf: d.workspaceKeyOf }) }
141
-
142
112
  /** 后端 ctx(platform / fs / exec 那一套):可以给对象,也可以给一个现取的函数(多工作区共用一个壳时后者更顺手)。 */
143
113
  function backendCtxNow() {
144
114
  const b = d.backendCtx
@@ -200,9 +170,37 @@ export function createDeckShell(deps) {
200
170
  let sent = null
201
171
  const t0 = now()
202
172
  try { if (typeof d.ensureReading === 'function') await d.ensureReading(s.cwd, s.workspaceKey) } catch (eR) {}
173
+ // 显式三层(947:内存绑定 > 本机记忆H > 工作区文件)。缺席的口子跳过,未注册 id 忽略,
174
+ // 显式无后端(null)落到 select 由注册表诚实判。判定收在闸内:每笔照旧过闸记账,不断 758 口径。
175
+ const explicitId = async function () {
176
+ try {
177
+ const mem = (registry && typeof registry.bound === 'function') ? registry.bound(handle) : undefined
178
+ if (typeof mem === 'string' && mem && typeof registry.has === 'function' && registry.has(mem)) return mem
179
+ } catch (eM) {}
180
+ try {
181
+ if (typeof d.readChoice === 'function') {
182
+ const h = await d.readChoice(s.cwd)
183
+ if (h && typeof h.backendId === 'string' && h.backendId && typeof registry.has === 'function' && registry.has(h.backendId)) return h.backendId
184
+ }
185
+ } catch (eH) {}
186
+ try {
187
+ if (typeof d.readWorkspaceFileText === 'function' && typeof d.parseWorkspaceFile === 'function') {
188
+ const parsed = d.parseWorkspaceFile(await d.readWorkspaceFileText(s.cwd))
189
+ if (parsed && typeof parsed.backendId === 'string' && parsed.backendId && typeof registry.has === 'function' && registry.has(parsed.backendId)) return parsed.backendId
190
+ }
191
+ } catch (eF) {}
192
+ return null
193
+ }
203
194
  try {
204
195
  sent = await gate.send({ source: 'tool.call', kind: 'probe', bucket: 'rest', workspaceKey: s.workspaceKey, plan: [{ phase: 'select' }] }, async () => {
205
- picked = await registry.select(handle, Object.assign({}, backendCtxNow(), { cwd: s.cwd, caller: DECK_TOOL_KIND }))
196
+ const ex = await explicitId()
197
+ if (ex) {
198
+ let ref = null
199
+ try { ref = registry.describe(handle, ex) } catch (eD) { ref = null }
200
+ picked = { backendId: ex, source: 'explicit', ref: ref, pending: false }
201
+ } else {
202
+ picked = await registry.select(handle, Object.assign({}, backendCtxNow(), { cwd: s.cwd, caller: DECK_TOOL_KIND }))
203
+ }
206
204
  return { requests: 1, points: 0 }
207
205
  })
208
206
  } catch (e) { failure = e }
@@ -358,7 +356,6 @@ export function createDeckShell(deps) {
358
356
 
359
357
  return {
360
358
  limits: limits,
361
- context: context,
362
359
  estimateFor: estimateFor,
363
360
  pickBackend: pickBackend,
364
361
  repoOf: repoOf,
@@ -0,0 +1,33 @@
1
+ // src/shared/deck-tools/workspace-file.js —— 工作区配置文件的纯半(#947 规格)
2
+ //
3
+ // 住共享层:探测(host 直接引用)与工具壳(shared,经接线注入)共用同一套解析,
4
+ // 不各写一份。调用方(host)引用本文件是允许的方向;shared 文件之间不互引,所以本文件零导入。
5
+ // 本文件只做字符串与 JSON:不碰 fs / 平台 / 注册表;读文件与「后端认不认得」由调用方做。
6
+ // 只认人亲手选的:source 不是 'user' 的一律不认(派生结论不许钉成意图,与选择记忆 H 同一条铁律)。
7
+ export const WORKSPACE_FILE_REL = 'docs/agents/workspace.json'
8
+ export const WORKSPACE_FILE_VERSION = 1
9
+ export const WORKSPACE_FILE_SOURCE = 'user'
10
+
11
+ function finiteNumber(v) { return (typeof v === 'number' && Number.isFinite(v)) }
12
+
13
+ // 工作区文件文本 → 人选后端候选。形状不对一律 null(调用方按「没有这层」继续往下走,不报错)。
14
+ // 已知性(注册表认不认得这个 id)不在这里判:调用方手里有注册表,各自按既有形状核验。
15
+ export function parseWorkspaceFile(text) {
16
+ let obj = null
17
+ try { obj = JSON.parse(String(text == null ? '' : text).replace(/^\uFEFF/, '')) } catch (e) { return null }
18
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return null
19
+ if (obj.version !== WORKSPACE_FILE_VERSION) return null
20
+ const backendId = (typeof obj.backendId === 'string') ? obj.backendId.trim() : ''
21
+ if (!backendId) return null
22
+ if (obj.source !== WORKSPACE_FILE_SOURCE) return null
23
+ if (!finiteNumber(obj.pickedAt)) return null
24
+ return { backendId: backendId, pickedAt: obj.pickedAt, source: WORKSPACE_FILE_SOURCE }
25
+ }
26
+
27
+ // 人选 → 落盘文本。只装四项:版本、后端、档案时间、来源;不装路径、令牌、登录态。
28
+ export function stringifyWorkspaceFile(input) {
29
+ const src = (input && typeof input === 'object') ? input : {}
30
+ const backendId = (typeof src.backendId === 'string') ? src.backendId.trim() : ''
31
+ const pickedAt = finiteNumber(src.pickedAt) ? src.pickedAt : 0
32
+ return JSON.stringify({ version: WORKSPACE_FILE_VERSION, backendId: backendId, pickedAt: pickedAt, source: WORKSPACE_FILE_SOURCE }, null, 2) + '\n'
33
+ }