dsh-workspace-api-key 0.2.3 → 0.2.5
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 +6 -6
- package/README.zh.md +6 -6
- package/lib/client.js +60 -11
- package/lib/index.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,12 +31,12 @@ Host half (`lib/index.js`):
|
|
|
31
31
|
- matches that directory against `workspaceRegistry.list()` after normalizing both sides (absolute, trailing separator removed, lower-cased on Windows);
|
|
32
32
|
- derives a stable ref per level — `DEEPSEEK_API_KEY_WS_<first 16 hex chars of sha1(normalized path), upper-cased>` for a workspace and `DEEPSEEK_API_KEY_SS_<first 16 hex chars of sha1(session id), upper-cased>` for a session (a session id contains `-` and cannot be a ref name) — writes the key through the credentials service, and serves that ref's value;
|
|
33
33
|
- rewrites only refs a provider actually reads: the default ref, every adapter's configured `apiKeyEnv`, and its own generated refs. Every other ref passes through untouched;
|
|
34
|
-
- builds the session list from the `sessionController` service (`list()`, unwrapping its `{items: […]}` envelope) and falls back to the live `sessions` service (`ctx.get('sessions').list()`), so the panel still has sessions when the controller is missing, throws, or returns an empty list
|
|
35
|
-
- reports its own version and a session probe (`available`, `shape`, `source`, `count`, `withoutCwd`, `samples`, `error`, plus sample workspace paths) in `/wsk-api/state`, so the panel can say *why* no sessions are listed: a stale host half (only a page reload instead of an app restart), a missing `sessionController`, a throwing `list()`, an unexpected envelope shape, or a `cwd` that matches no workspace path;
|
|
34
|
+
- builds the session list from the `sessionController` service (`list()`, unwrapping its `{items: […]}` envelope) and falls back to the live `sessions` service (`ctx.get('sessions').list()`), so the panel still has sessions when the controller is missing, throws, or returns an empty list. Subagent sessions (`origin: 'subagent'`, or a `parentSession`) are dropped from both sources — the persisted list contains them, and without that filter one workspace with a single real session showed thirteen;
|
|
35
|
+
- reports its own version and a session probe (`available`, `shape`, `source`, `count`, `withoutCwd`, `subagents`, `samples`, `error`, plus sample workspace paths) in `/wsk-api/state`, so the panel can say *why* no sessions are listed: a stale host half (only a page reload instead of an app restart), a missing `sessionController`, a throwing `list()`, an unexpected envelope shape, or a `cwd` that matches no workspace path;
|
|
36
36
|
- keeps its own bookkeeping (which level maps to which ref, invalid flags, the provider a key was recorded for) in `$DSH_HOME/storages/dsh-workspace-api-key.json`;
|
|
37
37
|
- observes failures through the `agent/request-error` waterfall without ever returning `{kind: 'retry'}` — it only records the verdict and calls `next()`.
|
|
38
38
|
|
|
39
|
-
The browser half (`lib/client.js`) registers into the `sidebar.panellist` slot (`order: 1`), the `main` slot (key `workspace-api-key`) and the `shell.overlay` slot (the entering-a-session warning), the same slots `dsh-skill-mcp-panel` and `dsh-429-guard` use. It is plain `React.createElement` with no build step and no bundled dependencies. It merges the host rows with the browser-side `sessions` store (`ctx.get('sessions').list.getSnapshot()`, which is what the sidebar itself renders), so session titles come from `displayTitle
|
|
39
|
+
The browser half (`lib/client.js`) registers into the `sidebar.panellist` slot (`order: 1`), the `main` slot (key `workspace-api-key`) and the `shell.overlay` slot (the entering-a-session warning), the same slots `dsh-skill-mcp-panel` and `dsh-429-guard` use. It is plain `React.createElement` with no build step and no bundled dependencies. It merges the host rows with the browser-side `sessions` store (`ctx.get('sessions').list.getSnapshot()`, which is what the sidebar itself renders), so session titles come from `displayTitle`, host rows are never dropped when that store only knows a subset, and sessions stay visible even when the host cannot list them. That store is provided by `dsh-api-session-controller` *after* the plugin's `apply()`, so both the panel and the overlay retry for it (every 500 ms, at most 40 times) instead of giving up on the first render. The overlay asks `/wsk-api/check?sessionId=…&path=…` about the session that currently owns the main view, asks at most once per session, and its buttons only ever navigate to the panel — the panel is where a key can actually be changed.
|
|
40
40
|
|
|
41
41
|
### HTTP API (loopback only)
|
|
42
42
|
|
|
@@ -71,11 +71,11 @@ Then add `"dsh-workspace-api-key"` to `dsh.profile.bundles` in that profile's `p
|
|
|
71
71
|
## Tests
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
|
-
node test/host.test.mjs #
|
|
75
|
-
node test/client.test.mjs #
|
|
74
|
+
node test/host.test.mjs # 130 assertions
|
|
75
|
+
node test/client.test.mjs # 134 assertions
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Neither suite needs a running DSH process. The host suite drives `apply()` against a mock context (fake `credentials`, `workspaceRegistry`, `agents`, `sessionController`, `sessions`, `webServer`, stubbed `fetch`) and covers ref derivation, the session → workspace → default cascade, workspace matching, redirecting a second adapter's `apiKeyEnv`, clearing back to the default, 401/402/429/5xx classification, the loopback and origin checks, state persistence, and the session probe including the `{items}` envelope
|
|
78
|
+
Neither suite needs a running DSH process. The host suite drives `apply()` against a mock context (fake `credentials`, `workspaceRegistry`, `agents`, `sessionController`, `sessions`, `webServer`, stubbed `fetch`) and covers ref derivation, the session → workspace → default cascade, workspace matching, redirecting a second adapter's `apiKeyEnv`, clearing back to the default, 401/402/429/5xx classification, the loopback and origin checks, state persistence, and the session probe including the `{items}` envelope, the live-session fallback, and subagent sessions being dropped. The client suite loads `lib/client.js` through a minimal `window.__ModuleLoader__` plus a small React runtime, renders the panel and the overlay, and asserts slot registration, the first render, session expand/collapse, per-session saving and testing, sessions recovered from the browser-side store when the host has none (with `displayTitle` winning and subagent sessions hidden), the ungrouped-directory card, the stale and mismatch prompts, the "go configure" hand-off, graceful degradation without a session list, and back-navigation when `ctx.get('layout')` only becomes available after the plugin was applied.
|
|
79
79
|
|
|
80
80
|
## Requirements and limits
|
|
81
81
|
|
package/README.zh.md
CHANGED
|
@@ -31,12 +31,12 @@ DSH 只从一个全局凭据 ref(默认 `DEEPSEEK_API_KEY`)解析 DeepSeek k
|
|
|
31
31
|
- 把该目录与 `workspaceRegistry.list()` 做规范化后比对(绝对化、去掉尾部分隔符、Windows 折大小写);
|
|
32
32
|
- 由每一层派生出稳定的 ref —— 工作区是 `DEEPSEEK_API_KEY_WS_<sha1(规范化路径) 前 16 位十六进制大写>`,会话是 `DEEPSEEK_API_KEY_SS_<sha1(会话 id) 前 16 位十六进制大写>`(会话 id 含 `-`,不能直接当 ref 名)——通过凭据服务写入 key,并返回这个 ref 的值;
|
|
33
33
|
- 只改写 provider 真正会读的 ref:默认 ref、每个适配器配置的 `apiKeyEnv`,以及插件自己生成的 ref;其它 ref 原样透传;
|
|
34
|
-
- 会话列表来自 `sessionController` 服务(`list()`,会拆开它 `{items: […]}` 的信封),控件缺失 / 抛错 / 列表为空时再退到宿主 live 会话服务(`ctx.get('sessions').list()
|
|
35
|
-
- `/wsk-api/state` 会带上插件自身版本与会话探针(`available`、`shape`、`source`、`count`、`withoutCwd`、`samples`、`error`,以及工作区路径样本),面板据此说明**为什么**没有列出会话:宿主半边是旧版本(只刷新了页面而没重启应用)、没有 `sessionController`、`list()` 抛错、信封形状不认识,还是会话的 `cwd` 与任何工作区路径都对不上;
|
|
34
|
+
- 会话列表来自 `sessionController` 服务(`list()`,会拆开它 `{items: […]}` 的信封),控件缺失 / 抛错 / 列表为空时再退到宿主 live 会话服务(`ctx.get('sessions').list()`),所以那种情况下面板仍能列出会话;两个来源都会丢掉子代理会话(`origin: 'subagent'` 或带 `parentSession`)——持久化列表里本来就含它们,不过滤的话一个只有 1 个真实会话的工作区会列出 13 个;
|
|
35
|
+
- `/wsk-api/state` 会带上插件自身版本与会话探针(`available`、`shape`、`source`、`count`、`withoutCwd`、`subagents`、`samples`、`error`,以及工作区路径样本),面板据此说明**为什么**没有列出会话:宿主半边是旧版本(只刷新了页面而没重启应用)、没有 `sessionController`、`list()` 抛错、信封形状不认识,还是会话的 `cwd` 与任何工作区路径都对不上;
|
|
36
36
|
- 自己的账本(哪一层对应哪个 ref、失效标记、key 是给哪个服务商记的)落在 `$DSH_HOME/storages/dsh-workspace-api-key.json`;
|
|
37
37
|
- 通过 `agent/request-error` waterfall 观察失败,但**从不**返回 `{kind: 'retry'}`——只记录判定结果并 `next()` 放行。
|
|
38
38
|
|
|
39
|
-
浏览器半边(`lib/client.js`)注册到 `sidebar.panellist` 槽(`order: 1`)、`main` 槽(key 为 `workspace-api-key`)与 `shell.overlay` 槽(点进会话时的提醒),和 `dsh-skill-mcp-panel`、`dsh-429-guard` 用的是同一套槽。全部是朴素的 `React.createElement`,没有构建步骤,也不打包任何依赖。它会把宿主行与浏览器端 `sessions` 存储(`ctx.get('sessions').list.getSnapshot()`,也就是侧栏自己渲染的数据)合并起来,因此标题取自 `displayTitle
|
|
39
|
+
浏览器半边(`lib/client.js`)注册到 `sidebar.panellist` 槽(`order: 1`)、`main` 槽(key 为 `workspace-api-key`)与 `shell.overlay` 槽(点进会话时的提醒),和 `dsh-skill-mcp-panel`、`dsh-429-guard` 用的是同一套槽。全部是朴素的 `React.createElement`,没有构建步骤,也不打包任何依赖。它会把宿主行与浏览器端 `sessions` 存储(`ctx.get('sessions').list.getSnapshot()`,也就是侧栏自己渲染的数据)合并起来,因此标题取自 `displayTitle`,浏览器端只认识一部分会话时也不会把宿主行挤掉,宿主列不出会话时面板也照样有会话可列。这个存储由 `dsh-api-session-controller` 在插件 `apply()` **之后**才提供,所以面板与覆盖层都会为它重试(每 500ms 一次,最多 40 次),而不是首次渲染拿不到就放弃。覆盖层向 `/wsk-api/check?sessionId=…&path=…` 查询当前占据主视图的会话,每个会话最多问一次,它的按钮只负责把你带到面板——改 key 只能由面板里的操作完成。
|
|
40
40
|
|
|
41
41
|
### HTTP 接口(仅限回环)
|
|
42
42
|
|
|
@@ -71,11 +71,11 @@ pnpm add "dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key"
|
|
|
71
71
|
## 测试
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
|
-
node test/host.test.mjs #
|
|
75
|
-
node test/client.test.mjs #
|
|
74
|
+
node test/host.test.mjs # 130 条断言
|
|
75
|
+
node test/client.test.mjs # 134 条断言
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
两个套件都不需要启动 DSH。宿主套件用一个假 ctx(假的 `credentials`、`workspaceRegistry`、`agents`、`sessionController`、`sessions`、`webServer`,以及打桩的 `fetch`)驱动 `apply()`,覆盖 ref 派生、会话→工作区→默认的级联、工作区匹配、把第二个适配器的 `apiKeyEnv` 一起重定向、清回默认、401/402/429/5xx 的判定、回环与来源校验、状态落盘,以及会话探针(含 `{items}` 信封与 live 兜底)。浏览器套件通过一个极小的 `window.__ModuleLoader__` 与自制 React 运行时加载 `lib/client.js`,渲染出面板与覆盖层,断言槽注册、首屏渲染、会话折叠/展开、会话级保存与测试、宿主列不出会话时从浏览器端存储恢复会话(标题取 `displayTitle
|
|
78
|
+
两个套件都不需要启动 DSH。宿主套件用一个假 ctx(假的 `credentials`、`workspaceRegistry`、`agents`、`sessionController`、`sessions`、`webServer`,以及打桩的 `fetch`)驱动 `apply()`,覆盖 ref 派生、会话→工作区→默认的级联、工作区匹配、把第二个适配器的 `apiKeyEnv` 一起重定向、清回默认、401/402/429/5xx 的判定、回环与来源校验、状态落盘,以及会话探针(含 `{items}` 信封与 live 兜底)。浏览器套件通过一个极小的 `window.__ModuleLoader__` 与自制 React 运行时加载 `lib/client.js`,渲染出面板与覆盖层,断言槽注册、首屏渲染、会话折叠/展开、会话级保存与测试、宿主列不出会话时从浏览器端存储恢复会话(标题取 `displayTitle`、隐藏子代理会话、浏览器端只认识一部分会话时不挤掉宿主行)、会话列表服务晚于插件提供时的重试、未分组目录卡片、失效与不匹配提醒、「去配置」的交接、没有会话列表时的降级,以及 `ctx.get('layout')` 在插件 apply 之后才可用时返回键仍然有效。
|
|
79
79
|
|
|
80
80
|
## 要求与限制
|
|
81
81
|
|
package/lib/client.js
CHANGED
|
@@ -249,11 +249,23 @@ window.__ModuleLoader__.load({
|
|
|
249
249
|
*/
|
|
250
250
|
function mergeSessions(state, sessions) {
|
|
251
251
|
const entries = clientSessionEntries(sessions)
|
|
252
|
-
if (state === null || state === undefined
|
|
252
|
+
if (state === null || state === undefined) return state
|
|
253
|
+
const isTopLevel = (row) => row?.parentSessionId === undefined && row?.origin !== 'subagent'
|
|
254
|
+
if (entries.length === 0) {
|
|
255
|
+
// 没有浏览器端列表时也把子代理会话滤掉(宿主新版本已经过滤,这里兜旧宿主)。
|
|
256
|
+
const clean = (rows) => (Array.isArray(rows) ? rows : []).filter(isTopLevel)
|
|
257
|
+
return {
|
|
258
|
+
...state,
|
|
259
|
+
workspaces: (state.workspaces ?? []).map((ws) => ({ ...ws, sessions: clean(ws.sessions) })),
|
|
260
|
+
ungrouped: (state.ungrouped ?? []).map((group) => ({ ...group, sessions: clean(group.sessions) })),
|
|
261
|
+
}
|
|
262
|
+
}
|
|
253
263
|
|
|
254
264
|
const hostRows = new Map()
|
|
255
265
|
for (const group of [...(state.workspaces ?? []), ...(state.ungrouped ?? [])]) {
|
|
256
266
|
for (const row of (Array.isArray(group?.sessions) ? group.sessions : [])) {
|
|
267
|
+
// 子代理会话不是独立会话(宿主新版本已经过滤,这里对旧宿主再兜一层)。
|
|
268
|
+
if (!isTopLevel(row)) continue
|
|
257
269
|
if (row?.sessionId !== undefined) hostRows.set(row.sessionId, row)
|
|
258
270
|
}
|
|
259
271
|
}
|
|
@@ -317,6 +329,7 @@ window.__ModuleLoader__.load({
|
|
|
317
329
|
}
|
|
318
330
|
|
|
319
331
|
const fill = (rows) => (Array.isArray(rows) ? rows : [])
|
|
332
|
+
.filter(isTopLevel)
|
|
320
333
|
.map((row) => enriched.get(row?.sessionId) ?? row)
|
|
321
334
|
|
|
322
335
|
// 第二遍:宿主结构照旧,浏览器端多出来的会话按 cwd 归档。
|
|
@@ -505,12 +518,31 @@ window.__ModuleLoader__.load({
|
|
|
505
518
|
// 浏览器端的会话列表(含标题、cwd)会随时变化:变了就重渲染。
|
|
506
519
|
const sessionStore = sessionsService(props?.sessions)
|
|
507
520
|
const [sessionTick, setSessionTick] = useState(0)
|
|
521
|
+
// 这个服务由 dsh-api-session-controller 在 effect 里晚于插件 apply 提供:
|
|
522
|
+
// 首次渲染拿不到就轮询一段时间,否则面板永远等不到会话列表。
|
|
523
|
+
const [storeMissing, setStoreMissing] = useState(false)
|
|
508
524
|
useEffect(() => {
|
|
509
525
|
const store = sessionStore?.list
|
|
510
|
-
if (store === undefined || store === null || typeof store.subscribe !== 'function')
|
|
526
|
+
if (store === undefined || store === null || typeof store.subscribe !== 'function') {
|
|
527
|
+
setStoreMissing(true)
|
|
528
|
+
return undefined
|
|
529
|
+
}
|
|
530
|
+
setStoreMissing(false)
|
|
511
531
|
const off = store.subscribe(() => setSessionTick((tick) => tick + 1))
|
|
512
532
|
return () => { if (typeof off === 'function') off() }
|
|
513
533
|
}, [sessionStore])
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (!storeMissing) return undefined
|
|
536
|
+
let tries = 0
|
|
537
|
+
const timer = setInterval(() => {
|
|
538
|
+
tries += 1
|
|
539
|
+
if (sessionsService(props?.sessions) !== undefined || tries >= 40) {
|
|
540
|
+
clearInterval(timer)
|
|
541
|
+
setSessionTick((tick) => tick + 1)
|
|
542
|
+
}
|
|
543
|
+
}, 500)
|
|
544
|
+
return () => clearInterval(timer)
|
|
545
|
+
}, [storeMissing])
|
|
514
546
|
|
|
515
547
|
// 让覆盖层能立刻刷新面板(点「去配置」时面板可能已经挂载着)。
|
|
516
548
|
useEffect(() => {
|
|
@@ -642,8 +674,9 @@ window.__ModuleLoader__.load({
|
|
|
642
674
|
+ (probe.source === 'sessions' ? '(live 兜底)' : '')
|
|
643
675
|
const listError = probe?.error !== undefined ? ` · list 报错:${probe.error}` : ''
|
|
644
676
|
const noCwd = probe?.withoutCwd > 0 ? ` · ${probe.withoutCwd} 个没有 cwd` : ''
|
|
677
|
+
const subs = probe?.subagents > 0 ? ` · 已跳过 ${probe.subagents} 个子代理会话` : ''
|
|
645
678
|
return `插件 v${hostVersion} · 状态存储 v${state.storeVersion ?? '—'}`
|
|
646
|
-
+ ` · 宿主会话列表:${sessions}${listError}${noCwd}`
|
|
679
|
+
+ ` · 宿主会话列表:${sessions}${listError}${noCwd}${subs}`
|
|
647
680
|
+ ` · 浏览器会话列表:${clientCount} 个`
|
|
648
681
|
+ ` · 工作区 ${grouped.length} + 未分组 ${ungrouped.length} · 挂在目录下的会话 ${sessionTotal}`
|
|
649
682
|
}
|
|
@@ -1031,13 +1064,15 @@ window.__ModuleLoader__.load({
|
|
|
1031
1064
|
|
|
1032
1065
|
useEffect(() => {
|
|
1033
1066
|
ensureStyles()
|
|
1034
|
-
const sessions = ctx.get('sessions')
|
|
1035
|
-
if (sessions === undefined || sessions === null) return undefined
|
|
1036
|
-
|
|
1037
1067
|
let disposed = false
|
|
1068
|
+
let timer = null
|
|
1069
|
+
let unsubscribe = null
|
|
1070
|
+
let tries = 0
|
|
1071
|
+
let list = null
|
|
1072
|
+
|
|
1038
1073
|
const currentSession = () => {
|
|
1039
1074
|
try {
|
|
1040
|
-
const snapshot =
|
|
1075
|
+
const snapshot = list?.getSnapshot?.()
|
|
1041
1076
|
const byId = snapshot?.byId ?? {}
|
|
1042
1077
|
for (const [id, item] of Object.entries(byId)) {
|
|
1043
1078
|
if (Number(item?.retainedBy?.mainView) > 0) return { id, cwd: item?.cwd }
|
|
@@ -1067,12 +1102,26 @@ window.__ModuleLoader__.load({
|
|
|
1067
1102
|
}
|
|
1068
1103
|
}
|
|
1069
1104
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1105
|
+
// 会话列表服务由 dsh-api-session-controller 在 effect 里晚于插件 apply 提供
|
|
1106
|
+
// (覆盖层更早挂载),所以这里要有上限地重试,否则永远看不到会话。
|
|
1107
|
+
const attach = () => {
|
|
1108
|
+
if (disposed) return
|
|
1109
|
+
const sessions = ctx.get('sessions')
|
|
1110
|
+
if (sessions === null || sessions === undefined) {
|
|
1111
|
+
tries += 1
|
|
1112
|
+
if (tries < 40) timer = setTimeout(attach, 500)
|
|
1113
|
+
return
|
|
1114
|
+
}
|
|
1115
|
+
list = sessions.list ?? null
|
|
1116
|
+
evaluate()
|
|
1117
|
+
unsubscribe = typeof sessions.list?.subscribe === 'function'
|
|
1118
|
+
? sessions.list.subscribe(() => { evaluate() })
|
|
1119
|
+
: null
|
|
1120
|
+
}
|
|
1121
|
+
attach()
|
|
1074
1122
|
return () => {
|
|
1075
1123
|
disposed = true
|
|
1124
|
+
if (timer !== null) clearTimeout(timer)
|
|
1076
1125
|
if (typeof unsubscribe === 'function') unsubscribe()
|
|
1077
1126
|
}
|
|
1078
1127
|
}, [])
|
package/lib/index.js
CHANGED
|
@@ -263,9 +263,16 @@ async function sessionProbe(ctx) {
|
|
|
263
263
|
shape = 'throw'
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
+
// 子代理会话(origin: 'subagent' / 带 parentSession)也在这份列表里,
|
|
267
|
+
// 但侧栏不把它们当独立会话显示(它们是某个会话派生出来的)。
|
|
268
|
+
// 一并过滤,否则一个工作区会冒出几十个「会话」(用户实测过 13 个里 12 个是子代理)。
|
|
269
|
+
const isSubagentRow = (row) => row?.parentSessionId !== undefined || row?.origin === 'subagent'
|
|
270
|
+
const allRows = list
|
|
271
|
+
const visibleRows = allRows.filter((row) => !isSubagentRow(row))
|
|
272
|
+
const skippedSubagents = allRows.length - visibleRows.length
|
|
266
273
|
// sessionController 给不出任何会话时(服务缺失、list 抛错、列表为空……),
|
|
267
274
|
// 用宿主 live 会话服务兜底,让界面至少能列出「当前装载中的会话」。
|
|
268
|
-
let rows =
|
|
275
|
+
let rows = visibleRows
|
|
269
276
|
let source = rows.length > 0 ? 'sessionController' : undefined
|
|
270
277
|
if (rows.length === 0) {
|
|
271
278
|
const live = liveSessionRows(ctx)
|
|
@@ -285,12 +292,14 @@ async function sessionProbe(ctx) {
|
|
|
285
292
|
}
|
|
286
293
|
if (samples.length < 5 && !samples.includes(cwd)) samples.push(cwd)
|
|
287
294
|
}
|
|
288
|
-
log('sessionProbe:', rows.length, 'rows from', source ?? 'none', ',', withoutCwd, 'without cwd, shape', shape
|
|
295
|
+
log('sessionProbe:', rows.length, 'rows from', source ?? 'none', ',', withoutCwd, 'without cwd, shape', shape,
|
|
296
|
+
',', skippedSubagents, 'subagent rows skipped')
|
|
289
297
|
return {
|
|
290
298
|
available: source !== undefined,
|
|
291
299
|
rows,
|
|
292
300
|
count: rows.length,
|
|
293
301
|
withoutCwd,
|
|
302
|
+
subagents: skippedSubagents,
|
|
294
303
|
samples,
|
|
295
304
|
shape,
|
|
296
305
|
source,
|
|
@@ -1133,6 +1142,7 @@ async function snapshot(ctx, state) {
|
|
|
1133
1142
|
available: probe.available,
|
|
1134
1143
|
count: probe.count,
|
|
1135
1144
|
withoutCwd: probe.withoutCwd ?? 0,
|
|
1145
|
+
subagents: probe.subagents ?? 0,
|
|
1136
1146
|
error: probe.error,
|
|
1137
1147
|
samples: probe.samples ?? [],
|
|
1138
1148
|
shape: probe.shape,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-workspace-api-key",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A DeepSeek Harness plugin that binds a separate DeepSeek API key to each workspace, or to a single session, so the spend of one key stays with the sessions you choose.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|