codex-overleaf-link 1.3.7 → 1.3.8

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.
@@ -130,6 +130,10 @@
130
130
  runAcceptTrackedConfirming: 'Accepting…',
131
131
  runAcceptTrackedDone: 'Accepted',
132
132
  runAcceptTrackedDoneTitle: 'This run\'s tracked changes have been accepted',
133
+ runAcceptTrackedNeedsReview: 'Accept changes',
134
+ runAcceptTrackedNeedsReviewTitle: 'Accept this run\'s Overleaf tracked changes',
135
+ runUndoNeedsReview: 'Undo changes',
136
+ runUndoNeedsReviewTitle: 'Undo this run\'s changes',
133
137
  runAcceptTrackedStarted: 'Starting to accept this run\'s Overleaf tracked changes',
134
138
  runAcceptTrackedResult: 'Accept result: accepted {applied} tracked change(s), skipped {skipped}',
135
139
  runAcceptTrackedFailed: 'Accept changes could not be completed',
@@ -298,7 +302,72 @@
298
302
  processed: 'Done {elapsed}',
299
303
  restoredRunStoppedTitle: 'Stopped tracking this run after a page refresh',
300
304
  restoredRunStoppedDetail: 'The plugin reloaded while this run was still marked in progress. It has been marked interrupted to avoid showing a stale status — run the task again to continue.',
