@liziy/session-queue 0.2.1 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +2 -83
  2. package/index.ts +81 -329
  3. package/package.json +41 -42
package/README.md CHANGED
@@ -1,84 +1,5 @@
1
1
  # @liziy/session-queue
2
2
 
3
- > **Switch UI language:** `/rollback zh` · `/rollback en` (zh = 中文, en = English)
4
-
5
- ---
6
-
7
- ## English
8
-
9
- Convert pi's session tree into a linear rollback queue. Every turn auto-records modified files; rollback restores them in one click.
10
-
11
- ### Features
12
-
13
- - **JIT capture** — snapshots before/after `edit`/`write`/`bash` tool calls (zero startup overhead)
14
- - **Content-addressed storage** — dedup by sha256
15
- - **Dirty check** — refuses to overwrite externally modified files
16
- - **Workspace whitelist** — opt-in per directory; nothing recorded by default
17
- - **Snapshot GC** — mark-sweep keeps disk usage bounded
18
- - **bash tracking** — `rm`/`del`/`mv`/`>`/`>>` (multi-file, simple globs)
19
- - **Session Tree sync** — navigating to an older point auto-rolls back the queue
20
- - **Zero dependencies** · **i18n: zh / en**
21
-
22
- ### Install
23
-
24
- ```bash
25
- pi install npm:@liziy/session-queue
26
- ```
27
-
28
- ### Commands
29
-
30
- | Command | Action |
31
- |---|---|
32
- | `/rollback` | Open main menu (rollback / workspace / GC / settings) |
33
- | `/rollback enable` | Start tracking current directory |
34
- | `/rollback disable` | Pause tracking |
35
- | `/rollback list` | List enabled workspaces |
36
- | `/rollback gc` | Manually run snapshot GC |
37
- | `/rollback zh` | Switch UI to Chinese |
38
- | `/rollback en` | Switch UI to English |
39
-
40
- ### Quick Start
41
-
42
- 1. `/rollback enable` — start tracking
43
- 2. Work normally; each `turn_end` writes a checkpoint
44
- 3. `/rollback` → select a checkpoint → confirm → files restored
45
- 4. Optional: open Session Tree, click an older message, type a new question — queue auto-syncs
46
-
47
- ### Status Bar
48
-
49
- | Indicator | Meaning |
50
- |---|---|
51
- | (hidden) | No workspace active |
52
- | `● 记录` (cyan) | Workspace tracking only |
53
- | `● 同步` (green) | Tracking + Session Tree auto-sync |
54
-
55
- ### Rollback Semantics
56
-
57
- - **Files**: each file's `beforeHash` from the **earliest** discarded turn is restored — reverts to the state right before the first modification, not an intermediate snapshot
58
- - **Queue**: `entries.slice(0, targetIdx)` — drops target and all later entries
59
- - **Conflicts**: skipped files are kept as a `residual` entry (re-tryable)
60
- - **Safety**: missing snapshot = skip + warn, never delete blindly
61
-
62
- ### Storage
63
-
64
- ```
65
- ~/.pi/agent/extensions/session-queue/
66
- ├── config.json # workspaces, followSessionTree, lang
67
- ├── queue-{sessionId}.json # last 10 entries + changes
68
- └── snapshots/{hash}.content # content-addressed
69
- ```
70
-
71
- ### Limits
72
-
73
- - Tracks `edit` / `write` / partial `bash` only (no `sed -i`, `npm install`, subshells)
74
- - 10-entry rolling window; older turns are not recorded or restorable
75
- - Doesn't undo pi's tree; use `/tree` for that
76
- - Directory create/remove are not reverted
77
-
78
- ---
79
-
80
- ## 中文
81
-
82
3
  把 pi 的会话从「树」改为「队列」:每个 turn 自动记录修改的文件,回滚时一键还原。
83
4
 
84
5
  ### 特性
@@ -90,7 +11,7 @@ pi install npm:@liziy/session-queue
90
11
  - **snapshot GC**:标记-扫描回收,控制磁盘占用
91
12
  - **bash 追踪**:`rm`/`del`/`mv`/`>`/`>>`(多文件、简单 glob)
92
13
  - **Session Tree 同步**:导航到旧对话点时队列自动回滚
93
- - **零依赖** · **i18n: zh / en**
14
+ - **零依赖**
94
15
 
95
16
  ### 安装
96
17
 
@@ -107,8 +28,6 @@ pi install npm:@liziy/session-queue
107
28
  | `/rollback disable` | 暂停记录 |
108
29
  | `/rollback list` | 列出已启用工作区 |
109
30
  | `/rollback gc` | 手动执行 GC |
110
- | `/rollback zh` | 切换界面为中文 |
111
- | `/rollback en` | 切换界面为英文 |
112
31
 
113
32
  ### 快速上手
114
33
 
@@ -136,7 +55,7 @@ pi install npm:@liziy/session-queue
136
55
 
137
56
  ```
138
57
  ~/.pi/agent/extensions/session-queue/
139
- ├── config.json # 工作区、followSessionTree、lang
58
+ ├── config.json # 工作区、followSessionTree
140
59
  ├── queue-{sessionId}.json # 最近 10 条 entry + changes
141
60
  └── snapshots/{hash}.content # 内容寻址
142
61
  ```
