@michengai/dsh-archive-manager 1.0.0 → 1.0.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ Published release notes are retained below; new versions are added without remov
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 1.0.2 - 2026-09-21
10
+
11
+ - Fix legacy session repair incorrectly rejecting Windows short and extended paths while retaining protection against directory links.
12
+
13
+ ## 1.0.1 - 2026-09-21
14
+
15
+ - Fix navigation errors and incorrect turn counts when session data is malformed.
16
+ - Remove personal information from documentation screenshots and reduce package size.
17
+
9
18
  ## 1.0.0 - 2026-09-21
10
19
 
11
20
  The first 1.0 release expands session organization, search, previews, and targeted repair since 0.1.44. The package name and existing data remain unchanged; no session migration is required. Existing archive, restore, permanent deletion, and native conversation navigation remain available.
@@ -6,6 +6,15 @@
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 1.0.2 - 2026-09-21
10
+
11
+ - 修复 Windows 短路径和扩展路径导致旧会话修复被误拦截的问题,保留目录链接安全防护。
12
+
13
+ ## 1.0.1 - 2026-09-21
14
+
15
+ - 修复会话数据异常时的导航报错和轮次统计问题。
16
+ - 移除文档截图中的个人信息,缩小安装包体积。
17
+
9
18
  ## 1.0.0 - 2026-09-21
10
19
 
11
20
  从 0.1.44 升级到首个 1.0 正式版本,集中完善会话整理、检索、预览与异常修复。沿用原有安装包名和数据,不要求迁移会话;现有归档、恢复、永久删除及原生会话导航继续保留。
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/branding/dsh-banner-en.png" alt="DSH Archive Manager" width="100%">
2
+ <img src="assets/branding/dsh-banner-en.webp" alt="DSH Archive Manager" width="100%">
3
3
  </p>
4
4
 
5
5
  <div align="center">
@@ -8,12 +8,13 @@
8
8
 
9
9
  **Safely manage archived sessions in DeepSeek Harness**
10
10
 
11
- [简体中文](README.zh-CN.md) · [Changelog](CHANGELOG.md) · [1.0.0 release notes](RELEASE_NOTES.md) · [Apache-2.0](LICENSE)
11
+ [简体中文](README.zh-CN.md) · [Changelog](CHANGELOG.md) · [Apache-2.0](LICENSE)
12
12
 