301
- restoredRunStoppedStatus: 'Stopped tracking after a page refresh'
305
+ restoredRunStoppedStatus: 'Stopped tracking after a page refresh',
306
+ // FailureReason structured-block section headings (design spec §10–§11).
307
+ failureReason_section_heading: 'Reason',
308
+ failureReason_section_stage: 'Stage',
309
+ failureReason_section_code: 'Code',
310
+ failureReason_section_next: 'Next',
311
+ // FailureReason bilingual high-priority strings (design spec §15.4).
312
+ // Each entry follows the §9 catalog copy. {file} / {activeFile} interpolate from the failure record.
313
+ failureReason_project_snapshot_unavailable_user: 'Codex could not read the Overleaf project snapshot.',
314
+ failureReason_project_snapshot_unavailable_next: 'Refresh Overleaf, then rerun the task.',
315
+ failureReason_selected_context_unresolved_user: 'Codex could not resolve the requested selection or context.',
316
+ failureReason_selected_context_unresolved_next: 'Select the target again or specify the file/section explicitly.',
317
+ failureReason_target_file_not_found_user: 'Codex could not find {file} in this Overleaf project.',
318
+ failureReason_target_file_not_found_next: 'Check the file name/path in Overleaf and retry.',
319
+ failureReason_target_file_open_failed_user: 'Codex could not open {file} in Overleaf.',
320
+ failureReason_target_file_open_failed_next: 'Expand the folder or manually open {file}, then retry.',
321
+ failureReason_target_file_not_active_user: 'Codex could not write {file} because Overleaf was still focused on {activeFile}.',
322
+ failureReason_target_file_not_active_next: 'Open {file} in Overleaf, then retry this run.',
323
+ failureReason_target_editor_not_ready_user: '{file} is active but the editor was not ready before timeout.',
324
+ failureReason_target_editor_not_ready_next: 'Wait for Overleaf to finish loading, then retry.',
325
+ failureReason_stale_source_changed_user: '{file} changed while Codex was working, so Codex did not overwrite it.',
326
+ failureReason_stale_source_changed_next: 'Review the current file, then rerun the task.',
327
+ failureReason_patch_anchor_not_found_user: 'The edit anchor for {file} no longer matches current Overleaf content.',
328
+ failureReason_patch_anchor_not_found_next: 'Rerun the task against the current document.',
329
+ failureReason_write_observed_mismatch_user: 'Codex attempted to write {file}, but the content read back from Overleaf did not match the approved change.',
330
+ failureReason_write_observed_mismatch_next: 'Open Technical Details and compare expected vs observed; use Undo written parts if the document looks wrong.',
331
+ failureReason_reviewing_state_unknown_user: 'Codex could not determine whether Reviewing/Track Changes is enabled.',
332
+ failureReason_reviewing_state_unknown_next: 'Check Overleaf mode manually before retrying.',
333
+ failureReason_editing_not_confirmed_user: 'Editing mode could not be proven stable before writing {file}.',
334
+ failureReason_editing_not_confirmed_next: 'Do not write; check the mode selector and retry.',
335
+ failureReason_tracked_changes_remain_user: 'Accept/reject finished for {file}, but Overleaf still reports remaining tracked changes.',
336
+ failureReason_tracked_changes_remain_next: 'Open Overleaf Reviewing and inspect the remaining changes before continuing.',
337
+ failureReason_undo_not_verified_user: 'Undo ran for {file}, but Codex could not prove the file returned to its pre-run content.',
338
+ failureReason_undo_not_verified_next: 'Inspect {file} manually before continuing.',
339
+ failureReason_accept_not_verified_user: 'Accept appeared to run for {file}, but Codex could not prove the final content or Track Changes state.',
340
+ failureReason_accept_not_verified_next: 'Inspect Overleaf Reviewing before continuing.',
341
+ failureReason_native_bridge_unavailable_user: 'Extension cannot connect to the Codex native host.',
342
+ failureReason_native_bridge_unavailable_next: 'Run install-native or reload the extension.',
343
+ failureReason_codex_no_usable_result_user: 'Local Codex returned no usable final report or operations.',
344
+ failureReason_codex_no_usable_result_next: 'Open Technical Details and resolve the local Codex error.',
345
+ failureReason_codex_project_locked_user: 'Another Codex task is already running for this Overleaf project.',
346
+ failureReason_codex_project_locked_next: 'Wait for the active task to finish, or cancel it before retrying.',
347
+ failureReason_storage_quota_exceeded_user: 'Browser storage quota was exceeded.',
348
+ failureReason_storage_quota_exceeded_next: 'Clear old run history or reduce attachments.',
349
+ failureReason_aborted_project_changed_user: 'Codex stopped a write because Overleaf switched to a different project mid-run.',
350
+ failureReason_aborted_project_changed_next: 'Reopen the original project and rerun the task if you still want this change.',
351
+ failureReason_editor_project_id_unavailable_user: 'Codex could not confirm which Overleaf project the editor is showing, so it did not write.',
352
+ failureReason_editor_project_id_unavailable_next: 'Refresh the Overleaf tab and retry; if it persists, reload the extension.',
353
+ // Welcome-panel + write-guard v1.3.8 add-on (Task 5): Recent-projects
354
+ // variant copy. Spec §5.3–§5.5, §5.8, §5.10 are the source of truth.
355
+ recentProjects_welcome: 'Codex Overleaf Link',
356
+ recentProjects_welcome_subtitle: 'Open a project from the Overleaf list on the left to start.',
357
+ recentProjects_empty: "You haven't run any Codex sessions yet. Open a project from the Overleaf list on the left to start.",
358
+ recentProjects_degraded: 'Sign in to Overleaf to see your recent Codex projects.',
359
+ recentProjects_row_projectLinkUnavailable: 'Project link unavailable.',
360
+ recentProjects_settings_entry: 'Settings & Diagnostics',
361
+ recentProjects_badge_pending: 'pending',
362
+ recentProjects_badge_accepted: 'accepted',
363
+ recentProjects_badge_rejected: 'rejected',
364
+ recentProjects_badge_needs_review: 'pending',
365
+ recentProjects_badge_running: 'running',
366
+ recentProjects_badge_completed: 'completed',
367
+ recentProjects_badge_failed: 'failed',
368
+ recentProjects_badge_background_completed: 'completed in background',
369
+ recentProjects_badge_needs_review_after_navigation: 'pending',
370
+ recentProjects_badge_abandoned_after_navigation: 'abandoned'
302
371
  },