package/index.ts CHANGED
@@ -76,7 +76,6 @@ interface QueueData {
76
76
  interface Config {
77
77
  workspaces: string[];
78
78
  followSessionTree?: boolean; // 是否在 Session Tree 导航时自动回滚队列
79
- lang?: Lang; // 语言:'zh' | 'en',默认 'zh'
80
79
  }
81
80
 
82
81
  // =============================================================================
@@ -115,237 +114,11 @@ const ANSI = {
115
114
  green: "\x1b[32m", // 亮绿
116
115
  };
117
116
 
118
- // =============================================================================
119
- // i18n
120
- // =============================================================================
121
-
122
- type Lang = "zh" | "en";
123
- type I18nValue = string | ((...args: any[]) => string);
124
-
125
- const I18N: Record<Lang, Record<string, I18nValue>> = {
126
- zh: {
127
- // === 状态栏 ===
128
- "status.sync": `${ANSI.green}●${ANSI.reset} 同步`,
129
- "status.record": `${ANSI.cyan}●${ANSI.reset} 记录`,
130
-
131
- // === 主菜单 ===
132
- "menu.rollbackHistory": (n: number) => `📋 回滚历史 (${n} 个变更点)`,
133
- "menu.manageWorkspaces": "📂 管理工作区",
134
- "menu.clearQueue": "🗑️ 清空当前队列",
135
- "menu.deleteAndClear": "⏸ 删除并清空",
136
- "menu.enableCurrent": "✅ 启用当前工作区",
137
- "menu.followSystemTree": (on: boolean) => `🔗 跟随系统Tree (${on ? "开" : "关"})`,
138
- "menu.collectOrphans": "🧹 回收孤儿快照",
139
- "menu.exit": "❌ 退出菜单",
140
-
141
- // === 菜单子项 ===
142
- "menu.back": "← 返回",
143
- "menu.workspaceCurrent": " ← 当前",
144
- "menu.pauseThisWorkspace": "⏸ 暂停此工作区",
145
- "menu.enableThisWorkspace": "✅ 启用此工作区",
146
- "menu.removeFromList": "🗑️ 从列表中删除",
147
-
148
- // === 选择器标题 ===
149
- "select.mainTitle": "会话队列回滚",
150
- "select.workspacesTitle": (n: number) => `已启用的工作区 (${n})`,
151
- "select.workspaceActionTitle": (ws: string) => `工作区: ${ws}`,
152
- "select.rollbackTitle": (n: number) => `回滚到哪个检查点?(${n} 个检查点 + 当前状态)`,
153
-
154
- // === 确认弹窗 ===
155
- "confirm.rollbackTitle": (text: string) => `确认回滚到「${text}」?`,
156
- "confirm.willProcess": (n: number) => `将处理 ${n} 个文件变更:`,
157
- "confirm.conflictsWarn": (n: number) => `⚠ ${n} 个文件被外部修改:`,
158
- "confirm.btnForceOverwrite": "✅ 强制覆盖并回滚",
159
- "confirm.btnSkipConflicts": "⏭ 跳过冲突,回滚其他",
160
- "confirm.btnConfirm": "✅ 确定回滚",
161
- "confirm.btnCancel": "❌ 取消",
162
-
163
- // === 预览格式 ===
164
- "format.restore": (n: number, files: string) => ` 还原(${n}):${files}`,
165
- "format.create": (n: number, files: string) => ` 新增(${n}):${files}`,
166
- "format.remove": (n: number, files: string) => ` 删除(${n}):${files}`,
167
- "format.noFileChanges": " (无文件变更)",
168
- "format.bashTag": " ⚠bash",
169
- "format.currentNode": " ← 当前节点",
170
- "format.currentState": "当前状态",
171
- "format.restored": (n: number) => `还原 ${n}`,
172
- "format.created": (n: number) => `新增 ${n}`,
173
- "format.removed": (n: number) => `删除 ${n}`,
174
- "format.skippedConflicts": (n: number) => `,跳过 ${n} 个冲突`,
175
- "format.skipped": (n: number) => `,跳过 ${n} 个`,
176
- "format.missingSnapshot": (n: number) => `(其中 ${n} 个快照丢失,请手动检查)`,
177
- "format.noChange": "无变更",
178
-
179
- // === 通知 ===
180
- "notify.needInteractive": "rollback 需要交互模式",
181
- "notify.noSession": "无活跃 session",
182
- "notify.processing": "正在处理上一轮,请稍候",
183
- "notify.emptyQueue": "队列为空",
184
- "notify.onlyTurn0": "只有 Turn 0,没有可回滚的记录",
185
- "notify.noCheckpoints": "没有可回滚的检查点",
186
- "notify.currentNode": "当前节点不可回滚,请选其他 turn",
187
- "notify.rolledBack": (text: string) => `已回滚到「${text}」`,
188
- "notify.rolledBackWith": (text: string, parts: string) => `已回滚到「${text}」:${parts}`,
189
- "notify.autoRollback": (text: string) => `↩️ Session Tree 导航 → 自动回滚到「${text}」`,
190
- "notify.autoRollbackWith": (text: string, parts: string) => `↩️ Session Tree 导航 → 自动回滚到「${text}」:${parts}`,
191
- "notify.queueCleared": (snap: number, queue?: number) => `✅ 队列已清空,回收 ${snap} 个孤儿快照${queue ? `,清理 ${queue} 个旧 queue 文件` : ""}`,
192
- "notify.deletedAndCleared": (snap: number, queue?: number) => `⏸ 已删除并清空当前 session 的记录,回收 ${snap} 个孤儿快照${queue ? `,清理 ${queue} 个旧 queue 文件` : ""}`,
193
- "notify.paused": (ws: string, snap: number, queue?: number) => `⏸ 已暂停:${ws},回收 ${snap} 个孤儿快照${queue ? `,清理 ${queue} 个旧 queue 文件` : ""}`,
194
- "notify.removedFromList": (ws: string) => `🗑️ 已从列表中删除:${ws}`,
195
- "notify.enabledTracking": (path: string) => `✅ 已启用变更记录:${path}`,
196
- "notify.enabled": (ws: string) => `✅ 已启用:${ws}`,
197
- "notify.noWorkspace": "当前工作区未启用",
198
- "notify.workspaceActive": "当前工作区已启用",
199
- "notify.otherActive": "已有其他工作区启用中,请先暂停",
200
- "notify.noWorkspaces": "没有已启用的工作区",
201
- "notify.queueAlreadyEmpty": "队列已经为空",
202
- "notify.followOn": "🔗 已开启:Session Tree 导航将自动回滚队列",
203
- "notify.followOff": "⏸ 已关闭:Session Tree 导航不会自动回滚队列",
204
- "notify.langSwitched": (lang: string) => `🌐 已切换语言:${lang}(zh=中文,en=English)`,
205
- "notify.langInvalid": (lang: string) => `⚠ 无效语言代码:${lang}(可选:zh、en)`,
206
- "notify.gcResult": (scanned: number, deleted: number, live: number, queueDeleted?: number, queueKept?: number) =>
207
- `🧹 扫描 ${scanned} 个快照,删除 ${deleted} 个孤儿,存活 ${live} 个` +
208
- `${queueDeleted ? `;清理 ${queueDeleted} 个旧 queue 文件,保留 ${queueKept ?? 0} 个` : ""}`,
209
-
210
- // === Debug ===
211
- "debug.fromExtension": "[SQ-debug] session_tree: fromExtension=true 跳过",
212
- "debug.activeWorkspaceNull": (sess: string) => `[SQ-debug] session_tree: activeWorkspace=null, currentSession=${sess}`,
213
- "debug.toggleOff": "[SQ-debug] session_tree: toggle off",
214
- "debug.isProcessing": "[SQ-debug] session_tree: isProcessing=true",
215
- "debug.noNav": (id: string) => `[SQ-debug] session_tree: 无实际导航 (newLeafId===oldLeafId=${id})`,
216
- "debug.emptyQueue": "[SQ-debug] session_tree: queue.entries=[]",
217
- "debug.nav": (src: string, navId: string, leafId: string, qLen: number, qCi: number, sess: string) =>
218
- `[SQ-debug] nav: ${src}=${navId}, leafId=${leafId}, queue(${qLen}e/${qCi}ci), session=${sess}`,
219
- "debug.noNavMsg": "[SQ-debug] session_tree: 未找到 nav user msg",
220
- "debug.navNoMatch": (navId: string, entries: string) => `[SQ-debug] session_tree: nav=${navId} 未匹配 entries=${entries}`,
221
- "debug.idxOutOfRange": (idx: number, len: number) => `[SQ-debug] session_tree: idx=${idx} >= ${len}(超出队列范围),跳过`,
222
- "debug.rollbackFired": (idx: number, len: number) => `[SQ-debug] session_tree 回滚 idx=${idx} entries=${len}`,
223
- "debug.exception": (msg: string) => `[SQ-debug] session_tree 异常: ${msg}`,
224
-
225
- // === 命令描述 ===
226
- "cmd.description": "会话队列回滚(单入口菜单);/rollback zh|en 切换语言",
227
- },
228
-
229
- en: {
230
- // === Status bar ===
231
- "status.sync": `${ANSI.green}●${ANSI.reset} Sync`,
232
- "status.record": `${ANSI.cyan}●${ANSI.reset} Record`,
233
-
234
- // === Main menu ===
235
- "menu.rollbackHistory": (n: number) => `📋 Rollback History (${n} checkpoints)`,
236
- "menu.manageWorkspaces": "📂 Manage Workspaces",
237
- "menu.clearQueue": "🗑️ Clear Current Queue",
238
- "menu.deleteAndClear": "⏸ Delete & Clear",
239
- "menu.enableCurrent": "✅ Enable Current Workspace",
240
- "menu.followSystemTree": (on: boolean) => `🔗 Follow System Tree (${on ? "on" : "off"})`,
241
- "menu.collectOrphans": "🧹 Collect Orphan Snapshots",
242
- "menu.exit": "❌ Exit Menu",
243
-
244
- // === Submenu ===
245
- "menu.back": "← Back",
246
- "menu.workspaceCurrent": " ← current",
247
- "menu.pauseThisWorkspace": "⏸ Pause This Workspace",
248
- "menu.enableThisWorkspace": "✅ Enable This Workspace",
249
- "menu.removeFromList": "🗑️ Remove From List",
250
-
251
- // === Select titles ===
252
- "select.mainTitle": "Session Queue Rollback",
253
- "select.workspacesTitle": (n: number) => `Enabled Workspaces (${n})`,
254
- "select.workspaceActionTitle": (ws: string) => `Workspace: ${ws}`,
255
- "select.rollbackTitle": (n: number) => `Rollback to which checkpoint? (${n} checkpoints + current state)`,
256
-
257
- // === Confirm dialog ===
258
- "confirm.rollbackTitle": (text: string) => `Confirm rollback to "${text}"?`,
259
- "confirm.willProcess": (n: number) => `Will process ${n} file change(s):`,
260
- "confirm.conflictsWarn": (n: number) => `⚠ ${n} file(s) externally modified:`,
261
- "confirm.btnForceOverwrite": "✅ Force overwrite and rollback",
262
- "confirm.btnSkipConflicts": "⏭ Skip conflicts, rollback others",
263
- "confirm.btnConfirm": "✅ Confirm Rollback",
264
- "confirm.btnCancel": "❌ Cancel",
265
-
266
- // === Preview format ===
267
- "format.restore": (n: number, files: string) => ` Restore (${n}): ${files}`,
268
- "format.create": (n: number, files: string) => ` Create (${n}): ${files}`,
269
- "format.remove": (n: number, files: string) => ` Remove (${n}): ${files}`,
270
- "format.noFileChanges": " (no file changes)",
271
- "format.bashTag": " ⚠bash",
272
- "format.currentNode": " ← current node",
273
- "format.currentState": "Current State",
274
- "format.restored": (n: number) => `Restored ${n}`,
275
- "format.created": (n: number) => `Created ${n}`,
276
- "format.removed": (n: number) => `Removed ${n}`,
277
- "format.skippedConflicts": (n: number) => `, skipped ${n} conflict(s)`,
278
- "format.skipped": (n: number) => `, skipped ${n}`,
279
- "format.missingSnapshot": (n: number) => `(${n} snapshot(s) missing, please check manually)`,
280
- "format.noChange": "no changes",
281
-
282
- // === Notifications ===
283
- "notify.needInteractive": "rollback requires interactive mode",
284
- "notify.noSession": "No active session",
285
- "notify.processing": "Processing previous turn, please wait",
286
- "notify.emptyQueue": "Queue is empty",
287
- "notify.onlyTurn0": "Only Turn 0, no rollback records",
288
- "notify.noCheckpoints": "No checkpoints to roll back to",
289
- "notify.currentNode": "Current node cannot be rolled back, choose another turn",
290
- "notify.rolledBack": (text: string) => `Rolled back to "${text}"`,
291
- "notify.rolledBackWith": (text: string, parts: string) => `Rolled back to "${text}": ${parts}`,
292
- "notify.autoRollback": (text: string) => `↩️ Session Tree nav → auto rollback to "${text}"`,
293
- "notify.autoRollbackWith": (text: string, parts: string) => `↩️ Session Tree nav → auto rollback to "${text}": ${parts}`,
294
- "notify.queueCleared": (snap: number, queue?: number) => `✅ Queue cleared, collected ${snap} orphan snapshot(s)${queue ? `, cleaned ${queue} old queue file(s)` : ""}`,
295
- "notify.deletedAndCleared": (snap: number, queue?: number) => `⏸ Deleted and cleared current session's records, collected ${snap} orphan snapshot(s)${queue ? `, cleaned ${queue} old queue file(s)` : ""}`,
296
- "notify.paused": (ws: string, snap: number, queue?: number) => `⏸ Paused: ${ws}, collected ${snap} orphan snapshot(s)${queue ? `, cleaned ${queue} old queue file(s)` : ""}`,
297
- "notify.removedFromList": (ws: string) => `🗑️ Removed from list: ${ws}`,
298
- "notify.enabledTracking": (path: string) => `✅ Tracking enabled: ${path}`,
299
- "notify.enabled": (ws: string) => `✅ Enabled: ${ws}`,
300
- "notify.noWorkspace": "Current workspace not enabled",
301
- "notify.workspaceActive": "Current workspace already enabled",
302
- "notify.otherActive": "Another workspace is active, please pause it first",
303
- "notify.noWorkspaces": "No enabled workspaces",
304
- "notify.queueAlreadyEmpty": "Queue is already empty",
305
- "notify.followOn": "🔗 Enabled: Session Tree navigation will auto-rollback queue",
306
- "notify.followOff": "⏸ Disabled: Session Tree navigation will not auto-rollback queue",
307
- "notify.langSwitched": (lang: string) => `🌐 Language switched: ${lang} (zh=中文, en=English)`,
308
- "notify.langInvalid": (lang: string) => `⚠ Invalid language code: ${lang} (options: zh, en)`,
309
- "notify.gcResult": (scanned: number, deleted: number, live: number, queueDeleted?: number, queueKept?: number) =>
310
- `🧹 Scanned ${scanned} snapshot(s), deleted ${deleted} orphan(s), ${live} live` +
311
- `${queueDeleted ? `; cleaned ${queueDeleted} old queue file(s), kept ${queueKept ?? 0}` : ""}`,
312
-
313
- // === Debug ===
314
- "debug.fromExtension": "[SQ-debug] session_tree: fromExtension=true skipped",
315
- "debug.activeWorkspaceNull": (sess: string) => `[SQ-debug] session_tree: activeWorkspace=null, currentSession=${sess}`,
316
- "debug.toggleOff": "[SQ-debug] session_tree: toggle off",
317
- "debug.isProcessing": "[SQ-debug] session_tree: isProcessing=true",
318
- "debug.noNav": (id: string) => `[SQ-debug] session_tree: no actual nav (newLeafId===oldLeafId=${id})`,
319
- "debug.emptyQueue": "[SQ-debug] session_tree: queue.entries=[]",
320
- "debug.nav": (src: string, navId: string, leafId: string, qLen: number, qCi: number, sess: string) =>
321
- `[SQ-debug] nav: ${src}=${navId}, leafId=${leafId}, queue(${qLen}e/${qCi}ci), session=${sess}`,
322
- "debug.noNavMsg": "[SQ-debug] session_tree: no nav user msg found",
323
- "debug.navNoMatch": (navId: string, entries: string) => `[SQ-debug] session_tree: nav=${navId} not matched entries=${entries}`,
324
- "debug.idxOutOfRange": (idx: number, len: number) => `[SQ-debug] session_tree: idx=${idx} >= ${len} (out of range), skipped`,
325
- "debug.rollbackFired": (idx: number, len: number) => `[SQ-debug] session_tree rollback idx=${idx} entries=${len}`,
326
- "debug.exception": (msg: string) => `[SQ-debug] session_tree exception: ${msg}`,
327
-
328
- // === Command description ===
329
- "cmd.description": "Session queue rollback (single-entry menu); /rollback zh|en to switch language",
330
- },
331
- };
332
-
333
- let currentLang: Lang = "zh";
334
-
335
- function setLang(lang: Lang): void {
336
- if (I18N[lang]) currentLang = lang;
337
- }
338
-
339
- function t(key: string, ...args: any[]): string {
340
- const entry = I18N[currentLang]?.[key] ?? I18N.zh[key] ?? key;
341
- return typeof entry === "function" ? entry(...args) : entry;
342
- }
343
-
344
117
  // 状态文本(3 态:未启用 / 仅记录 / 记录+同步)
345
118
  function getStatusText(): string | undefined {
346
119
  if (!activeWorkspace) return undefined; // 状态 1:未启用 → 不显示
347
- if (followSessionTreeEnabled) return t("status.sync"); // 状态 3:亮绿点
348
- return t("status.record"); // 状态 2:青色点
120
+ if (followSessionTreeEnabled) return `${ANSI.green}●${ANSI.reset} 同步`; // 状态 3:亮绿点
121
+ return `${ANSI.cyan}●${ANSI.reset} 记录`; // 状态 2:青色点
349
122
  }
350
123
 
351
124
  // =============================================================================
@@ -485,9 +258,6 @@ function loadConfig(): Config {
485
258
  if (typeof cfg.followSessionTree === "boolean") {
486
259
  followSessionTreeEnabled = cfg.followSessionTree;
487
260
  }
488
- if (cfg.lang) {
489
- setLang(cfg.lang);
490
- }
491
261
  return cfg;
492
262
  } catch {
493
263
  return { workspaces: [] };
@@ -1148,15 +918,15 @@ function previewRollback(plan: RollbackPlan, workspace: string): RollbackPreview
1148
918
  function formatPreview(preview: RollbackPreview): string {
1149
919
  const lines: string[] = [];
1150
920
  if (preview.restore.length > 0) {
1151
- lines.push(t("format.restore", preview.restore.length, preview.restore.join(", ")));
921
+ lines.push(` 还原(${preview.restore.length}):${preview.restore.join(", ")}`);
1152
922
  }
1153
923
  if (preview.create.length > 0) {
1154
- lines.push(t("format.create", preview.create.length, preview.create.join(", ")));
924
+ lines.push(` 新增(${preview.create.length}):${preview.create.join(", ")}`);
1155
925
  }
1156
926
  if (preview.remove.length > 0) {
1157
- lines.push(t("format.remove", preview.remove.length, preview.remove.join(", ")));
927
+ lines.push(` 删除(${preview.remove.length}):${preview.remove.join(", ")}`);
1158
928
  }
1159
- if (lines.length === 0) lines.push(t("format.noFileChanges"));
929
+ if (lines.length === 0) lines.push(" (无文件变更)");
1160
930
  return lines.join("\n");
1161
931
  }
1162
932
 
@@ -1256,10 +1026,10 @@ function autoRollbackForSessionTree(
1256
1026
 
1257
1027
  // 通知
1258
1028
  const parts: string[] = [];
1259
- if (result.restored > 0) parts.push(t("format.restored", result.restored));
1260
- let msg = t("notify.autoRollback", targetEntry.text.slice(0, 30));
1029
+ if (result.restored > 0) parts.push(`还原 ${result.restored}`);
1030
+ let msg = `↩️ Session Tree 导航 → 自动回滚到「${targetEntry.text.slice(0, 30)}」`;
1261
1031
  if (parts.length > 0) msg += `:${parts.join("、")}`;
1262
- if (result.skipped.length > 0) msg += t("format.skippedConflicts", result.skipped.length);
1032
+ if (result.skipped.length > 0) msg += `,跳过 ${result.skipped.length} 个冲突`;
1263
1033
  ctx.ui.notify(msg, result.skipped.length > 0 ? "warning" : "info");
1264
1034
  }
1265
1035
 
@@ -1280,25 +1050,25 @@ function buildQueueItems(queue: QueueData): QueueItem[] {
1280
1050
 
1281
1051
  async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1282
1052
  if (!ctx.hasUI) {
1283
- ctx.ui.notify(t("notify.needInteractive"), "error");
1053
+ ctx.ui.notify("rollback 需要交互模式", "error");
1284
1054
  return;
1285
1055
  }
1286
1056
  if (!currentSessionId) {
1287
- ctx.ui.notify(t("notify.noSession"), "error");
1057
+ ctx.ui.notify("无活跃 session", "error");
1288
1058
  return;
1289
1059
  }
1290
1060
  if (isProcessing) {
1291
- ctx.ui.notify(t("notify.processing"), "warning");
1061
+ ctx.ui.notify("正在处理上一轮,请稍候", "warning");
1292
1062
  return;
1293
1063
  }
1294
1064
 
1295
1065
  const queue = loadQueue(currentSessionId);
1296
1066
  if (queue.entries.length === 0) {
1297
- ctx.ui.notify(t("notify.emptyQueue"), "info");
1067
+ ctx.ui.notify("队列为空", "info");
1298
1068
  return;
1299
1069
  }
1300
1070
  if (queue.entries.length === 1) {
1301
- ctx.ui.notify(t("notify.onlyTurn0"), "info");
1071
+ ctx.ui.notify("只有 Turn 0,没有可回滚的记录", "info");
1302
1072
  return;
1303
1073
  }
1304
1074
 
@@ -1310,7 +1080,7 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1310
1080
  const phantomCurrent: QueueItem = {
1311
1081
  entry: {
1312
1082
  turnIndex: queue.entries.length,
1313
- text: t("format.currentState"),
1083
+ text: "当前状态",
1314
1084
  timestamp: new Date().toISOString(),
1315
1085
  changes: [],
1316
1086
  },
@@ -1320,17 +1090,17 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1320
1090
  (it) => !it.entry.residual && !(it.entry.turnIndex === 0 && it.entry.text === "(会话起点)"),
1321
1091
  );
1322
1092
  if (checkpoints.length === 0) {
1323
- ctx.ui.notify(t("notify.noCheckpoints"), "info");
1093
+ ctx.ui.notify("没有可回滚的检查点", "info");
1324
1094
  return;
1325
1095
  }
1326
1096
  const orderedDisplay: QueueItem[] = [phantomCurrent, ...[...checkpoints].reverse()];
1327
1097
 
1328
1098
  const selectOptions = orderedDisplay.map((item) => {
1329
- const bashTag = item.entry.changes.some((c) => c.viaBash) ? t("format.bashTag") : "";
1330
- const curTag = item.isCurrent ? t("format.currentNode") : "";
1099
+ const bashTag = item.entry.changes.some((c) => c.viaBash) ? " ⚠bash" : "";
1100
+ const curTag = item.isCurrent ? " ← 当前节点" : "";
1331
1101
  let label: string;
1332
1102
  if (item.isCurrent) {
1333
- label = t("format.currentState");
1103
+ label = "当前状态";
1334
1104
  } else {
1335
1105
  label = item.entry.text;
1336
1106
  }
@@ -1341,7 +1111,7 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1341
1111
  let targetEntry: QueueEntry | null = null;
1342
1112
  while (targetEntry === null) {
1343
1113
  const choice = await ctx.ui.select(
1344
- t("select.rollbackTitle", checkpoints.length),
1114
+ `回滚到哪个检查点?(${checkpoints.length} 个检查点 + 当前状态)`,
1345
1115
  selectOptions,
1346
1116
  );
1347
1117
  if (!choice) return; // 用户取消
@@ -1349,7 +1119,7 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1349
1119
  if (choiceIdx < 0) return;
1350
1120
  const picked = orderedDisplay[choiceIdx];
1351
1121
  if (picked.isCurrent) {
1352
- ctx.ui.notify(t("notify.currentNode"), "info");
1122
+ ctx.ui.notify("当前节点不可回滚,请选其他 turn", "info");
1353
1123
  continue; // 重新提示
1354
1124
  }
1355
1125
  targetEntry = picked.entry;
@@ -1370,15 +1140,15 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1370
1140
  saveQueue(queue);
1371
1141
  maybeGc();
1372
1142
  lastFlushedUserMsgId = null;
1373
- ctx.ui.notify(t("notify.rolledBack", targetEntry.text.slice(0, 30)), "info");
1143
+ ctx.ui.notify(`已回滚到「${targetEntry.text.slice(0, 30)}」`, "info");
1374
1144
  return;
1375
1145
  }
1376
1146
 
1377
1147
  // ── 第三步:单次确认弹窗(确定/取消 + 冲突处理一起) ──
1378
1148
  const preview = previewRollback(plan, activeWorkspace || process.cwd());
1379
1149
  let confirmTitle =
1380
- t("confirm.rollbackTitle", targetEntry.text.slice(0, 30)) + "\n" +
1381
- t("confirm.willProcess", plan.changes.length) + "\n" +
1150
+ `确认回滚到「${targetEntry.text.slice(0, 30)}」?\n` +
1151
+ `将处理 ${plan.changes.length} 个文件变更:\n` +
1382
1152
  formatPreview(preview);
1383
1153
  const hasConflict = plan.conflictPaths.length > 0;
1384
1154
  let confirmButtons: string[];
@@ -1386,14 +1156,14 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1386
1156
  const conflictList = plan.conflictPaths
1387
1157
  .map((p) => " ⚠ " + path.relative(activeWorkspace || process.cwd(), p))
1388
1158
  .join("\n");
1389
- confirmTitle += "\n" + t("confirm.conflictsWarn", plan.conflictPaths.length) + "\n" + conflictList;
1159
+ confirmTitle += `\n ${plan.conflictPaths.length} 个文件被外部修改:\n` + conflictList;
1390
1160
  confirmButtons = [
1391
- t("confirm.btnForceOverwrite"),
1392
- t("confirm.btnSkipConflicts"),
1393
- t("confirm.btnCancel"),
1161
+ "✅ 强制覆盖并回滚",
1162
+ "⏭ 跳过冲突,回滚其他",
1163
+ "❌ 取消",
1394
1164
  ];
1395
1165
  } else {
1396
- confirmButtons = [t("confirm.btnConfirm"), t("confirm.btnCancel")];
1166
+ confirmButtons = ["✅ 确定回滚", "❌ 取消"];
1397
1167
  }
1398
1168
 
1399
1169
  const confirm = await ctx.ui.select(confirmTitle, confirmButtons);
@@ -1448,15 +1218,15 @@ async function showRollbackUI(pi: ExtensionAPI, ctx: any): Promise<void> {
1448
1218
  maybeGc(true);
1449
1219
 
1450
1220
  const parts: string[] = [];
1451
- if (preview.restore.length > 0) parts.push(t("format.restored", preview.restore.length));
1452
- if (preview.create.length > 0) parts.push(t("format.created", preview.create.length));
1453
- if (preview.remove.length > 0) parts.push(t("format.removed", preview.remove.length));
1454
- let msg = t("notify.rolledBackWith", targetEntry.text.slice(0, 30), parts.join("、") || t("format.noChange"));
1221
+ if (preview.restore.length > 0) parts.push(`还原 ${preview.restore.length}`);
1222
+ if (preview.create.length > 0) parts.push(`新增 ${preview.create.length}`);
1223
+ if (preview.remove.length > 0) parts.push(`删除 ${preview.remove.length}`);
1224
+ let msg = `已回滚到「${targetEntry.text.slice(0, 30)}」:${parts.join("、") || "无变更"}`;
1455
1225
  if (result.skipped.length > 0) {
1456
- msg += t("format.skipped", result.skipped.length);
1226
+ msg += `,跳过 ${result.skipped.length} 个`;
1457
1227
  }
1458
1228
  if (result.missingSnapshot.length > 0) {
1459
- msg += t("format.missingSnapshot", result.missingSnapshot.length);
1229
+ msg += `(其中 ${result.missingSnapshot.length} 个快照丢失,请手动检查)`;
1460
1230
  }
1461
1231
  ctx.ui.notify(msg, result.missingSnapshot.length > 0 ? "warning" : "success");
1462
1232
  } finally {
@@ -1501,30 +1271,30 @@ export default function (pi: ExtensionAPI) {
1501
1271
  // 改为:直接拿 branch 中最后一条 user 消息 id 作为 sessionEntryId 匹配。
1502
1272
  pi.on("session_tree", (event, ctx) => {
1503
1273
  if (event.fromExtension) {
1504
- ctx.ui.notify(t("debug.fromExtension"), "info");
1274
+ ctx.ui.notify("[SQ-debug] session_tree: fromExtension=true 跳过", "info");
1505
1275
  return;
1506
1276
  }
1507
1277
  if (!activeWorkspace) {
1508
- ctx.ui.notify(t("debug.activeWorkspaceNull", currentSessionId?.slice(-12) ?? "?"), "info");
1278
+ ctx.ui.notify(`[SQ-debug] session_tree: activeWorkspace=null, currentSession=${currentSessionId?.slice(-12) ?? "?"}`, "info");
1509
1279
  return;
1510
1280
  }
1511
1281
  if (!ctx.hasUI) return;
1512
1282
  if (!followSessionTreeEnabled) {
1513
- ctx.ui.notify(t("debug.toggleOff"), "info");
1283
+ ctx.ui.notify("[SQ-debug] session_tree: toggle off", "info");
1514
1284
  return;
1515
1285
  }
1516
1286
  if (isProcessing) {
1517
- ctx.ui.notify(t("debug.isProcessing"), "info");
1287
+ ctx.ui.notify("[SQ-debug] session_tree: isProcessing=true", "info");
1518
1288
  return;
1519
1289
  }
1520
1290
  if (event.newLeafId === event.oldLeafId) {
1521
- ctx.ui.notify(t("debug.noNav", event.newLeafId?.slice(0, 8) ?? "?"), "info");
1291
+ ctx.ui.notify(`[SQ-debug] session_tree: 无实际导航 (newLeafId===oldLeafId=${event.newLeafId?.slice(0, 8) ?? "?"})`, "info");
1522
1292
  return;
1523
1293
  }
1524
1294
  try {
1525
1295
  const queue = loadQueue(currentSessionId || "");
1526
1296
  if (queue.entries.length === 0) {
1527
- ctx.ui.notify(t("debug.emptyQueue"), "info");
1297
+ ctx.ui.notify("[SQ-debug] session_tree: queue.entries=[]", "info");
1528
1298
  return;
1529
1299
  }
1530
1300
 
@@ -1574,11 +1344,11 @@ export default function (pi: ExtensionAPI) {
1574
1344
  }
1575
1345
 
1576
1346
  ctx.ui.notify(
1577
- t("debug.nav", navSource, navUserMsgId?.slice(0, 8) ?? "?", leafId?.slice(0, 8) ?? "?", queue.entries.length, queue.currentIndex, currentSessionId?.slice(-12) ?? "?"),
1347
+ `[SQ-debug] nav: ${navSource}=${navUserMsgId?.slice(0, 8) ?? "?"}, leafId=${leafId?.slice(0, 8) ?? "?"}, queue(${queue.entries.length}e/${queue.currentIndex}ci), session=${currentSessionId?.slice(-12) ?? "?"}`,
1578
1348
  "info",
1579
1349
  );
1580
1350
  if (!navUserMsgId) {
1581
- ctx.ui.notify(t("debug.noNavMsg"), "info");
1351
+ ctx.ui.notify("[SQ-debug] session_tree: 未找到 nav user msg", "info");
1582
1352
  return;
1583
1353
  }
1584
1354
  // 查找队列中对应的 entry
@@ -1591,17 +1361,17 @@ export default function (pi: ExtensionAPI) {
1591
1361
  // 但 currentIndex 永远 = entries.length-1,所以 idx >= currentIndex 的情形是“未真正导航”。
1592
1362
  // 同样:idx > entries.length-1 的越界情形跳过。
1593
1363
  if (idx < 0) {
1594
- ctx.ui.notify(t("debug.navNoMatch", navUserMsgId?.slice(0, 8) ?? "?", queue.entries.map(e => (e.sessionEntryId || '?').slice(0, 8)).join(',')), "info");
1364
+ ctx.ui.notify(`[SQ-debug] session_tree: nav=${navUserMsgId?.slice(0, 8) ?? "?"} 未匹配 entries=${queue.entries.map(e => (e.sessionEntryId || '?').slice(0, 8)).join(',')}`, "info");
1595
1365
  return;
1596
1366
  }
1597
1367
  // Option C 语义:entry 是检查点。“导航到 q3” 包含 “q3 本身被撤销”(q3 不再在检查点列表中)。
1598
1368
  // 所以即使 idx === currentIndex(队列中最后一个 entry),仍然需要 fire。
1599
1369
  // 唯一需要跳过的是 idx 超出 entries 范围(不可能访问的检查点)。
1600
1370
  if (idx >= queue.entries.length) {
1601
- ctx.ui.notify(t("debug.idxOutOfRange", idx, queue.entries.length), "info");
1371
+ ctx.ui.notify(`[SQ-debug] session_tree: idx=${idx} >= ${queue.entries.length}(超出队列范围),跳过`, "info");
1602
1372
  return;
1603
1373
  }
1604
- ctx.ui.notify(t("debug.rollbackFired", idx, queue.entries.length), "info");
1374
+ ctx.ui.notify(`[SQ-debug] session_tree 回滚 idx=${idx} entries=${queue.entries.length}`, "info");
1605
1375
  isProcessing = true;
1606
1376
  try {
1607
1377
  autoRollbackForSessionTree(ctx, queue, idx);
@@ -1609,7 +1379,7 @@ export default function (pi: ExtensionAPI) {
1609
1379
  isProcessing = false;
1610
1380
  }
1611
1381
  } catch (e: any) {
1612
- ctx.ui.notify(t("debug.exception", e.message), "warning");
1382
+ ctx.ui.notify(`[SQ-debug] session_tree 异常: ${e.message}`, "warning");
1613
1383
  }
1614
1384
  });
1615
1385
 
@@ -1625,28 +1395,10 @@ export default function (pi: ExtensionAPI) {
1625
1395
 
1626
1396
  // ── 命令:/rollback(单入口菜单) ──
1627
1397
  pi.registerCommand("rollback", {
1628
- description: t("cmd.description"),
1398
+ description: "会话队列回滚(单入口菜单)",
1629
1399
  handler: async (args, ctx) => {
1630
- // 子命令:/rollback zh | /rollback en 切换语言
1631
- const sub = args.trim().toLowerCase();
1632
- if (sub === "zh" || sub === "en") {
1633
- const config = loadConfig();
1634
- config.lang = sub;
1635
- saveConfig(config);
1636
- setLang(sub);
1637
- if (ctx.hasUI) {
1638
- ctx.ui.setStatus("session-queue", getStatusText());
1639
- ctx.ui.notify(t("notify.langSwitched", sub), "success");
1640
- }
1641
- return;
1642
- }
1643
- if (sub.length > 0 && sub !== "zh" && sub !== "en") {
1644
- if (ctx.hasUI) ctx.ui.notify(t("notify.langInvalid", args.trim()), "warning");
1645
- return;
1646
- }
1647
-
1648
1400
  if (!ctx.hasUI) {
1649
- ctx.ui.notify(t("notify.needInteractive"), "error");
1401
+ ctx.ui.notify("rollback 需要交互模式", "error");
1650
1402
  return;
1651
1403
  }
1652
1404
 
@@ -1658,17 +1410,17 @@ export default function (pi: ExtensionAPI) {
1658
1410
  const config = loadConfig();
1659
1411
 
1660
1412
  const mainOptions: string[] = [
1661
- t("menu.rollbackHistory", Math.max(0, queueSize - 1)),
1662
- t("menu.manageWorkspaces"),
1663
- t("menu.clearQueue"),
1664
- activeWorkspace ? t("menu.deleteAndClear") : t("menu.enableCurrent"),
1665
- t("menu.followSystemTree", followSessionTreeEnabled),
1666
- t("menu.collectOrphans"),
1667
- t("menu.exit"),
1413
+ `📋 回滚历史 (${Math.max(0, queueSize - 1)} 个变更点)`,
1414
+ "📂 管理工作区",
1415
+ "🗑️ 清空当前队列",
1416
+ activeWorkspace ? "⏸ 删除并清空" : "✅ 启用当前工作区",
1417
+ `🔗 跟随 Session Tree (${followSessionTreeEnabled ? "" : "关"})`,
1418
+ "🧹 回收孤儿快照",
1419
+ "❌ 退出菜单",
1668
1420
  ];
1669
1421
 
1670
1422
  const mainChoice = await ctx.ui.select(
1671
- t("select.mainTitle"),
1423
+ "会话队列回滚",
1672
1424
  mainOptions,
1673
1425
  );
1674
1426
  if (!mainChoice || mainChoice.startsWith("❌")) return;
@@ -1687,11 +1439,11 @@ export default function (pi: ExtensionAPI) {
1687
1439
  } else if (mainChoice.startsWith("🗑️")) {
1688
1440
  // 清空当前队列
1689
1441
  if (!currentSessionId) {
1690
- ctx.ui.notify(t("notify.noSession"), "info");
1442
+ ctx.ui.notify("无活跃 session", "info");
1691
1443
  continue;
1692
1444
  }
1693
1445
  if (queueSize === 0) {
1694
- ctx.ui.notify(t("notify.queueAlreadyEmpty"), "info");
1446
+ ctx.ui.notify("队列已经为空", "info");
1695
1447
  continue;
1696
1448
  }
1697
1449
  const clearedQueue = loadQueue(currentSessionId);
@@ -1707,14 +1459,14 @@ export default function (pi: ExtensionAPI) {
1707
1459
  // 清空后立即 GC,回收该 session 引用的孤儿快照
1708
1460
  const gc = runGc();
1709
1461
  ctx.ui.notify(
1710
- t("notify.queueCleared", gc.snapDeleted, gc.queueDeleted || undefined),
1462
+ `✅ 队列已清空,回收 ${gc.snapDeleted} 个孤儿快照${gc.queueDeleted ? `,清理 ${gc.queueDeleted} 个旧 queue 文件` : ""}`,
1711
1463
  "success",
1712
1464
  );
1713
1465
  // 回到主菜单
1714
1466
  } else if (mainChoice.startsWith("⏸")) {
1715
1467
  // 删除并清空
1716
1468
  if (!activeWorkspace) {
1717
- ctx.ui.notify(t("notify.noWorkspace"), "info");
1469
+ ctx.ui.notify("当前工作区未启用", "info");
1718
1470
  continue;
1719
1471
  }
1720
1472
  removeWorkspace(ctx.cwd);
@@ -1730,21 +1482,21 @@ export default function (pi: ExtensionAPI) {
1730
1482
  // 删除 queue.json 后 GC 回收该 session 独占的孤儿快照
1731
1483
  const gc = runGc();
1732
1484
  ctx.ui.notify(
1733
- t("notify.deletedAndCleared", gc.snapDeleted, gc.queueDeleted || undefined),
1485
+ `⏸ 已删除并清空当前 session 的记录,回收 ${gc.snapDeleted} 个孤儿快照${gc.queueDeleted ? `,清理 ${gc.queueDeleted} 个旧 queue 文件` : ""}`,
1734
1486
  "success",
1735
1487
  );
1736
1488
  return; // 状态已变,退出菜单
1737
1489
  } else if (mainChoice.startsWith("✅")) {
1738
1490
  // 启用当前工作区
1739
1491
  if (activeWorkspace) {
1740
- ctx.ui.notify(t("notify.workspaceActive"), "info");
1492
+ ctx.ui.notify("当前工作区已启用", "info");
1741
1493
  continue;
1742
1494
  }
1743
1495
  const resolved = path.resolve(ctx.cwd);
1744
1496
  addWorkspace(resolved);
1745
1497
  activeWorkspace = resolved;
1746
1498
  ctx.ui.setStatus("session-queue", getStatusText());
1747
- ctx.ui.notify(t("notify.enabledTracking", resolved), "success");
1499
+ ctx.ui.notify(`✅ 已启用变更记录:${resolved}`, "success");
1748
1500
  return; // 状态已变,退出菜单
1749
1501
  } else if (mainChoice.startsWith("🔗")) {
1750
1502
  // 切换 Session Tree 跟随回滚开关
@@ -1761,7 +1513,7 @@ export default function (pi: ExtensionAPI) {
1761
1513
  );
1762
1514
  }
1763
1515
  ctx.ui.notify(
1764
- t(followSessionTreeEnabled ? "notify.followOn" : "notify.followOff"),
1516
+ followSessionTreeEnabled ? "🔗 已开启:Session Tree 导航将自动回滚队列" : "⏸ 已关闭:Session Tree 导航不会自动回滚队列",
1765
1517
  "info",
1766
1518
  );
1767
1519
  // 同步更新状态栏文案
@@ -1771,7 +1523,7 @@ export default function (pi: ExtensionAPI) {
1771
1523
  // 手动 GC
1772
1524
  const gc = runGc();
1773
1525
  ctx.ui.notify(
1774
- t("notify.gcResult", gc.snapScanned, gc.snapDeleted, gc.live, gc.queueDeleted || undefined, gc.queueKept || undefined),
1526
+ `🧹 扫描 ${gc.snapScanned} 个快照,删除 ${gc.snapDeleted} 个孤儿,存活 ${gc.live} 个${gc.queueDeleted ? `;清理 ${gc.queueDeleted} 个旧 queue 文件,保留 ${gc.queueKept ?? 0} 个` : ""}`,
1775
1527
  "success",
1776
1528
  );
1777
1529
  }
@@ -1788,24 +1540,24 @@ async function manageWorkspaces(ctx: any, _config: Config): Promise<boolean> {
1788
1540
  // 一级:列出已启用的工作区
1789
1541
  const config = loadConfig();
1790
1542
  if (config.workspaces.length === 0) {
1791
- ctx.ui.notify(t("notify.noWorkspaces"), "info");
1543
+ ctx.ui.notify("没有已启用的工作区", "info");
1792
1544
  return false;
1793
1545
  }
1794
1546
 
1795
1547
  // 用绝对路径 + 标记当前激活
1796
1548
  const wsOptions = config.workspaces.map((ws) => {
1797
1549
  const isCurrent = activeWorkspace && path.resolve(ws) === activeWorkspace;
1798
- const tag = isCurrent ? t("menu.workspaceCurrent") : "";
1550
+ const tag = isCurrent ? " ← 当前" : "";
1799
1551
  return `${ws}${tag}`;
1800
1552
  });
1801
- wsOptions.push(t("menu.back"));
1553
+ wsOptions.push("← 返回");
1802
1554
 
1803
1555
  const wsChoice = await ctx.ui.select(
1804
- t("select.workspacesTitle", config.workspaces.length),
1556
+ `已启用的工作区 (${config.workspaces.length})`,
1805
1557
  wsOptions,
1806
1558
  );
1807
1559
 
1808
- if (!wsChoice || wsChoice === t("menu.back")) return false;
1560
+ if (!wsChoice || wsChoice === "← 返回") return false;
1809
1561
 
1810
1562
  // 从选项中提取路径(精确匹配)
1811
1563
  const selectedWs = config.workspaces.find((ws) => wsChoice.startsWith(ws));
@@ -1815,19 +1567,19 @@ async function manageWorkspaces(ctx: any, _config: Config): Promise<boolean> {
1815
1567
  const isCurrent = activeWorkspace && path.resolve(selectedWs) === activeWorkspace;
1816
1568
  const opOptions: string[] = [];
1817
1569
  if (isCurrent) {
1818
- opOptions.push(t("menu.pauseThisWorkspace"));
1570
+ opOptions.push("⏸ 暂停此工作区");
1819
1571
  } else {
1820
- opOptions.push(t("menu.enableThisWorkspace"));
1572
+ opOptions.push("✅ 启用此工作区");
1821
1573
  }
1822
- opOptions.push(t("menu.removeFromList"));
1823
- opOptions.push(t("menu.back"));
1574
+ opOptions.push("🗑️ 从列表中删除");
1575
+ opOptions.push("← 返回");
1824
1576
 
1825
1577
  const opChoice = await ctx.ui.select(
1826
- t("select.workspaceActionTitle", selectedWs),
1578
+ `工作区: ${selectedWs}`,
1827
1579
  opOptions,
1828
1580
  );
1829
1581
 
1830
- if (!opChoice || opChoice === t("menu.back")) return false;
1582
+ if (!opChoice || opChoice === "← 返回") return false;
1831
1583
 
1832
1584
  if (opChoice.startsWith("⏸")) {
1833
1585
  // 暂停
@@ -1840,7 +1592,7 @@ async function manageWorkspaces(ctx: any, _config: Config): Promise<boolean> {
1840
1592
  }
1841
1593
  const gc = runGc();
1842
1594
  ctx.ui.notify(
1843
- t("notify.paused", selectedWs, gc.snapDeleted, gc.queueDeleted || undefined),
1595
+ `⏸ 已暂停:${selectedWs},回收 ${gc.snapDeleted} 个孤儿快照${gc.queueDeleted ? `,清理 ${gc.queueDeleted} 个旧 queue 文件` : ""}`,
1844
1596
  "success",
1845
1597
  );
1846
1598
  return true;
@@ -1851,10 +1603,10 @@ async function manageWorkspaces(ctx: any, _config: Config): Promise<boolean> {
1851
1603
  addWorkspace(ctx.cwd);
1852
1604
  activeWorkspace = path.resolve(ctx.cwd);
1853
1605
  ctx.ui.setStatus("session-queue", getStatusText());
1854
- ctx.ui.notify(t("notify.enabled", selectedWs), "success");
1606
+ ctx.ui.notify(`✅ 已启用:${selectedWs}`, "success");
1855
1607
  return true;
1856
1608
  } else {
1857
- ctx.ui.notify(t("notify.otherActive"), "warning");
1609
+ ctx.ui.notify("已有其他工作区启用中,请先暂停", "warning");
1858
1610
  }
1859
1611
  } else if (opChoice.startsWith("🗑️")) {
1860
1612
  // 删除(从 config 移除)
@@ -1865,7 +1617,7 @@ async function manageWorkspaces(ctx: any, _config: Config): Promise<boolean> {
1865
1617
  activeWorkspace = null;
1866
1618
  ctx.ui.setStatus("session-queue", undefined);
1867
1619
  }
1868
- ctx.ui.notify(t("notify.removedFromList", selectedWs), "success");
1620
+ ctx.ui.notify(`🗑️ 已从列表中删除:${selectedWs}`, "success");
1869
1621
  return false; // 状态未变(只是配置变了),可继续菜单
1870
1622
  }
1871
1623
 
package/package.json CHANGED
@@ -1,42 +1,41 @@
1
- {
2
- "name": "@liziy/session-queue",
3
- "version": "0.2.1",
4
- "description": "pi extension: linear session queue with file-change rollback, snapshot GC, dirty check, and Session Tree auto-sync (i18n: zh/en)",
5
- "keywords": [
6
- "pi-extension",
7
- "pi-package",
8
- "session",
9
- "queue",
10
- "rollback",
11
- "file-snapshot",
12
- "checkpoint",
13
- "linear-history",
14
- "memory",
15
- "i18n"
16
- ],
17
- "license": "MIT",
18
- "main": "index.ts",
19
- "files": [
20
- "index.ts",
21
- "README.md"
22
- ],
23
- "pi": {
24
- "extensions": [
25
- "./index.ts"
26
- ]
27
- },
28
- "peerDependencies": {
29
- "@earendil-works/pi-coding-agent": "*",
30
- "@earendil-works/pi-tui": "*"
31
- },
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/yu-d1/pi-extensions.git",
35
- "directory": "packages/session-queue"
36
- },
37
- "homepage": "https://github.com/yu-d1/pi-extensions/tree/main/packages/session-queue#readme",
38
- "bugs": {
39
- "url": "https://github.com/yu-d1/pi-extensions/issues"
40
- },
41
- "author": "liziy"
42
- }
1
+ {
2
+ "name": "@liziy/session-queue",
3
+ "version": "0.2.2",
4
+ "description": "线性队列式回滚 · 文件快照一键还原",
5
+ "keywords": [
6
+ "pi-extension",
7
+ "pi-package",
8
+ "session",
9
+ "queue",
10
+ "rollback",
11
+ "file-snapshot",
12
+ "checkpoint",
13
+ "linear-history",
14
+ "memory"
15
+ ],
16
+ "license": "MIT",
17
+ "main": "index.ts",
18
+ "files": [
19
+ "index.ts",
20
+ "README.md"
21
+ ],
22
+ "pi": {
23
+ "extensions": [
24
+ "./index.ts"
25
+ ]
26
+ },
27
+ "peerDependencies": {
28
+ "@earendil-works/pi-coding-agent": "*",
29
+ "@earendil-works/pi-tui": "*"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/yu-d1/pi-extensions.git",
34
+ "directory": "packages/session-queue"
35
+ },
36
+ "homepage": "https://github.com/yu-d1/pi-extensions/tree/main/packages/session-queue#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/yu-d1/pi-extensions/issues"
39
+ },
40
+ "author": "liziy"
41
+ }