dsh-sessions-manager 3.7.1 → 3.7.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-sessions-manager",
3
3
  "description": "DSH 设置面板会话管理器:归档 / 恢复 / 彻底删除 / 移动到其他工作区,带工作区标签与会话日期;统一「会话管理」面板。Session manager for the DeepSeek Harness settings panel — archive / restore / permanently delete / move sessions across workspaces, with workspace tags & session dates in one unified panel.",
4
- "version": "3.7.1",
4
+ "version": "3.7.3",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -58,14 +58,14 @@
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@deepseek-ai/cordis": "^4.0.1",
61
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
62
- "@deepseek-ai/dsh-workspace": "^0.1.0-rc.6",
63
- "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.0-rc.6",
64
- "@deepseek-ai/dsh-session-query": "^0.1.0-rc.6",
65
- "@deepseek-ai/dsh-storage-domain": "^0.1.0-rc.6",
66
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
67
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
68
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
61
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6",
62
+ "@deepseek-ai/dsh-workspace": ">=0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-session-persistence-jsonl": ">=0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-session-query": ">=0.1.0-rc.6",
65
+ "@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.6",
66
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.6",
67
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.6",
68
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.6",
69
69
  "react": "^18.2.0"
70
70
  },
71
71
  "allowScripts": {
@@ -244,15 +244,58 @@ async function postJSON(path, body) {
244
244
 
245
245
  // 打开一个会话(尤其是侧栏不渲染的子代理会话)。
246
246
  //
247
- // 只走官方公开契约(runtime 的 client api-catalog):
248
- // sessions.open(id) —— 选中会话为当前
249
- // sessions.refreshSubagents(parent) —— 刷新某父会话的直接子代理目录
250
- // sessions.openSubagent(address) —— 用「直接父 + 子」地址打开目录里的子代理
251
- // 拿不到 sessions 服务(老 runtime)时返回 false,由调用方给出降级提示,绝不
252
- // 触碰任何私有内部状态。
247
+ // 只走官方公开契约,按方法存在性分支、不写死核心版本:
248
+ // 旧核心(≤0.1.6) ctx.get('sessions') 自带
249
+ // sessions.open(id) —— 选中会话为当前
250
+ // sessions.refreshSubagents(parent) —— 刷新某父会话的直接子代理目录
251
+ // sessions.openSubagent(address) —— 用「直接父 + 子」地址打开目录里的子代理
252
+ // 新核心(0.1.7+ / 0.2.0+)sessions 退化为会话数据流服务(retain/release/
253
+ // readPage…,没有 open),切换会话的公开入口是 uiWorkspace.openSession(target):
254
+ // target 为会话 id 字符串,或 {parentSessionId, childSessionId, mode} 子代理
255
+ // 地址(与旧 openSubagent 契约同形,replaceMain 经 sessions.retain 原生支持;
256
+ // id 命中已加载目录的子代理时核心会自动解析地址)。核心侧栏点会话走的就是
257
+ // 这条路径(dsh-workspace-kit 0.1.12 起同样从 ctx.sessions.open 迁移过来)。
258
+ // 拿不到任何服务时返回 null,由调用方给出降级提示,绝不触碰私有内部状态。
253
259
  let dsmClientCtx = null
254
260
  function dsmSessionsService() {
255
- try { return dsmClientCtx && typeof dsmClientCtx.get === 'function' ? dsmClientCtx.get('sessions') : null } catch (e) { return null }
261
+ try {
262
+ if (!dsmClientCtx || typeof dsmClientCtx.get !== 'function') return null
263
+ const native = dsmClientCtx.get('sessions')
264
+ if (native && (typeof native.open === 'function' || typeof native.openSubagent === 'function')) return native
265
+ const ui = dsmClientCtx.get('uiWorkspace')
266
+ if (!ui || typeof ui.openSession !== 'function') return native
267
+ let lastOpened = null
268
+ const bridge = {
269
+ open(id) {
270
+ if (id === null || id === undefined) return
271
+ const target = String(id)
272
+ // 地址未加载的子代理会在这里被核心拒绝(unknown session),调用方
273
+ // 依赖抛错落回 openSubagent 地址路径,因此不吞异常。
274
+ ui.openSession(target)
275
+ lastOpened = target
276
+ },
277
+ openSubagent(address) {
278
+ if (address === null || address === undefined) return
279
+ if (typeof address === 'object') {
280
+ // 逐字段原样透传(parentSessionId / childSessionId / mode),新核心
281
+ // 的 retain 直接以该地址解析历史路由。
282
+ ui.openSession(address)
283
+ lastOpened = String(address.childSessionId)
284
+ } else {
285
+ bridge.open(address)
286
+ }
287
+ },
288
+ // 新核心的子代理目录由投影自动维护,无需手动刷新。
289
+ refreshSubagents() {},
290
+ }
291
+ // dsmCurrentSessionId 走 list.getSnapshot().current 校验切换结果;桥接下
292
+ // 核心未公开等价快照,以自己最近一次成功提交的目标为准。
293
+ Object.defineProperty(bridge, 'list', {
294
+ configurable: true,
295
+ get() { return { getSnapshot: () => ({ current: lastOpened }) } },
296
+ })
297
+ return bridge
298
+ } catch (e) { return null }
256
299
  }
257
300
  function dsmCurrentSessionId(svc) {
258
301
  try {
@@ -129,6 +129,37 @@ function failureText(e) {
129
129
  return `${(e && e.name) || ''} ${(e && e.message) || e}`
130
130
  }
131
131
 
132
+ // 0.1.7 起官方把 Session 格式升到 v4,create() 严格要求 header.version ===
133
+ // 后端当前版本(`encodeCurrent requires Session format vN`)。旧代(v2/v3)
134
+ // 会话的 header 直接透传必炸。从该错误里解析后端声明的当前版本号。
135
+ function requiredFormatVersionFromError(e) {
136
+ const m = /requires Session format v(\d+)/.exec(String((e && e.message) || ''))
137
+ return m ? Number(m[1]) : null
138
+ }
139
+
140
+ // 官方 v2→v4 一直接受的 header 字段。旧代 header 可能携带后续版本已移除的
141
+ // 字段(如 seedLength),而 v4 起严格拒绝任何未知字段("unexpected field"),
142
+ // 重试前必须把 header 收敛到这个长期稳定集合。
143
+ const OFFICIAL_HEADER_FIELDS = ['id', 'createdAt', 'cwd', 'isSeeded', 'delegationDepth', 'parentSession', 'origin', 'agentPreset']
144
+
145
+ // 兼容性 create:先用调用方/采样得到的 header 直接建;若后端以「版本不符」
146
+ // 拒绝,则把版本号提到后端声明的当前值、字段收敛到官方稳定集合后重试一次。
147
+ // 只重试这一类错误:真正的词汇表/事件校验错误原样上抛,绝不静默降级。
148
+ async function createWithCompatibleHeader(sp, header, createOptions) {
149
+ try {
150
+ return await sp.create(header, createOptions)
151
+ } catch (e) {
152
+ const required = requiredFormatVersionFromError(e)
153
+ if (required === null || header.version === required) throw e
154
+ const normalized = {}
155
+ for (const key of OFFICIAL_HEADER_FIELDS) {
156
+ if (Object.hasOwn(header, key)) normalized[key] = header[key]
157
+ }
158
+ normalized.version = required
159
+ return await sp.create(normalized, createOptions)
160
+ }
161
+ }
162
+
132
163
  function isAlreadyOwned(e) {
133
164
  return /already owned/i.test(failureText(e))
134
165
  }
@@ -278,7 +309,20 @@ export async function moveSessionToCwd({ sp, sid, header, canonical, events = []
278
309
  error.code = 'DSM_SESSION_DUP_LOG'
279
310
  throw error
280
311
  }
281
- const newHeader = Object.assign({}, header, { cwd: canonical })
312
+ const newHeaderBase = Object.assign({}, header, { cwd: canonical })
313
+ // 写所有权探测可能已为旧代会话发布了当前代;探测成功后再用官方 stat 重新
314
+ // 采样一次 header —— 后端自己编码的 header 天然携带当前格式版本与完整词汇表
315
+ // (0.1.7 的 create 严格要求 header.version === 后端当前版本)。stat 失败则
316
+ // 保留调用方 header,交给 createWithCompatibleHeader 的错误驱动重试兜底。
317
+ let createHeader = newHeaderBase
318
+ if (typeof sp.stat === 'function') {
319
+ try {
320
+ const fresh = await sp.stat(sid)
321
+ if (fresh && fresh.header && fresh.header.id === sid) {
322
+ createHeader = Object.assign({}, fresh.header, { cwd: canonical })
323
+ }
324
+ } catch (e) { /* 保持调用方 header */ }
325
+ }
282
326
  const firstSeq = events.length ? Number(events[0].seq) : 0
283
327
  const replay = firstSeq !== 0 ? events.map((event, index) => ({ ...event, seq: index })) : events
284
328
  const createOptions = header.isSeeded && Number.isSafeInteger(inheritedEventCount) && inheritedEventCount > 0
@@ -298,7 +342,7 @@ export async function moveSessionToCwd({ sp, sid, header, canonical, events = []
298
342
  let writer = null
299
343
  try {
300
344
  try {
301
- writer = await sp.create(newHeader, createOptions)
345
+ writer = await createWithCompatibleHeader(sp, createHeader, createOptions)
302
346
  for (let i = 0; i < replay.length; i += MOVE_BATCH) {
303
347
  await writer.append(replay.slice(i, i + MOVE_BATCH))
304
348
  }