303
372
  zh: {
304
373
  resizePanel: '拖动调整 Codex 面板宽度,双击恢复默认宽度',
@@ -419,6 +488,10 @@
419
488
  runAcceptTrackedConfirming: '接受中…',
420
489
  runAcceptTrackedDone: '已接受',
421
490
  runAcceptTrackedDoneTitle: '本轮留痕改动已被接受',
491
+ runAcceptTrackedNeedsReview: '接受改动 — 需待核对',
492
+ runAcceptTrackedNeedsReviewTitle: '本轮留痕未必已完全接受;请在 Overleaf 中核对后重试。',
493
+ runUndoNeedsReview: '撤销 — 需待核对',
494
+ runUndoNeedsReviewTitle: '本轮撤销未必已完全完成;请在 Overleaf 中核对后重试。',
422
495
  runAcceptTrackedStarted: '开始接受本轮 Overleaf 留痕改动',
423
496
  runAcceptTrackedResult: '接受结果:已接受 {applied} 条留痕,跳过 {skipped} 条',
424
497
  runAcceptTrackedFailed: '没有完成接受改动',
@@ -587,7 +660,71 @@
587
660
  processed: '已处理 {elapsed}',
588
661
  restoredRunStoppedTitle: '页面刷新后已停止跟踪这轮任务',
589
662
  restoredRunStoppedDetail: '插件重新加载时发现这轮任务还标记为处理中。为了避免继续显示过期状态,已把它标记为中断;可以重新运行任务。',
590
- restoredRunStoppedStatus: '页面刷新后已停止跟踪'
663
+ restoredRunStoppedStatus: '页面刷新后已停止跟踪',
664
+ // FailureReason 结构块标题(设计规格 §10–§11)。
665
+ failureReason_section_heading: '原因',
666
+ failureReason_section_stage: '阶段',
667
+ failureReason_section_code: '代码',
668
+ failureReason_section_next: '下一步',
669
+ // FailureReason 高优先级双语文案(设计规格 §15.4)。文案对应 §9 目录的英文 fallback。
670
+ failureReason_project_snapshot_unavailable_user: 'Codex 没能读到 Overleaf 项目快照。',
671
+ failureReason_project_snapshot_unavailable_next: '请刷新 Overleaf,然后重试本轮任务。',
672
+ failureReason_selected_context_unresolved_user: 'Codex 没能解析所选的内容或上下文。',
673
+ failureReason_selected_context_unresolved_next: '请重新选择目标,或者显式指定文件 / 区段。',
674
+ failureReason_target_file_not_found_user: 'Codex 在当前 Overleaf 项目中没有找到 {file}。',
675
+ failureReason_target_file_not_found_next: '请在 Overleaf 中核对文件名和路径后重试。',
676
+ failureReason_target_file_open_failed_user: 'Codex 没能在 Overleaf 中打开 {file}。',
677
+ failureReason_target_file_open_failed_next: '请展开所在文件夹,或手动打开 {file},然后重试。',
678
+ failureReason_target_file_not_active_user: 'Codex 无法写入 {file},因为 Overleaf 仍停留在 {activeFile}。',
679
+ failureReason_target_file_not_active_next: '请在 Overleaf 中打开 {file},然后重试本轮任务。',
680
+ failureReason_target_editor_not_ready_user: '{file} 已成为当前文件,但编辑器在超时前没有就绪。',
681
+ failureReason_target_editor_not_ready_next: '请等 Overleaf 加载完成后再重试。',
682
+ failureReason_stale_source_changed_user: '{file} 在任务执行期间被修改过,Codex 没有覆盖它。',
683
+ failureReason_stale_source_changed_next: '请先查看当前文件内容,再重新运行本轮任务。',
684
+ failureReason_patch_anchor_not_found_user: '{file} 的修改锚点已经无法和当前 Overleaf 内容对齐。',
685
+ failureReason_patch_anchor_not_found_next: '请基于当前文档重新运行任务。',
686
+ failureReason_write_observed_mismatch_user: 'Codex 尝试写入 {file},但写入后从 Overleaf 读回的内容与批准的改动不一致。',
687
+ failureReason_write_observed_mismatch_next: '请打开“技术细节”比对预期与实际内容;如发现异常可使用“撤销已写入部分”。',
688
+ failureReason_reviewing_state_unknown_user: 'Codex 没能确认 Overleaf Reviewing/Track Changes 是否开启。',
689
+ failureReason_reviewing_state_unknown_next: '请在 Overleaf 手动检查留痕状态后再重试。',
690
+ failureReason_editing_not_confirmed_user: '写入 {file} 之前没能确认 Editing 模式已稳定。',
691
+ failureReason_editing_not_confirmed_next: '请暂停写入,检查模式选择器后再重试。',
692
+ failureReason_tracked_changes_remain_user: '{file} 的接受/拒绝操作完成了,但 Overleaf 仍报告存在未处理的留痕改动。',
693
+ failureReason_tracked_changes_remain_next: '请在 Overleaf 审阅面板查看剩余的留痕改动,再决定是否继续。',
694
+ failureReason_undo_not_verified_user: '{file} 的撤销已执行,但 Codex 没能确认文件恢复到本轮写入前的内容。',
695
+ failureReason_undo_not_verified_next: '请先在 Overleaf 手动确认 {file} 的内容,再继续操作。',
696
+ failureReason_accept_not_verified_user: '{file} 的接受改动似乎已执行,但 Codex 没能确认最终内容或留痕状态。',
697
+ failureReason_accept_not_verified_next: '请先在 Overleaf 审阅面板核对后再继续。',
698
+ failureReason_native_bridge_unavailable_user: '扩展无法连接到 Codex Native Host。',
699
+ failureReason_native_bridge_unavailable_next: '请运行 install-native,或者重新加载扩展。',
700
+ failureReason_codex_no_usable_result_user: '本地 Codex 没有返回可用的最终报告或操作。',
701
+ failureReason_codex_no_usable_result_next: '请打开“技术细节”排查本地 Codex 错误。',
702
+ failureReason_codex_project_locked_user: '同一个 Overleaf 项目里已经有一轮 Codex 任务正在运行。',
703
+ failureReason_codex_project_locked_next: '请等待当前任务完成,或先取消当前任务后再重试。',
704
+ failureReason_storage_quota_exceeded_user: '浏览器本地存储配额已超出。',
705
+ failureReason_storage_quota_exceeded_next: '请清理旧的运行历史,或减少附件大小。',
706
+ failureReason_aborted_project_changed_user: 'Codex 已停止一次写入,因为 Overleaf 在写入过程中切换到了另一个项目。',
707
+ failureReason_aborted_project_changed_next: '如仍需此次改动,请回到原项目后重新运行任务。',
708
+ failureReason_editor_project_id_unavailable_user: 'Codex 无法确认当前 Overleaf 编辑器对应的项目,因此没有写入。',
709
+ failureReason_editor_project_id_unavailable_next: '请刷新 Overleaf 页面后重试;如果仍然失败,请重新加载扩展。',
710
+ // Welcome-panel + write-guard v1.3.8 add-on (Task 5): Recent-projects
711
+ // variant copy. 见 spec §5.3–§5.5、§5.8、§5.10。
712
+ recentProjects_welcome: 'Codex Overleaf Link',
713
+ recentProjects_welcome_subtitle: '从左侧 Overleaf 项目列表中打开任意项目即可开始。',
714
+ recentProjects_empty: '你还没有运行过 Codex 会话。从左侧 Overleaf 项目列表中打开任意项目即可开始。',
715
+ recentProjects_degraded: '请登录 Overleaf 后查看你最近的 Codex 项目。',
716
+ recentProjects_row_projectLinkUnavailable: '项目链接不可用。',
717
+ recentProjects_settings_entry: '设置与诊断',
718
+ recentProjects_badge_pending: '待处理',
719
+ recentProjects_badge_accepted: '已接受',
720
+ recentProjects_badge_rejected: '已撤回',
721
+ recentProjects_badge_needs_review: '待处理',
722
+ recentProjects_badge_running: '运行中',
723
+ recentProjects_badge_completed: '已完成',
724
+ recentProjects_badge_failed: '失败',
725
+ recentProjects_badge_background_completed: '后台已完成',
726
+ recentProjects_badge_needs_review_after_navigation: '待处理',
727
+ recentProjects_badge_abandoned_after_navigation: '已中止'
591
728
  }
