dsh-workspace-api-key 0.2.2 → 0.2.3
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +63 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Then add `"dsh-workspace-api-key"` to `dsh.profile.bundles` in that profile's `p
|
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
74
|
node test/host.test.mjs # 125 assertions
|
|
75
|
-
node test/client.test.mjs #
|
|
75
|
+
node test/client.test.mjs # 124 assertions
|
|
76
76
|
```
|
|
77
77
|
|
|
78
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 and the live-session fallback. 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.
|
package/README.zh.md
CHANGED
|
@@ -72,7 +72,7 @@ pnpm add "dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key"
|
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
74
|
node test/host.test.mjs # 125 条断言
|
|
75
|
-
node test/client.test.mjs #
|
|
75
|
+
node test/client.test.mjs # 124 条断言
|
|
76
76
|
```
|
|
77
77
|
|
|
78
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 之后才可用时返回键仍然有效。
|
package/lib/client.js
CHANGED
|
@@ -212,6 +212,11 @@ window.__ModuleLoader__.load({
|
|
|
212
212
|
return parts[parts.length - 1] ?? p
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/** 会话按最近使用排序。 */
|
|
216
|
+
function sortByUpdated(rows) {
|
|
217
|
+
return rows.slice().sort((a, b) => (b?.updatedAt ?? 0) - (a?.updatedAt ?? 0))
|
|
218
|
+
}
|
|
219
|
+
|
|
215
220
|
/** 浏览器端 sessions 服务快照 → 顶层会话条目(子代理会话不算)。 */
|
|
216
221
|
function clientSessionEntries(sessions) {
|
|
217
222
|
const snap = sessions?.list?.getSnapshot?.()
|
|
@@ -237,9 +242,10 @@ window.__ModuleLoader__.load({
|
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* /state
|
|
245
|
+
* 用浏览器端的会话列表补全「工作区/目录 → 会话」的树。
|
|
246
|
+
* 宿主行始终保留(不会被浏览器端列表挤掉),浏览器端额外只做两件事:
|
|
247
|
+
* 给同名会话补标题/cwd,以及把宿主漏掉的会话补进来(层级从 /state 的
|
|
248
|
+
* 覆盖记录推出)。这样宿主列表为空时面板照样有会话可列。
|
|
243
249
|
*/
|
|
244
250
|
function mergeSessions(state, sessions) {
|
|
245
251
|
const entries = clientSessionEntries(sessions)
|
|
@@ -261,18 +267,27 @@ window.__ModuleLoader__.load({
|
|
|
261
267
|
if (key !== undefined) workspaceByPath.set(key, ws)
|
|
262
268
|
}
|
|
263
269
|
|
|
264
|
-
|
|
265
|
-
const
|
|
270
|
+
// 第一遍:宿主行能补什么就补什么,宿主没有的会话合成一行。
|
|
271
|
+
const enriched = new Map()
|
|
272
|
+
const additions = []
|
|
266
273
|
for (const entry of entries) {
|
|
267
274
|
const host = hostRows.get(entry.sessionId)
|
|
268
|
-
|
|
275
|
+
if (host !== undefined) {
|
|
276
|
+
const patch = {}
|
|
277
|
+
if ((host.title === undefined || host.title === '') && entry.title !== undefined) patch.title = entry.title
|
|
278
|
+
if ((host.cwd === undefined || host.cwd === '') && entry.cwd !== undefined) patch.cwd = entry.cwd
|
|
279
|
+
if (Object.keys(patch).length > 0) enriched.set(entry.sessionId, { ...host, ...patch })
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
const cwd = entry.cwd
|
|
269
283
|
const pathKey = normKey(cwd)
|
|
270
284
|
const ws = pathKey !== undefined ? workspaceByPath.get(pathKey) : undefined
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
const own = overrideBySession.get(entry.sessionId)
|
|
286
|
+
const level = own !== undefined ? 'session' : ws?.configured === true ? 'workspace' : 'default'
|
|
287
|
+
additions.push({
|
|
288
|
+
pathKey,
|
|
289
|
+
workspace: ws,
|
|
290
|
+
row: {
|
|
276
291
|
sessionId: entry.sessionId,
|
|
277
292
|
title: entry.title,
|
|
278
293
|
cwd,
|
|
@@ -297,45 +312,50 @@ window.__ModuleLoader__.load({
|
|
|
297
312
|
effectiveTitle: undefined,
|
|
298
313
|
invalid: (level === 'session' ? own?.invalid : level === 'workspace' ? ws?.invalid : null) ?? null,
|
|
299
314
|
mismatch: null,
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const patch = {}
|
|
303
|
-
if ((row.title === undefined || row.title === '') && entry.title !== undefined) patch.title = entry.title
|
|
304
|
-
if ((row.cwd === undefined || row.cwd === '') && cwd !== undefined) patch.cwd = cwd
|
|
305
|
-
if (Object.keys(patch).length > 0) row = { ...row, ...patch }
|
|
306
|
-
}
|
|
307
|
-
const bucketKey = pathKey ?? ''
|
|
308
|
-
const target = ws !== undefined ? buckets : extras
|
|
309
|
-
const list = target.get(ws !== undefined ? ws.path : bucketKey) ?? []
|
|
310
|
-
list.push(row)
|
|
311
|
-
target.set(ws !== undefined ? ws.path : bucketKey, list)
|
|
315
|
+
},
|
|
316
|
+
})
|
|
312
317
|
}
|
|
313
318
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
319
|
+
const fill = (rows) => (Array.isArray(rows) ? rows : [])
|
|
320
|
+
.map((row) => enriched.get(row?.sessionId) ?? row)
|
|
321
|
+
|
|
322
|
+
// 第二遍:宿主结构照旧,浏览器端多出来的会话按 cwd 归档。
|
|
323
|
+
const workspaces = (state.workspaces ?? []).map((ws) => {
|
|
324
|
+
const rows = fill(ws.sessions)
|
|
325
|
+
for (const item of additions) {
|
|
326
|
+
if (item.workspace?.path === ws.path) rows.push(item.row)
|
|
327
|
+
}
|
|
328
|
+
return { ...ws, sessions: sortByUpdated(rows) }
|
|
329
|
+
})
|
|
330
|
+
|
|
318
331
|
const ungrouped = []
|
|
332
|
+
const extras = new Map()
|
|
333
|
+
for (const item of additions) {
|
|
334
|
+
if (item.workspace !== undefined) continue
|
|
335
|
+
const key = item.pathKey ?? ''
|
|
336
|
+
const list = extras.get(key) ?? []
|
|
337
|
+
list.push(item.row)
|
|
338
|
+
extras.set(key, list)
|
|
339
|
+
}
|
|
340
|
+
for (const group of (state.ungrouped ?? [])) {
|
|
341
|
+
const key = normKey(group?.path) ?? String(group?.path ?? '')
|
|
342
|
+
const rows = fill(group.sessions)
|
|
343
|
+
for (const row of (extras.get(key) ?? [])) rows.push(row)
|
|
344
|
+
extras.delete(key)
|
|
345
|
+
ungrouped.push({ ...group, sessions: sortByUpdated(rows) })
|
|
346
|
+
}
|
|
319
347
|
for (const [key, rows] of extras) {
|
|
320
|
-
const
|
|
321
|
-
const path = known?.path ?? rows[0]?.cwd ?? undefined
|
|
348
|
+
const path = key !== '' ? key : undefined
|
|
322
349
|
ungrouped.push({
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}),
|
|
331
|
-
sessions: sorted(rows),
|
|
350
|
+
id: undefined,
|
|
351
|
+
path: rows[0]?.cwd ?? path,
|
|
352
|
+
title: path !== undefined && path !== '' ? dirName(path) : '(没有目录)',
|
|
353
|
+
kind: 'directory',
|
|
354
|
+
configured: false,
|
|
355
|
+
exists: true,
|
|
356
|
+
sessions: sortByUpdated(rows),
|
|
332
357
|
})
|
|
333
358
|
}
|
|
334
|
-
for (const group of (state.ungrouped ?? [])) {
|
|
335
|
-
if (!ungrouped.some((item) => item.path === group.path)) {
|
|
336
|
-
ungrouped.push({ ...group, sessions: sorted(buckets.get(group.path) ?? []) })
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
359
|
return { ...state, workspaces, ungrouped, sessionSource: 'client' }
|
|
340
360
|
}
|
|
341
361
|
|
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.3",
|
|
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",
|