13
13
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
14
14
  [![npm package](https://img.shields.io/npm/v/%40michengai%2Fdsh-archive-manager.svg?label=npm%20package)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
15
15
  [![npm downloads](https://img.shields.io/npm/dt/%40michengai%2Fdsh-archive-manager.svg?label=npm%20downloads)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
16
16
  [![DSH Web Plugin](https://img.shields.io/badge/DSH%20Web-Plugin-0f766e.svg)](https://github.com/MichengAI/dsh-archive-manager)
17
+ [![DSH supported through 0.1.6-alpha.2](https://img.shields.io/badge/DSH-up%20to%200.1.6--alpha.2-2563eb.svg)](#prerequisites)
17
18
  </div>
18
19
 
19
20
  > DSH Archive Manager is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
@@ -29,7 +30,7 @@ Put inactive conversations away and find them again when needed, keeping everyda
29
30
  - **Organize important chats**: save favorites and preview idle cleanup while protecting favorites and active work.
30
31
  - **Inspect and troubleshoot**: preview without restoring, copy IDs/paths, diagnose errors, and repair supported legacy logs.
31
32
 
32
- > This README describes 1.0.0. See the release notes for the complete feature list and upgrade boundaries.
33
+ > This README describes 1.0.0. See the [changelog](CHANGELOG.md) for the complete feature list and upgrade boundaries.
33
34
 
34
35
  ## Session diagnosis and repair
35
36
 
@@ -55,11 +56,11 @@ The archive page collects read failures, including archived IDs with missing sum
55
56
 
56
57
  Search, favorite, preview, restore, and clean up in **Settings → Archived sessions → Archived**:
57
58
 
58
- ![Archived: shared search filters, favorites, and restore](assets/screenshots/archived-sessions.png)
59
+ ![Archived: shared search filters, favorites, and restore](assets/screenshots/archived-sessions.webp)
59
60
 
60
61
  Switch to **Unarchived** for the same filters, idle cleanup previews, and project-wide archiving:
61
62
 
62
- ![Unarchived: idle cleanup and project archiving](assets/screenshots/unarchived-sessions.png)
63
+ ![Unarchived: idle cleanup and project archiving](assets/screenshots/unarchived-sessions.webp)
63
64
 
64
65
  > Screenshots show the pre-release development build labeled 0.1.44; this release is 1.0.0. The sidebar belongs to the installed UI plugin and does not represent new sidebar features in this release.
65
66
 
@@ -188,7 +189,7 @@ pnpm build
188
189
  dsh plugin --profile web add .
189
190
  ```
190
191
 
191
- Restart DSH Web and hard-refresh your browser afterward. Edit [src](src), not the generated `lib` directory. Run `pnpm test` to validate changes or `pnpm verify` for the full checks.
192
+ Restart DSH Web and hard-refresh your browser afterward. Source code in [src](src) uses strict TypeScript. Generated `lib` files are excluded from Git; `prepack` rebuilds them automatically. Published JavaScript entry points and runtime modules remain unchanged; installing the npm package does not require TypeScript. Run `pnpm typecheck` for type checking, `pnpm test` to validate changes, or `pnpm verify` for the full checks.
192
193
 
193
194
  </details>
194
195
 
package/README.zh-CN.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/branding/dsh-banner.png" alt="DSH Archive Manager" width="100%">
2
+ <img src="assets/branding/dsh-banner.webp" alt="DSH Archive Manager" width="100%">
3
3
  </p>
4
4
 
5
5
  <div align="center">
@@ -8,12 +8,13 @@
8
8
 
9
9
  **在 DeepSeek Harness 中安全管理已归档会话**
10
10
 
11
- [English](README.md) · [更新日志](CHANGELOG.zh-CN.md) · [1.0.0 发行说明](RELEASE_NOTES.md) · [Apache-2.0](LICENSE)
11
+ [English](README.md) · [更新日志](CHANGELOG.zh-CN.md) · [Apache-2.0](LICENSE)
12
12
 
13
13
  [![许可证:Apache-2.0](https://img.shields.io/badge/许可证-Apache--2.0-blue.svg)](LICENSE)
14
14
  [![npm package](https://img.shields.io/npm/v/%40michengai%2Fdsh-archive-manager.svg?label=npm%20package)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
15
15
  [![npm 下载量](https://img.shields.io/npm/dt/%40michengai%2Fdsh-archive-manager.svg?label=npm%20%E4%B8%8B%E8%BD%BD%E9%87%8F)](https://www.npmjs.com/package/@michengai/dsh-archive-manager)
16
16
  [![DSH Web Plugin](https://img.shields.io/badge/DSH%20Web-Plugin-0f766e.svg)](https://github.com/MichengAI/dsh-archive-manager)
17
+ [![DSH 支持至 0.1.6-alpha.2](https://img.shields.io/badge/DSH-%E6%94%AF%E6%8C%81%E8%87%B3%200.1.6--alpha.2-2563eb.svg)](#前置条件)
17
18
  </div>
18
19
 
19
20
  > DSH Archive Manager 是社区维护的 DeepSeek Harness(DSH)插件,并非 DeepSeek AI 官方产品。
@@ -29,7 +30,7 @@
29
30
  - **整理重要会话**:收藏常用聊天,预览闲置归档候选,保护收藏与活跃任务。
30
31
  - **查看与排障**:不恢复即可快速预览,复制会话 ID/路径,诊断异常并定向修复支持的旧日志。
31
32
 
32
- > 本文对应 1.0.0 版本,完整功能清单与升级边界见发行说明。
33
+ > 本文对应 1.0.0 版本,完整功能清单与升级边界见[更新日志](CHANGELOG.zh-CN.md)。
33
34
 
34
35
  ## 异常会话诊断与修复
35
36
 
@@ -59,11 +60,11 @@
59
60
 
60
61
  在「设置 → 归档会话 → 已归档」检索、收藏、预览、恢复和清理:
61
62
 
62
- ![已归档会话:统一检索筛选、收藏与恢复](assets/screenshots/archived-sessions.png)
63
+ ![已归档会话:统一检索筛选、收藏与恢复](assets/screenshots/archived-sessions.webp)
63
64
 
64
65
  切换到「未归档」,使用相同筛选条件整理会话,预览闲置归档或按项目批量归档:
65
66
 
66
- ![未归档会话:闲置整理与项目归档](assets/screenshots/unarchived-sessions.png)
67
+ ![未归档会话:闲置整理与项目归档](assets/screenshots/unarchived-sessions.webp)
67
68
 
68
69
  > 截图来自发布前开发版,界面中版本标识为 0.1.44;本次发行版本为 1.0.0。侧栏由当前安装的 UI 插件提供,不代表本插件新增的侧栏功能。
69
70
 
@@ -192,7 +193,7 @@ pnpm build
192
193
  dsh plugin --profile web add .
193
194
  ```
194
195
 
195
- 完成后重启 DSH Web 并硬刷新浏览器。修改 [src](src) 中的源码,不直接编辑生成目录 `lib`;使用 `pnpm test` 验证修改,使用 `pnpm verify` 执行完整检查。
196
+ 完成后重启 DSH Web 并硬刷新浏览器。业务源码统一在 [src](src) 中使用 TypeScript 严格模式;共享协议在 `contracts.ts`,宿主版本适配在 `host-compat.ts`、`client-compat.ts`,客户端组件与状态类型在 `client-types.ts`。`lib` 是本地构建目录,不纳入 Git,也不直接编辑;`prepack` 会在打包前自动构建,npm 包仍包含 `lib`。`pnpm typecheck` 检查业务源码和类型负例,`pnpm test` 先检查类型、构建再执行回归,`pnpm verify` 还执行兼容矩阵、打包和生成物不被 Git 跟踪的检查。测试与构建脚本继续使用 `.mjs`;对外发布仍为 JavaScript,不要求宿主加载 TypeScript。
196
197
 
197
198
  </details>
198
199
 
@@ -1,3 +1,4 @@
1
- 中文 README 使用 dsh-banner.png,英文 README 使用 dsh-banner-en.png,全宽展示。
1
+ 中文 README 使用 dsh-banner.webp,英文 README 使用 dsh-banner-en.webp,全宽展示。
2
2
  两张横幅均为 1.0.0 版本,尺寸 2172×724,通过 custom API 生成并经用户确认。
3
3
  仓库标识仍保留 assets/icon.png,供插件图标和其他入口复用。
4
+ 横幅采用 WebP quality=90、method=6,截图采用无损 WebP;均保留原始分辨率。
Binary file
@@ -1,3 +1,4 @@
1
+ import { record, errorMessage, errorCode } from "./contracts.js";
1
2
  const id = (value) => {
2
3
  if (typeof value !== "string" || !value.length || value.length > 1024) throw new TypeError("\u4F1A\u8BDD ID \u65E0\u6548");
3
4
  return value;
@@ -6,11 +7,13 @@ const queryText = (value, required = false) => {
6
7
  if (typeof value !== "string" || value.length > 200 || required && !value.trim()) throw new TypeError("\u5173\u952E\u8BCD\u987B\u4E3A 1\uFF5E200 \u4E2A\u5B57\u7B26");
7
8
  return value.trim();
8
9
  };
9
- const searchInputSchema = { parse(value) {
10
+ const searchInputSchema = { parse(input) {
11
+ const value = record(input);
10
12
  if (!value || !Array.isArray(value.sessionIds) || !value.sessionIds.length || value.sessionIds.length > 20) throw new TypeError("\u6BCF\u6B21\u68C0\u7D22\u987B\u5305\u542B 1\uFF5E20 \u4E2A\u4F1A\u8BDD");
11
13
  return { sessionIds: [...new Set(value.sessionIds.map(id))], query: queryText(value.query, true) };
12
14
  } };
13
- const previewInputSchema = { parse(value) {
15
+ const previewInputSchema = { parse(input) {
16
+ const value = record(input);
14
17
  if (!value) throw new TypeError("\u9884\u89C8\u53C2\u6570\u65E0\u6548");
15
18
  return { sessionId: id(value.sessionId), query: queryText(value.query ?? "") };
16
19
  } };
@@ -19,49 +22,57 @@ const boundedString = (value, max) => {
19
22
  return value;
20
23
  };
21
24
  const sequence = (value) => {
22
- if (!Number.isSafeInteger(value) || value < 0) throw new TypeError("\u6D88\u606F\u5E8F\u53F7\u65E0\u6548");
25
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new TypeError("\u6D88\u606F\u5E8F\u53F7\u65E0\u6548");
23
26
  return value;
24
27
  };
25
- const searchResultSchema = { parse(value) {
28
+ const searchResultSchema = { parse(input) {
29
+ const value = record(input);
26
30
  if (!value || !Array.isArray(value.items) || !Array.isArray(value.failures) || value.items.length > 20 || value.failures.length > 20) throw new TypeError("\u68C0\u7D22\u7ED3\u679C\u65E0\u6548");
27
31
  return {
28
- items: value.items.map((row) => ({ sessionId: id(row.sessionId), seq: sequence(row.seq), snippet: boundedString(row.snippet, 400) })),
29
- failures: value.failures.map((row) => ({ sessionId: id(row.sessionId), message: boundedString(row.message, 500) }))
32
+ items: value.items.map(record).map((row) => ({ sessionId: id(row.sessionId), seq: sequence(row.seq), snippet: boundedString(row.snippet, 400) })),
33
+ failures: value.failures.map(record).map((row) => ({ sessionId: id(row.sessionId), message: boundedString(row.message, 500) }))
30
34
  };
31
35
  } };
32
- const previewResultSchema = { parse(value) {
36
+ const previewResultSchema = { parse(input) {
37
+ const value = record(input);
33
38
  if (!value || !Array.isArray(value.messages) || value.messages.length > 8 || typeof value.hasEarlier !== "boolean" || typeof value.hasLater !== "boolean") throw new TypeError("\u9884\u89C8\u7ED3\u679C\u65E0\u6548");
34
39
  return {
35
40
  sessionId: id(value.sessionId),
36
41
  hasEarlier: value.hasEarlier,
37
42
  hasLater: value.hasLater,
38
- messages: value.messages.map((row) => {
39
- if (!["user", "assistant"].includes(row.role) || typeof row.truncated !== "boolean") throw new TypeError("\u6D88\u606F\u89D2\u8272\u6216\u622A\u65AD\u72B6\u6001\u65E0\u6548");
43
+ messages: value.messages.map(record).map((row) => {
44
+ if (typeof row.role !== "string" || !["user", "assistant"].includes(row.role) || typeof row.truncated !== "boolean") throw new TypeError("\u6D88\u606F\u89D2\u8272\u6216\u622A\u65AD\u72B6\u6001\u65E0\u6548");
40
45
  return { seq: sequence(row.seq), role: row.role, text: boundedString(row.text, 2e3), truncated: row.truncated };
41
46
  })
42
47
  };
43
48
  } };
44
- const detailsInputSchema = { parse(value) {
49
+ const detailsInputSchema = { parse(input) {
50
+ const value = record(input);
45
51
  if (!value || !Array.isArray(value.sessionIds) || !value.sessionIds.length || value.sessionIds.length > 20) throw new TypeError("\u6BCF\u6B21\u987B\u5305\u542B 1\uFF5E20 \u4E2A\u4F1A\u8BDD");
46
52
  return { sessionIds: [...new Set(value.sessionIds.map(id))] };
47
53
  } };
48
- const detailsResultSchema = { parse(value) {
54
+ const detailsResultSchema = { parse(input) {
55
+ const value = record(input);
49
56
  if (!value || !Array.isArray(value.items) || value.items.length > 20) throw new TypeError("\u4F1A\u8BDD\u8BE6\u60C5\u65E0\u6548");
50
- return { items: value.items.map((row) => ({ sessionId: id(row.sessionId), ...row.createdAt !== void 0 ? { createdAt: sequence(row.createdAt) } : {}, turnCount: row.turnCount === null ? null : sequence(row.turnCount), path: row.path === null ? null : boundedString(row.path, 32768), error: boundedString(row.error, 500) })) };
57
+ return { items: value.items.map(record).map((row) => ({ sessionId: id(row.sessionId), ...row.createdAt !== void 0 ? { createdAt: sequence(row.createdAt) } : {}, turnCount: row.turnCount === null ? null : sequence(row.turnCount), path: row.path === null ? null : boundedString(row.path, 32768), error: boundedString(row.error, 500) })) };
51
58
  } };
52
59
  function countConversationTurns(events) {
53
60
  if (!Array.isArray(events)) throw new TypeError("\u4F1A\u8BDD\u65E5\u5FD7\u65E0\u6548");
54
- return events.filter((event) => {
55
- if (event.type !== "user/message") return false;
56
- const message = event.data?.message ?? event.data;
57
- return message && (!message.source?.kind || message.source.kind === "user");
61
+ return events.filter((value) => {
62
+ if (!value || typeof value !== "object" || !("type" in value) || value.type !== "user/message") return false;
63
+ const data = "data" in value ? value.data : void 0;
64
+ const message = (data && typeof data === "object" && "message" in data ? data.message : void 0) ?? data;
65
+ if (!message) return false;
66
+ const source = typeof message === "object" && "source" in message ? message.source : void 0;
67
+ const kind = source && typeof source === "object" && "kind" in source ? source.kind : void 0;
68
+ return !kind || kind === "user";
58
69
  }).length;
59
70
  }
60
71
  function compareTurnCounts(left, right, order) {
61
- const a = Number.isSafeInteger(left) && left >= 0;
62
- const b = Number.isSafeInteger(right) && right >= 0;
72
+ const a = typeof left === "number" && Number.isSafeInteger(left) && left >= 0;
73
+ const b = typeof right === "number" && Number.isSafeInteger(right) && right >= 0;
63
74
  if (a !== b) return a ? -1 : 1;
64
- return a ? order === "turnsAsc" ? left - right : right - left : 0;
75
+ return a && b ? order === "turnsAsc" ? left - right : right - left : 0;
65
76
  }
66
77
  async function copySessionText(text, clipboard = globalThis.navigator?.clipboard) {
67
78
  if (typeof text !== "string" || !text) throw Object.assign(new Error("\u6CA1\u6709\u53EF\u590D\u5236\u7684\u5185\u5BB9"), { code: "copy.empty" });
@@ -74,7 +85,8 @@ async function copySessionText(text, clipboard = globalThis.navigator?.clipboard
74
85
  }
75
86
  function discoveryInvocations() {
76
87
  const codec = (name, schema) => ({ mode: "strict", typeSymbol: `@michengai/dsh-archive-manager/types#${name}`, create: () => schema, schema });
77
- return [["searchSessionContent", searchInputSchema, searchResultSchema], ["diagnoseSession", repairInputSchema, repairResultSchema], ["repairSession", repairInputSchema, repairResultSchema], ["sessionDetails", detailsInputSchema, detailsResultSchema], ["searchArchivedContent", searchInputSchema, searchResultSchema], ["previewArchivedSession", previewInputSchema, previewResultSchema]].map(([method, input, output]) => ({
88
+ const methods = [["searchSessionContent", searchInputSchema, searchResultSchema], ["diagnoseSession", repairInputSchema, repairResultSchema], ["repairSession", repairInputSchema, repairResultSchema], ["sessionDetails", detailsInputSchema, detailsResultSchema], ["searchArchivedContent", searchInputSchema, searchResultSchema], ["previewArchivedSession", previewInputSchema, previewResultSchema]];
89
+ return methods.map(([method, input, output]) => ({
78
90
  id: `@michengai/dsh-archive-manager#workspaceRegistry/${method}`,
79
91
  service: "workspaceRegistry",
80
92
  namespace: "workspaceRegistry",
@@ -88,11 +100,13 @@ function discoveryInvocations() {
88
100
  function extractConversation(events) {
89
101
  if (!Array.isArray(events)) throw new TypeError("\u4F1A\u8BDD\u65E5\u5FD7\u65E0\u6548");
90
102
  const messages = [];
91
- for (const event of events) {
103
+ for (const event of events.map(record)) {
92
104
  if (event.type !== "user/message" && event.type !== "assistant/message") continue;
93
- const message = event.data?.message ?? event.data;
105
+ const data = event.data && typeof event.data === "object" ? record(event.data) : {};
106
+ const message = data.message && typeof data.message === "object" ? record(data.message) : data;
94
107
  const role = event.type === "user/message" ? "user" : "assistant";
95
- if (role === "user" && message?.source?.kind && message.source.kind !== "user") continue;
108
+ const source = message.source && typeof message.source === "object" ? record(message.source) : {};
109
+ if (role === "user" && source.kind && source.kind !== "user") continue;
96
110
  const content = message?.content;
97
111
  const text = typeof content === "string" ? content : Array.isArray(content) ? content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n") : "";
98
112
  if (text.trim()) messages.push({ seq: sequence(event.seq), role, text });
@@ -130,7 +144,7 @@ function findTextRange(text, query) {
130
144
  const next = folded + character.toLowerCase().length;
131
145
  if (start === void 0 && next > position) start = original;
132
146
  original += character.length;
133
- if (next >= position + needle.length) return { start, end: original };
147
+ if (next >= position + needle.length) return { start: start ?? original, end: original };
134
148
  folded = next;
135
149
  }
136
150
  return null;
@@ -158,7 +172,7 @@ function validUpdatedRange(from, to) {
158
172
  function matchesUpdatedRange(value, from, to) {
159
173
  if (!validUpdatedRange(from, to)) return false;
160
174
  if (!from && !to) return true;
161
- const time = typeof value === "number" ? value : Date.parse(value);
175
+ const time = typeof value === "number" ? value : Date.parse(value ?? "");
162
176
  return Number.isFinite(time) && (!from || time >= dayBoundary(from)) && (!to || time < dayBoundary(to, true));
163
177
  }
164
178
  async function searchArchiveBatches(ids, query, call, { signal, onProgress } = {}) {
@@ -179,7 +193,7 @@ async function searchArchiveBatches(ids, query, call, { signal, onProgress } = {
179
193
  return { items, failures };
180
194
  }
181
195
  function classifySessionError(error) {
182
- const message = String(error?.message ?? error);
196
+ const message = errorMessage(error);
183
197
  const descriptions = {
184
198
  "repair-frame": ["\u65E7\u7248\u4FEE\u590D\u751F\u6210\u7684\u538B\u7F29\u683C\u5F0F\u4E0D\u7B26\u5408\u5BBF\u4E3B\u8981\u6C42", "\u91CD\u65B0\u8BCA\u65AD\u53EF\u7EA0\u6B63\u538B\u7F29\u5E27\u683C\u5F0F\uFF0C\u539F\u59CB\u65E5\u5FD7\u4FDD\u6301\u4E0D\u53D8\u3002"],
185
199
  "legacy-source": ["\u65E7\u7248\u6D88\u606F\u6765\u6E90\u4E0D\u53D7\u65B0\u7248 DSH \u652F\u6301", "\u8FD0\u884C\u8BCA\u65AD\uFF0C\u68C0\u67E5\u662F\u5426\u4E3A\u53EF\u8F6C\u6362\u7684\u65E7\u81EA\u52A8\u5316\u6765\u6E90\u3002"],
@@ -196,17 +210,20 @@ function classifySessionError(error) {
196
210
  ["permission", /EACCES|EPERM|permission/i],
197
211
  ["corrupt", /corrupt|JSON|incomplete|truncat|损坏/i]
198
212
  ];
199
- const explicit = Object.hasOwn(codes, error?.code) ? codes[error.code] : error?.code;
213
+ const rawCode = errorCode(error);
214
+ const explicit = typeof rawCode === "string" ? codes[rawCode] ?? rawCode : "";
200
215
  const code = Object.hasOwn(descriptions, explicit) ? explicit : patterns.find(([, pattern]) => pattern.test(message))?.[0] ?? "unknown";
201
216
  const [reason, advice] = descriptions[code];
202
217
  return { code, reason, advice };
203
218
  }
204
- const repairInputSchema = { parse(value) {
219
+ const repairInputSchema = { parse(input) {
220
+ const value = record(input);
205
221
  const sessionId = id(value?.sessionId);
206
- if (value?.token !== void 0 && !/^[a-f0-9]{64}$/.test(value.token)) throw new TypeError("\u4FEE\u590D\u51ED\u636E\u65E0\u6548");
222
+ if (value?.token !== void 0 && (typeof value.token !== "string" || !/^[a-f0-9]{64}$/.test(value.token))) throw new TypeError("\u4FEE\u590D\u51ED\u636E\u65E0\u6548");
207
223
  return { sessionId, ...value.token !== void 0 ? { token: value.token } : {} };
208
224
  } };
209
- const repairResultSchema = { parse(value) {
225
+ const repairResultSchema = { parse(input) {
226
+ const value = record(input);
210
227
  if (!value || typeof value.repairable !== "boolean" || typeof value.repaired !== "boolean") throw new TypeError("\u8BCA\u65AD\u7ED3\u679C\u65E0\u6548");
211
228
  return { ...value.code !== void 0 ? { code: boundedString(value.code, 64) } : {}, sessionId: id(value.sessionId), repairable: value.repairable, repaired: value.repaired, reason: boundedString(value.reason, 1e3), advice: boundedString(value.advice, 1e3), token: boundedString(value.token ?? "", 64), count: sequence(value.count ?? 0) };
212
229
  } };
@@ -1,4 +1,6 @@
1
+ import { errorMessage } from "./contracts.js";
1
2
  class ArchiveNavigationError extends Error {
3
+ code;
2
4
  constructor(code) {
3
5
  super(code);
4
6
  this.name = "ArchiveNavigationError";
@@ -6,18 +8,22 @@ class ArchiveNavigationError extends Error {
6
8
  }
7
9
  }
8
10
  function formatArchiveNavigationError(error, t) {
9
- return error instanceof ArchiveNavigationError ? t(`archives.${error.code}`) : String(error?.message ?? error);
11
+ return error instanceof ArchiveNavigationError ? t(`archives.${error.code}`) : errorMessage(error);
10
12
  }
11
13
  function retainInfoSnapshot(sessions, id) {
12
14
  if (typeof sessions?.retainInfo !== "function") return;
13
15
  const source = sessions.retainInfo(id);
14
- return typeof source?.getSnapshot === "function" ? source.getSnapshot() : source;
16
+ const value = source && typeof source === "object" && "getSnapshot" in source && typeof source.getSnapshot === "function" ? source.getSnapshot() : source;
17
+ if (!value || typeof value !== "object" || !("retainedBy" in value)) return;
18
+ const retainedBy = value.retainedBy;
19
+ if (!retainedBy || typeof retainedBy !== "object" || !("mainView" in retainedBy) || typeof retainedBy.mainView !== "number") return;
20
+ return { retainedBy: { mainView: retainedBy.mainView } };
15
21
  }
16
22
  function mainViewSessionId(list) {
17
23
  const byId = list?.byId;
18
24
  if (byId === void 0) return;
19
25
  for (const session of Object.values(byId)) {
20
- if ((session?.retainedBy?.mainView ?? 0) > 0) return session.id;
26
+ if (session && (session.retainedBy?.mainView ?? 0) > 0) return session.id;
21
27
  }
22
28
  }
23
29
  function currentSessionId(list) {
@@ -46,10 +52,10 @@ function allowArchivedNavigation(navigation, sessions, workspaces, { onOpened =
46
52
  const wrapped = function(...args) {
47
53
  if (allowed !== void 0 && viewing(allowed) && workspaces.list.getSnapshot().archivedSessionIds.includes(allowed)) return false;
48
54
  allowed = void 0;
49
- return original.apply(this, args);
55
+ return original?.apply(this, args);
50
56
  };
51
57
  const ownsWrapper = () => navigation && Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent")?.value === wrapped;
52
- if (typeof original === "function") {
58
+ if (navigation && typeof original === "function") {
53
59
  try {
54
60
  navigation.clearArchivedCurrent = wrapped;
55
61
  if (!ownsWrapper()) throw new Error("\u5BFC\u822A\u65B9\u6CD5\u672A\u63A5\u7EB3\u5F52\u6863\u9002\u914D");
@@ -88,7 +94,7 @@ function allowArchivedNavigation(navigation, sessions, workspaces, { onOpened =
88
94
  if (!navigation || Object.getOwnPropertyDescriptor(navigation, "clearArchivedCurrent")?.value !== wrapped) return;
89
95
  if (descriptor) Object.defineProperty(navigation, "clearArchivedCurrent", descriptor);
90
96
  else delete navigation.clearArchivedCurrent;
91
- original.call(navigation);
97
+ original?.call(navigation);
92
98
  }
93
99
  };
94
100
  }
@@ -1,15 +1,18 @@
1
+ import { record } from "./contracts.js";
1
2
  function checkedId(value) {
2
3
  if (typeof value !== "string" || value.length === 0 || value.length > 1024) throw new TypeError("\u4F1A\u8BDD ID \u957F\u5EA6\u987B\u4E3A 1\uFF5E1024 \u4E2A\u5B57\u7B26");
3
4
  return value;
4
5
  }
5
6
  const favoriteInputSchema = {
6
- parse(value) {
7
+ parse(input) {
8
+ const value = record(input);
7
9
  if (!value || typeof value !== "object" || typeof value.favorite !== "boolean") throw new TypeError("\u6536\u85CF\u72B6\u6001\u5FC5\u987B\u4E3A\u5E03\u5C14\u503C");
8
10
  return { sessionId: checkedId(value.sessionId), favorite: value.favorite };
9
11
  }
10
12
  };
11
13
  const favoriteStateSchema = {
12
- parse(value) {
14
+ parse(input) {
15
+ const value = record(input);
13
16
  if (!value || !Array.isArray(value.favoriteSessionIds) || value.favoriteSessionIds.length > 1e5) throw new TypeError("\u6536\u85CF\u96C6\u5408\u65E0\u6548");
14
17
  return { favoriteSessionIds: [...new Set(value.favoriteSessionIds.map(checkedId))] };
15
18
  },
@@ -36,9 +39,9 @@ function favoriteInvocations() {
36
39
  }
37
40
  function idleArchiveCandidate(session, { days, now = Date.now(), favorites = /* @__PURE__ */ new Set(), currentId, pending = /* @__PURE__ */ new Map() }) {
38
41
  if (!session || !Number.isInteger(days) || days < 1 || days > 36500) return false;
39
- if (favorites.has(session.id) || session.id === currentId || session.retainedBy?.mainView > 0) return false;
40
- if (session.running || session.runningSubagentCount > 0 || session.pendingInteraction != null || pending.has(session.id)) return false;
41
- const updated = typeof session.updatedAt === "number" ? session.updatedAt : Date.parse(session.updatedAt);
42
+ if (favorites.has(session.id) || session.id === currentId || (session.retainedBy?.mainView ?? 0) > 0) return false;
43
+ if (session.running || (session.runningSubagentCount ?? 0) > 0 || session.pendingInteraction != null || pending.has(session.id)) return false;
44
+ const updated = typeof session.updatedAt === "number" ? session.updatedAt : Date.parse(session.updatedAt ?? "");
42
45
  return Number.isFinite(updated) && updated <= now - days * 864e5;
43
46
  }
44
47
  async function runSessionBatch(ids, operate, { onProgress, refresh } = {}) {