592
729
  };
593
730
 
@@ -0,0 +1,143 @@
1
+ (function initPathRedaction(root, factory) {
2
+ if (typeof module === 'object' && module.exports) {
3
+ module.exports = factory();
4
+ } else {
5
+ root.CodexOverleafPathRedaction = factory();
6
+ }
7
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function pathRedactionFactory() {
8
+ 'use strict';
9
+
10
+ // -------------------------------------------------------------------------
11
+ // Welcome-panel + write-guard v1.3.8 add-on (Fix C / spec §5.6.2).
12
+ //
13
+ // Canonical local-path sanitizer shared between `computeSafeTaskSummary`
14
+ // (sessionState) and the storage-side audit redaction helpers
15
+ // (`sanitizeBareLocalPaths` in storageDb). One regex set, one placeholder
16
+ // shape, one place to broaden coverage when a new path shape shows up.
17
+ //
18
+ // Spec §5.6.2 explicitly directs implementers NOT to hand-roll narrow
19
+ // path regexes inside individual sanitizers — the audit redaction layer
20
+ // already exists for this. This module is the extraction the spec calls
21
+ // for so both layers stay in sync.
22
+ //
23
+ // Coverage required by the task brief:
24
+ // - /Users/... (Unix macOS home)
25
+ // - /home/... (Unix Linux home)
26
+ // - /private/var/..., /private/... (macOS firmlinks)
27
+ // - /tmp/... (Unix temp)
28
+ // - /var/folders/... (macOS per-user tmp)
29
+ // - /Volumes/... (macOS mounted volumes)
30
+ // - /etc/..., /opt/..., /usr/... (system / opt / usr)
31
+ // - file:///Users/... (file URLs)
32
+ // - C:\Users\bob\foo (Windows backslash)
33
+ // - C:/Users/bob/foo (Windows forward-slash)
34
+ // - \\server\share\foo (Windows UNC)
35
+ // - .codex-overleaf/projects/... (this extension's local workspace)
36
+ //
37
+ // Pattern construction notes:
38
+ // - The Unix branch uses a non-capturing alternation over the top-level
39
+ // directory names so a future addition (e.g. /srv/...) is one entry,
40
+ // not a new regex.
41
+ // - The UNC branch matches `\\` (or `//`) followed by host + share +
42
+ // path. We deliberately accept the forward-slash form too because
43
+ // some tools normalize backslashes to forward slashes when logging.
44
+ // - The file:// branch matches `file://` followed by either two or
45
+ // three slashes (RFC + common mis-encoded variants).
46
+ // -------------------------------------------------------------------------
47
+
48
+ // Top-level Unix directories that mark an absolute local path. We include
49
+ // the most common ones plus a few opt/usr/etc trees so a stray reference
50
+ // does not leak. Adding a new entry is a one-line change here.
51
+ const UNIX_TOPLEVELS = [
52
+ 'Users',
53
+ 'home',
54
+ 'root',
55
+ 'private',
56
+ 'var',
57
+ 'tmp',
58
+ 'Volumes',
59
+ 'etc',
60
+ 'opt',
61
+ 'usr',
62
+ 'srv',
63
+ 'mnt',
64
+ 'media'
65
+ ];
66
+
67
+ // Match a path token to its end-of-word boundary. We stop at whitespace,
68
+ // closing paren/bracket (markdown link form), backtick, double-quote, or
69
+ // single-quote so we don't over-consume into surrounding text.
70
+ // Note: trailing punctuation like '.,;!?' is allowed inside the path body
71
+ // because filenames legitimately contain dots; downstream callers strip
72
+ // a trailing sentence punctuation character separately when needed.
73
+ const PATH_BODY = '[^\\s)\\]`"\']+';
74
+
75
+ // Build the master pattern. Each branch is a complete absolute-path shape.
76
+ // Order matters for clarity but not correctness — alternation is greedy
77
+ // by default in `|`-separated branches; we still anchor each branch to
78
+ // its distinguishing prefix so they don't overlap.
79
+ const ABSOLUTE_PATH_PATTERN = new RegExp(
80
+ [
81
+ // file:// URLs with two or three slashes after the scheme.
82
+ 'file:\\/{2,3}' + PATH_BODY,
83
+ // Windows UNC: \\server\share\path or //server/share/path
84
+ '(?:\\\\\\\\|\\/\\/)[A-Za-z0-9._-]+(?:[\\\\\\/][A-Za-z0-9._$-]+)+',
85
+ // Windows drive letter: C:\path or C:/path
86
+ '[A-Za-z]:[\\\\\\/]' + PATH_BODY,
87
+ // Unix absolute path under a known top-level directory.
88
+ '\\/(?:' + UNIX_TOPLEVELS.join('|') + ')\\/' + PATH_BODY,
89
+ // Local Codex workspace marker — appears with or without a leading
90
+ // slash; e.g. /home/x/.codex-overleaf/projects/p1 or ./.codex-
91
+ // overleaf/projects/p1. The Unix branch above already handles the
92
+ // common /home form; this branch catches the relative form.
93
+ '(?:^|[\\s\\\\/])\\.codex-overleaf[\\\\\\/]projects[\\\\\\/]' + PATH_BODY
94
+ ].join('|'),
95
+ 'gi'
96
+ );
97
+
98
+ // Quick predicate — used by callers that only want to do the (more
99
+ // expensive) substitution pass when there's actually something to strip.
100
+ const MIGHT_CONTAIN_LOCAL_PATH_PATTERN = new RegExp(
101
+ [
102
+ 'file:\\/{2,3}',
103
+ '(?:\\\\\\\\|\\/\\/)[A-Za-z0-9._-]+[\\\\\\/]',
104
+ '[A-Za-z]:[\\\\\\/]',
105
+ '\\/(?:' + UNIX_TOPLEVELS.join('|') + ')\\/',
106
+ '\\.codex-overleaf[\\\\\\/]projects[\\\\\\/]'
107
+ ].join('|'),
108
+ 'i'
109
+ );
110
+
111
+ // Replace every absolute-local-path token in `value` with `placeholder`.
112
+ // Default placeholder is `<local-path>` to match the prior summary-side
113
+ // sanitizer. The storage-side audit redactor uses a richer `[local path]`
114
+ // (and `[local path:LINE]` when a `:line` suffix was present); that path
115
+ // continues to use its own formatter — this helper is the floor, not
116
+ // the only formatter.
117
+ function redactLocalPaths(value, placeholder) {
118
+ if (typeof value !== 'string' || !value) {
119
+ return '';
120
+ }
121
+ const token = typeof placeholder === 'string' ? placeholder : '<local-path>';
122
+ // Reset state (these are /g regexes) before reusing.
123
+ ABSOLUTE_PATH_PATTERN.lastIndex = 0;
124
+ return value.replace(ABSOLUTE_PATH_PATTERN, token);
125
+ }
126
+
127
+ function mightContainLocalPath(value) {
128
+ if (typeof value !== 'string' || !value) {
129
+ return false;
130
+ }
131
+ return MIGHT_CONTAIN_LOCAL_PATH_PATTERN.test(value);
132
+ }
133
+
134
+ return {
135
+ redactLocalPaths,
136
+ mightContainLocalPath,
137
+ // Exposed so the storage-side audit redactor (which formats per-token
138
+ // placeholders with line-suffix preservation) can iterate the same set
139
+ // without duplicating the regex source.
140
+ ABSOLUTE_PATH_PATTERN,
141
+ UNIX_TOPLEVELS
142
+ };
143
+ });
@@ -38,10 +38,16 @@
38
38
  const VALID_LOCALES = new Set(['en', 'zh']);
39
39
  const VALID_EVENT_STATUSES = new Set(['info', 'running', 'completed', 'failed', 'warning', 'blocked', 'skipped', 'pending']);
40
40
  const VALID_TITLE_SOURCES = new Set(['auto', 'manual']);
41
+ // `needs_review` is the §7 settlement state surfaced when Accept/Undo cannot
42
+ // prove a clean post-action state. It is non-terminal for UI purposes: both
43
+ // Accept and Undo remain visible AND actionable so the user can inspect
44
+ // Overleaf and reconcile. Step 3 (terminal payload cleanup) intentionally
45
+ // does NOT empty refs for `needs_review` — the user is supposed to retry.
41
46
  const VALID_TRACKED_CHANGE_STATUS = new Set([
42
47
  'pending',
43
48
  'accepted',
44
- 'rejected'
49
+ 'rejected',
50
+ 'needs_review'
45
51
  ]);
46
52
  const TERMINAL_TRACKED_CHANGE_STATUS = new Set(['accepted', 'rejected']);
47
53
  const LEGACY_DEFAULT_SESSION_TITLE = 'New task';
@@ -93,6 +99,7 @@
93
99
  /\b(?:api[_-]?key|token|password|passwd|secret)\b\s*[:=]\s*["']?[^"'\s,;]+["']?/gi
94
100
  ];
95
101
  const LineReferences = loadLineReferences();
102
+ const PathRedaction = loadPathRedaction();
96
103
 
97
104
  function normalizePanelState(input = {}, options = {}) {
98
105
  const state = {
@@ -530,6 +537,7 @@
530
537
  speedTier: typeof run.speedTier === 'string' ? run.speedTier : '',
531
538
  status: shouldStopRestoredRun ? 'failed' : normalizeRunStatus(run.status),
532
539
  statusText: shouldStopRestoredRun ? i18n.t(locale, 'restoredRunStoppedStatus') : sanitizeAssistantVisibleText(run.statusText),
540
+ runProjectId: normalizeProjectPrefKey(run.runProjectId),
533
541
  startedAt: typeof run.startedAt === 'string' ? run.startedAt : '',
534
542
  finishedAt: shouldStopRestoredRun ? new Date().toISOString() : typeof run.finishedAt === 'string' ? run.finishedAt : '',
535
543
  events: events.slice(-MAX_RUN_EVENTS),
@@ -594,8 +602,30 @@
594
602
  }
595
603
  }
596
604
 
605
+ // Welcome-panel + write-guard v1.3.8 add-on (Task 2): the run lifecycle now
606
+ // settles on three additional values when the user navigates away mid-run
607
+ // and the original run completes (or aborts) in the background. They land on
608
+ // the ORIGINAL project's session record, not the active one. The catalog is
609
+ // a `Set` so the recovery branch can defensively coerce any unknown legacy
610
+ // value to `pending` without throwing.
611
+ const VALID_RUN_STATUS = new Set([
612
+ 'pending',
613
+ 'running',
614
+ 'completed',
615
+ 'failed',
616
+ 'background_completed',
617
+ 'needs_review_after_navigation',
618
+ 'abandoned_after_navigation'
619
+ ]);
620
+
597
621
  function normalizeRunStatus(status) {
598
- return ['running', 'completed', 'failed'].includes(status) ? status : 'completed';
622
+ if (VALID_RUN_STATUS.has(status)) return status;
623
+ // Legacy persisted runs without an explicit status fall back to `completed`
624
+ // (the historical default for the recovery branch); unknown values land on
625
+ // `pending` so the UI surfaces them as fresh / actionable rather than
626
+ // pretending they finished.
627
+ if (status === undefined || status === null || status === '') return 'completed';
628
+ return 'pending';
599
629
  }
600
630
 
601
631
  function normalizeEventStatus(status) {
@@ -860,7 +890,7 @@
860
890
  function compactSessionForStorage(session, fallbackState, limits) {
861
891
  const runs = compactRunsForStorage(session.runs, limits);
862
892
  const titleSource = VALID_TITLE_SOURCES.has(session.titleSource) ? session.titleSource : 'auto';
863
- return {
893
+ const compact = {
864
894
  id: session.id,
865
895
  title: compactSessionTitleForStorage(session, titleSource, limits),
866
896
  titleSource,
@@ -876,6 +906,24 @@
876
906
  requireReviewing: session.requireReviewing !== false,
877
907
  focusFiles: normalizeFocusFiles(session.focusFiles)
878
908
  };
909
+ // Welcome-panel + write-guard v1.3.8 add-on (Task 3): preserve the four
910
+ // Recent-projects fields through compaction so they round-trip when state
911
+ // is reloaded from chrome.storage.local. The active record builder in
912
+ // `buildSessionRecord` is the canonical writer; this branch preserves an
913
+ // existing value so it survives.
914
+ if (typeof session.lastActivityAt === 'string' && session.lastActivityAt) {
915
+ compact.lastActivityAt = session.lastActivityAt;
916
+ }
917
+ if (typeof session.accountScopeId === 'string' && session.accountScopeId) {
918
+ compact.accountScopeId = session.accountScopeId;
919
+ }
920
+ if (typeof session.accountScopeUnavailable === 'boolean') {
921
+ compact.accountScopeUnavailable = session.accountScopeUnavailable;
922
+ }
923
+ if (typeof session.safeTaskSummary === 'string' && session.safeTaskSummary) {
924
+ compact.safeTaskSummary = session.safeTaskSummary;
925
+ }
926
+ return compact;
879
927
  }
880
928
 
881
929
  function compactSessionTitleForStorage(session, titleSource, limits) {
@@ -927,6 +975,7 @@
927
975
  speedTier: typeof run.speedTier === 'string' ? run.speedTier : '',
928
976
  status: normalizeRunStatus(run.status),
929
977
  statusText: summarizeTextForStorage(run.statusText, 'status text'),
978
+ runProjectId: normalizeProjectPrefKey(run.runProjectId),
930
979
  startedAt: typeof run.startedAt === 'string' ? run.startedAt : '',
931
980
  finishedAt: typeof run.finishedAt === 'string' ? run.finishedAt : '',
932
981
  events: compactRunEvents(run.events, limits),
@@ -1080,6 +1129,51 @@
1080
1129
  return Math.round(Math.min(760, Math.max(340, width)));
1081
1130
  }
1082
1131
 
1132
+ // Welcome-panel + write-guard v1.3.8 add-on (Task 3): the Recent-projects
1133
+ // dashboard variant renders one sanitized line per project. `computeSafeTaskSummary`
1134
+ // is the privacy floor for that line. It is written on every `saveState` and
1135
+ // stored on the session record (`session.safeTaskSummary`), so the dashboard
1136
+ // never has to touch the raw `task` text on render.
1137
+ //
1138
+ // The `@` regex is intentionally broad — see spec §5.6.2:
1139
+ // > Replace `@<token>` references with `@…` (regex `/@[\w./-]+/g` → `'@…'`).
1140
+ // > This intentionally over-redacts: it will also strip plain email
1141
+ // > addresses, social handles, and any `@foo` mention in the task body.
1142
+ // > That is the conservative-privacy choice and is by design — future
1143
+ // > implementers must not narrow the pattern to "only known attachment
1144
+ // > tokens" because doing so would re-expose paths, citation keys,
1145
+ // > reviewer names, etc. that users frequently combine with `@` in task
1146
+ // > text.
1147
+ function computeSafeTaskSummary(task) {
1148
+ if (typeof task !== 'string' || !task) return '';
1149
+ let s = task;
1150
+ // Strip absolute local paths via the canonical shared helper
1151
+ // (spec §5.6.2 / Fix C). The helper covers Unix (/Users, /home,
1152
+ // /private/var, /tmp, /var/folders, /Volumes, /etc, /opt, /usr, ...),
1153
+ // Windows drive letters with both `\\` and `/`, UNC `\\server\share`,
1154
+ // and `file:///` URLs. Adding a new path shape is a one-line change
1155
+ // in `pathRedaction.js` and benefits both this summary and the
1156
+ // storage-side audit redaction in lockstep.
1157
+ if (PathRedaction && PathRedaction.redactLocalPaths instanceof Function) {
1158
+ s = PathRedaction.redactLocalPaths(s, '<local-path>');
1159
+ } else {
1160
+ // Defensive fallback for hosts where the shared helper failed to
1161
+ // load. The narrower legacy patterns ship a baseline (better than
1162
+ // nothing) and the test suite asserts the broad coverage path
1163
+ // succeeds, so this branch should never run in production.
1164
+ s = s.replace(/(?:\/Users\/[^\s]+|\/home\/[^\s]+|\/private\/var\/[^\s]+|\/tmp\/[^\s]+|\/Volumes\/[^\s]+)/g, '<local-path>');
1165
+ s = s.replace(/[A-Za-z]:[\\/][^\s]+/g, '<local-path>');
1166
+ }
1167
+ // INTENTIONALLY OVER-REDACT @<token>: spec §5.6.2 — do not narrow to
1168
+ // attachment tokens, that would re-expose user info / paths / handles.
1169
+ s = s.replace(/@[\w./-]+/g, '@…');
1170
+ // Collapse whitespace runs to single space; trim.
1171
+ s = s.replace(/\s+/g, ' ').trim();
1172
+ // Hard cap 80 visible chars.
1173
+ if (s.length > 80) s = s.slice(0, 79) + '…';
1174
+ return s;
1175
+ }
1176
+
1083
1177
  function normalizeTextField(value, maxChars) {
1084
1178
  const text = sanitizeAssistantVisibleText(value);
1085
1179
  if (!Number.isFinite(maxChars) || maxChars <= 0 || text.length <= maxChars) {
@@ -1161,6 +1255,20 @@
1161
1255
  return null;
1162
1256
  }
1163
1257
 
1258
+ function loadPathRedaction() {
1259
+ if (typeof globalThis !== 'undefined' && globalThis.CodexOverleafPathRedaction) {
1260
+ return globalThis.CodexOverleafPathRedaction;
1261
+ }
1262
+ if (typeof require === 'function') {
1263
+ try {
1264
+ return require('./pathRedaction');
1265
+ } catch (_error) {
1266
+ return null;
1267
+ }
1268
+ }
1269
+ return null;
1270
+ }
1271
+
1164
1272
  function fallbackSanitizeLocalReferences(value) {
1165
1273
  return String(value || '')
1166
1274
  .replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_raw, label, target) => {
@@ -1263,6 +1371,7 @@
1263
1371
  updateActiveSession,
1264
1372
  normalizeRuns,
1265
1373
  prepareStateForStorage,
1266
- estimateJsonBytes
1374
+ estimateJsonBytes,
1375
+ computeSafeTaskSummary
1267
1376
  };
1268
1377
  });