codex-overleaf-link 1.3.7 → 1.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -21
- package/extension/src/shared/agentTranscript.js +304 -21
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/failureReasons.js +593 -0
- package/extension/src/shared/i18n.js +151 -2
- package/extension/src/shared/pathRedaction.js +117 -0
- package/extension/src/shared/sensitiveScan.js +1 -4
- package/extension/src/shared/sessionState.js +113 -4
- package/extension/src/shared/staleGuard.js +0 -2
- package/extension/src/shared/storageDb.js +217 -4
- package/native-host/src/codexPromptAssembly.js +10 -2
- package/native-host/src/codexSessionRunner.js +73 -1
- package/native-host/src/taskRunnerRuntime.js +53 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h1>Codex Overleaf Link</h1>
|
|
4
4
|
<p><strong>Empower Overleaf with Codex.</strong></p>
|
|
5
5
|
<p>
|
|
6
|
-
<img src="https://img.shields.io/badge/version-1.3.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-1.3.9-blue" alt="version">
|
|
7
7
|
<img src="https://img.shields.io/badge/platform-macOS%20%2F%20Windows%20%2F%20Linux-lightgrey" alt="platform">
|
|
8
8
|
<img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
|
|
9
9
|
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
|
|
@@ -38,14 +38,14 @@ One command installs the native host **and** sets up the extension: the script r
|
|
|
38
38
|
macOS / Linux:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
CODEX_OVERLEAF_REF=v1.3.
|
|
41
|
+
CODEX_OVERLEAF_REF=v1.3.9 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v1.3.9/install.sh)"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Windows PowerShell:
|
|
45
45
|
|
|
46
46
|
```powershell
|
|
47
|
-
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v1.3.
|
|
48
|
-
$env:CODEX_OVERLEAF_REF='v1.3.
|
|
47
|
+
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v1.3.9/install.ps1 -OutFile install.ps1
|
|
48
|
+
$env:CODEX_OVERLEAF_REF='v1.3.9'
|
|
49
49
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -56,10 +56,10 @@ Then, in the `chrome://extensions` tab the script opened: enable **Developer mod
|
|
|
56
56
|
`npm exec` installs and updates the **native host only** — it does not include the Chrome extension. Use it if you prefer a pinned npm package to a source checkout.
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npm exec --yes codex-overleaf-link@1.3.
|
|
59
|
+
npm exec --yes codex-overleaf-link@1.3.9 -- install-native
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Then add the extension yourself: download `codex-overleaf-link-extension-v1.3.
|
|
62
|
+
Then add the extension yourself: download `codex-overleaf-link-extension-v1.3.9.zip` from the [v1.3.9 GitHub Release](https://github.com/Ghqqqq/codex-overleaf-link/releases/tag/v1.3.9), unzip it to a stable folder, and in `chrome://extensions` enable **Developer mode**, click **Load unpacked**, and select that folder.
|
|
63
63
|
|
|
64
64
|
### Open Overleaf
|
|
65
65
|
|
|
@@ -86,9 +86,9 @@ npm installs, updates, uninstalls, and diagnoses the native host only. npm does
|
|
|
86
86
|
|
|
87
87
|
| Action | Command |
|
|
88
88
|
|--------|---------|
|
|
89
|
-
| Install / update | `npm exec --yes codex-overleaf-link@1.3.
|
|
90
|
-
| Diagnose | `npm exec --yes codex-overleaf-link@1.3.
|
|
91
|
-
| Uninstall | `npm exec --yes codex-overleaf-link@1.3.
|
|
89
|
+
| Install / update | `npm exec --yes codex-overleaf-link@1.3.9 -- install-native` |
|
|
90
|
+
| Diagnose | `npm exec --yes codex-overleaf-link@1.3.9 -- doctor` |
|
|
91
|
+
| Uninstall | `npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native` |
|
|
92
92
|
|
|
93
93
|
Use `--extension-id <chrome-extension-id>` only for a custom/dev unpacked extension id that differs from the official bundled id.
|
|
94
94
|
|
|
@@ -98,13 +98,13 @@ To update, re-run any of the [native host installers](#install) — they install
|
|
|
98
98
|
|
|
99
99
|
## GitHub Release Artifacts
|
|
100
100
|
|
|
101
|
-
The v1.3.
|
|
101
|
+
The v1.3.9 GitHub Release contains:
|
|
102
102
|
|
|
103
|
-
- `codex-overleaf-link-extension-v1.3.
|
|
104
|
-
- `codex-overleaf-native-host-v1.3.
|
|
105
|
-
- `codex-overleaf-link-1.3.
|
|
106
|
-
- `install.sh`: release-pinned macOS / Linux installer that defaults to `v1.3.
|
|
107
|
-
- `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v1.3.
|
|
103
|
+
- `codex-overleaf-link-extension-v1.3.9.zip`: loadable Chrome extension package for manual unpacked installation.
|
|
104
|
+
- `codex-overleaf-native-host-v1.3.9.tar.gz`: native host runtime files used by the installer and release verification.
|
|
105
|
+
- `codex-overleaf-link-1.3.9.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
|
|
106
|
+
- `install.sh`: release-pinned macOS / Linux installer that defaults to `v1.3.9` when run directly from the release artifact.
|
|
107
|
+
- `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v1.3.9` when run directly from the release artifact.
|
|
108
108
|
- `uninstall-native-host.mjs`: native host uninstaller that removes the Chrome Native Messaging manifest, bridge executable, and runtime copy.
|
|
109
109
|
- `nativeHostPlatform.js`, `manifest.js`, `runtimeInstaller.js`: helper files required by the loose uninstaller asset.
|
|
110
110
|
- `SHA256SUMS` and `release-manifest.json`: checksum and artifact metadata for release verification.
|
|
@@ -115,7 +115,7 @@ The v1.3.7 GitHub Release contains:
|
|
|
115
115
|
Remove the native host (use `--browser chromium` on Linux Chromium):
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
npm exec --yes codex-overleaf-link@1.3.
|
|
118
|
+
npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
The same command works on Windows PowerShell. If you installed from a manual checkout or source installer, you can also run `npm run uninstall:native` inside the repo, use `node ~/.codex-overleaf/source/scripts/uninstall-native-host.mjs` on macOS / Linux, or use `node $env:LOCALAPPDATA\CodexOverleaf\source\scripts\uninstall-native-host.mjs` on Windows PowerShell.
|
|
@@ -150,13 +150,13 @@ Then remove the extension from `chrome://extensions`. To delete local data: on m
|
|
|
150
150
|
Linux Chromium install or update:
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
CODEX_OVERLEAF_REF=v1.3.
|
|
153
|
+
CODEX_OVERLEAF_REF=v1.3.9 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v1.3.9/install.sh)" -- --browser chromium
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
Linux Chromium uninstall:
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
npm exec --yes codex-overleaf-link@1.3.
|
|
159
|
+
npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native --browser chromium
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
## Features
|
|
@@ -289,7 +289,7 @@ Composer attachments are turn-scoped Codex context. Limits are 8 attachments per
|
|
|
289
289
|
Re-run any [native host installer](#install), reload the extension in `chrome://extensions`, then refresh the Overleaf tab. This also fixes extension/native version mismatch and native protocol mismatch.
|
|
290
290
|
|
|
291
291
|
```bash
|
|
292
|
-
npm exec --yes codex-overleaf-link@1.3.
|
|
292
|
+
npm exec --yes codex-overleaf-link@1.3.9 -- install-native
|
|
293
293
|
```
|
|
294
294
|
|
|
295
295
|
**The Windows popup or panel shows a Bash recovery command**
|
|
@@ -338,8 +338,8 @@ Use this matrix for release-candidate signoff and compatibility reports. Record
|
|
|
338
338
|
| Browser/channel/version | Google Chrome channel and version. | Google Chrome channel and version. | Google Chrome channel and version. | Chromium channel/package and version. |
|
|
339
339
|
| Install mode | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout; native host installed with `--browser chromium`. |
|
|
340
340
|
| Extension id | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. |
|
|
341
|
-
| Installer/update command | `npm exec --yes codex-overleaf-link@1.3.
|
|
342
|
-
| Uninstall command | `npm exec --yes codex-overleaf-link@1.3.
|
|
341
|
+
| Installer/update command | `npm exec --yes codex-overleaf-link@1.3.9 -- install-native` | `npm exec --yes codex-overleaf-link@1.3.9 -- install-native` | `npm exec --yes codex-overleaf-link@1.3.9 -- install-native` | `npm exec --yes codex-overleaf-link@1.3.9 -- install-native --browser chromium` |
|
|
342
|
+
| Uninstall command | `npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native` | `npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native` | `npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native` | `npm exec --yes codex-overleaf-link@1.3.9 -- uninstall-native --browser chromium` |
|
|
343
343
|
| Manifest/registry path | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.codex.overleaf.json` | `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.codex.overleaf` -> `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime\com.codex.overleaf.json` | `~/.config/google-chrome/NativeMessagingHosts/com.codex.overleaf.json` | `~/.config/chromium/NativeMessagingHosts/com.codex.overleaf.json` |
|
|
344
344
|
| Bridge/runtime/source path | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `%LOCALAPPDATA%\CodexOverleaf\codex-overleaf-bridge.cmd`; runtime `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime`; source `%LOCALAPPDATA%\CodexOverleaf\source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. |
|
|
345
345
|
| Node/Git/Codex/TeX | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. |
|
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
(function initAgentTranscript(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory(
|
|
3
|
+
module.exports = factory({
|
|
4
|
+
getFailureReasons: function getFailureReasonsCjs() { return require('./failureReasons.js'); },
|
|
5
|
+
getI18n: function getI18nCjs() { return require('./i18n.js'); }
|
|
6
|
+
});
|
|
4
7
|
} else {
|
|
5
|
-
|
|
8
|
+
// Browser script-tag load order is determined by manifest.json content_scripts;
|
|
9
|
+
// resolve dependencies lazily so this module can be listed before its deps.
|
|
10
|
+
root.CodexOverleafAgentTranscript = factory({
|
|
11
|
+
getFailureReasons: function getFailureReasonsWindow() { return root.CodexOverleafFailureReasons || null; },
|
|
12
|
+
getI18n: function getI18nWindow() { return root.CodexOverleafI18n || null; }
|
|
13
|
+
});
|
|
6
14
|
}
|
|
7
|
-
})(typeof globalThis !== 'undefined' ? globalThis : window, function agentTranscriptFactory() {
|
|
15
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function agentTranscriptFactory(deps) {
|
|
8
16
|
'use strict';
|
|
9
17
|
|
|
18
|
+
const getFailureReasonsModule = (deps && deps.getFailureReasons) instanceof Function
|
|
19
|
+
? deps.getFailureReasons
|
|
20
|
+
: function noFailureReasons() { return null; };
|
|
21
|
+
const getI18nModule = (deps && deps.getI18n) instanceof Function
|
|
22
|
+
? deps.getI18n
|
|
23
|
+
: function noI18n() { return null; };
|
|
24
|
+
|
|
10
25
|
const TECHNICAL_EVENT_PATTERNS = [
|
|
11
26
|
/^agent\.command\./,
|
|
12
27
|
/^native\.task\./,
|
|
@@ -487,72 +502,117 @@
|
|
|
487
502
|
function translateRawError(message, context = {}) {
|
|
488
503
|
const locale = normalizeLocale(context);
|
|
489
504
|
const text = String(message || '');
|
|
505
|
+
// Each branch returns the user-visible {conclusion, nextStep} pair and,
|
|
506
|
+
// when the error maps to a structured FailureReason code in the v1.3.8
|
|
507
|
+
// catalog, the `failureCode`. Callers that emit a content-side failure
|
|
508
|
+
// event can attach the catalog code so the run record carries structured
|
|
509
|
+
// failure data alongside the human-readable text. The string text is kept
|
|
510
|
+
// verbatim because it has mode-aware nuance (e.g. "No files were written"
|
|
511
|
+
// vs "This run did not start") the catalog's fallback strings don't capture.
|
|
490
512
|
if (/Mode must be "confirm" or "auto"/i.test(text)) {
|
|
491
513
|
return {
|
|
492
514
|
conclusion: textFor(locale, '这轮没有写入:当前是“只问不改”,但这个任务需要写入权限。', 'No files were written: this task needs write access, but the current mode is Ask.'),
|
|
493
|
-
nextStep: textFor(locale, '请切换到“建议修改”或“自动写入”后重新运行。', 'Switch to Suggest or Auto and run the task again.')
|
|
515
|
+
nextStep: textFor(locale, '请切换到“建议修改”或“自动写入”后重新运行。', 'Switch to Suggest or Auto and run the task again.'),
|
|
516
|
+
failureCode: null // no direct catalog match — preflight / governance
|
|
494
517
|
};
|
|
495
518
|
}
|
|
496
519
|
if (/Agent returned invalid JSON/i.test(text)) {
|
|
497
520
|
return {
|
|
498
521
|
conclusion: textFor(locale, '这轮没有写入:Codex 已结束,但本地桥接器没有读到可用结果。', 'No files were written: Codex finished, but the local bridge did not receive a usable result.'),
|
|
499
|
-
nextStep: textFor(locale, '请重新运行一次;如果再次失败,请打开技术详情查看本地 Codex 输出。', 'Run it again. If it fails again, open Technical Details to inspect local Codex output.')
|
|
522
|
+
nextStep: textFor(locale, '请重新运行一次;如果再次失败,请打开技术详情查看本地 Codex 输出。', 'Run it again. If it fails again, open Technical Details to inspect local Codex output.'),
|
|
523
|
+
failureCode: 'codex_result_parse_failed'
|
|
500
524
|
};
|
|
501
525
|
}
|
|
502
526
|
if (/Could not parse Codex output/i.test(text)) {
|
|
503
527
|
return {
|
|
504
528
|
conclusion: textFor(locale, '这轮没有写入:Codex 返回的结果格式不完整。', 'No files were written: Codex returned an incomplete result format.'),
|
|
505
|
-
nextStep: textFor(locale, '请重新运行一次;如果再次失败,请打开技术详情查看原始输出。', 'Run it again. If it fails again, open Technical Details to inspect the raw output.')
|
|
529
|
+
nextStep: textFor(locale, '请重新运行一次;如果再次失败,请打开技术详情查看原始输出。', 'Run it again. If it fails again, open Technical Details to inspect the raw output.'),
|
|
530
|
+
failureCode: 'codex_result_parse_failed'
|
|
506
531
|
};
|
|
507
532
|
}
|
|
508
533
|
if (/timed out/i.test(text)) {
|
|
509
534
|
return {
|
|
510
535
|
conclusion: textFor(locale, '这轮没有写入:本地 Codex 长时间没有完成。', 'No files were written: local Codex took too long to finish.'),
|
|
511
|
-
nextStep: textFor(locale, '请检查本机 Codex 是否仍在运行;如果没有进展,可以中断后缩小 @context 再重试。', 'Check whether local Codex is still running. If there is no progress, cancel and retry with smaller @context.')
|
|
536
|
+
nextStep: textFor(locale, '请检查本机 Codex 是否仍在运行;如果没有进展,可以中断后缩小 @context 再重试。', 'Check whether local Codex is still running. If there is no progress, cancel and retry with smaller @context.'),
|
|
537
|
+
failureCode: 'codex_timeout'
|
|
512
538
|
};
|
|
513
539
|
}
|
|
514
540
|
if (/output limit exceeded/i.test(text)) {
|
|
515
541
|
return {
|
|
516
542
|
conclusion: textFor(locale, '这轮没有写入:本地 Codex 输出过长,桥接器停止读取。', 'No files were written: local Codex output was too large, so the bridge stopped reading.'),
|
|
517
|
-
nextStep: textFor(locale, '请缩小 @context 后重试,或在技术详情中查看输出限制。', 'Retry with smaller @context, or open Technical Details to inspect the output limit.')
|
|
543
|
+
nextStep: textFor(locale, '请缩小 @context 后重试,或在技术详情中查看输出限制。', 'Retry with smaller @context, or open Technical Details to inspect the output limit.'),
|
|
544
|
+
failureCode: 'codex_output_limit'
|
|
518
545
|
};
|
|
519
546
|
}
|
|
520
547
|
if (/codex_not_found|Codex CLI was not found|ENOENT/i.test(text)) {
|
|
521
548
|
return {
|
|
522
549
|
conclusion: textFor(locale, '这轮没有启动:本机没有找到 Codex CLI。', 'This run did not start: Codex CLI was not found locally.'),
|
|
523
|
-
nextStep: textFor(locale, '请确认终端里可以运行 `codex`,然后重新安装 native host 或刷新扩展后重试。', 'Confirm `codex` works in Terminal, then reinstall the native host or reload the extension.')
|
|
550
|
+
nextStep: textFor(locale, '请确认终端里可以运行 `codex`,然后重新安装 native host 或刷新扩展后重试。', 'Confirm `codex` works in Terminal, then reinstall the native host or reload the extension.'),
|
|
551
|
+
failureCode: 'codex_not_found'
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
if (/project_locked|currently in use by codex\.run/i.test(text)) {
|
|
555
|
+
return {
|
|
556
|
+
conclusion: textFor(locale,
|
|
557
|
+
'这轮没有启动:同一个 Overleaf 项目里已经有一轮 Codex 任务正在运行。',
|
|
558
|
+
'This run did not start: another Codex task is already running for this Overleaf project.'),
|
|
559
|
+
nextStep: textFor(locale,
|
|
560
|
+
'请等待当前任务完成,或先取消当前任务后再重试。',
|
|
561
|
+
'Wait for the current task to finish, or cancel it before retrying.'),
|
|
562
|
+
failureCode: 'codex_project_locked'
|
|
524
563
|
};
|
|
525
564
|
}
|
|
526
565
|
if (/unsupported[_ ]parameter/i.test(text) && /reasoning\.summary|summary/i.test(text)) {
|
|
527
566
|
return {
|
|
528
567
|
conclusion: textFor(locale, '这轮没有继续:当前 Codex 模型不支持插件请求的推理摘要参数。', 'This run did not continue: the selected Codex model does not support the requested reasoning summary parameter.'),
|
|
529
|
-
nextStep: textFor(locale, '请刷新扩展并重新运行;插件会按模型能力自动去掉不兼容参数。', 'Reload the extension and run again; the plugin will omit incompatible parameters based on model capability.')
|
|
568
|
+
nextStep: textFor(locale, '请刷新扩展并重新运行;插件会按模型能力自动去掉不兼容参数。', 'Reload the extension and run again; the plugin will omit incompatible parameters based on model capability.'),
|
|
569
|
+
failureCode: 'native_protocol_incompatible'
|
|
530
570
|
};
|
|
531
571
|
}
|
|
532
572
|
if (/quota|kQuotaBytes|QUOTA_BYTES/i.test(text)) {
|
|
533
573
|
return {
|
|
534
574
|
conclusion: textFor(locale, 'Codex 结果已经生成,但本地会话记录超出 Chrome 存储配额。', 'Codex produced a result, but local session history exceeded Chrome storage quota.'),
|
|
535
|
-
nextStep: textFor(locale, '请删除一些旧 session,或刷新扩展后重试;这不是论文分析本身失败。', 'Delete older sessions or reload the extension and retry. The paper analysis itself did not fail.')
|
|
575
|
+
nextStep: textFor(locale, '请删除一些旧 session,或刷新扩展后重试;这不是论文分析本身失败。', 'Delete older sessions or reload the extension and retry. The paper analysis itself did not fail.'),
|
|
576
|
+
failureCode: 'storage_quota_exceeded'
|
|
536
577
|
};
|
|
537
578
|
}
|
|
538
579
|
if (/checkpoint/i.test(text)) {
|
|
539
580
|
return {
|
|
540
581
|
conclusion: textFor(locale, '这轮没有自动写入:Codex 没有拿到可恢复版本。', 'This run did not auto-write: Codex did not get a recoverable version.'),
|
|
541
|
-
nextStep: textFor(locale, '请切换到“建议修改”,或确认 Overleaf Reviewing 已开启后再用“自动写入”。', 'Switch to Suggest, or confirm Overleaf Reviewing is enabled before using Auto.')
|
|
582
|
+
nextStep: textFor(locale, '请切换到“建议修改”,或确认 Overleaf Reviewing 已开启后再用“自动写入”。', 'Switch to Suggest, or confirm Overleaf Reviewing is enabled before using Auto.'),
|
|
583
|
+
failureCode: null // no current catalog code; preflight reviewing issue
|
|
542
584
|
};
|
|
543
585
|
}
|
|
544
586
|
if (/changed while Codex was working|任务执行期间被你或协作者改过/i.test(text)) {
|
|
545
587
|
return {
|
|
546
588
|
conclusion: textFor(locale, '这轮没有覆盖文件:任务执行期间文件被你或协作者改过。', 'No file was overwritten: a file changed while Codex was working.'),
|
|
547
|
-
nextStep: textFor(locale, '请先确认 Overleaf 当前内容,再重新运行任务。', 'Review the current Overleaf content, then run the task again.')
|
|
589
|
+
nextStep: textFor(locale, '请先确认 Overleaf 当前内容,再重新运行任务。', 'Review the current Overleaf content, then run the task again.'),
|
|
590
|
+
failureCode: 'stale_source_changed'
|
|
548
591
|
};
|
|
549
592
|
}
|
|
550
593
|
|
|
594
|
+
// When the caller signals Codex DID produce an answer (assistantMessage
|
|
595
|
+
// arrived on the stream) but an unrelated exception still escaped to the
|
|
596
|
+
// outer catch — the original 'no usable result' copy is wrong-by-design
|
|
597
|
+
// because the user can see Codex's answer in chat. Surface the real
|
|
598
|
+
// shape: Codex returned, post-processing failed, the answer is preserved.
|
|
599
|
+
if (context.codexReturned) {
|
|
600
|
+
return {
|
|
601
|
+
conclusion: textFor(locale,
|
|
602
|
+
'Codex 已经返回了结果,但本地处理这一轮时出错了。',
|
|
603
|
+
'Codex returned a result, but local post-processing of this run failed.'),
|
|
604
|
+
nextStep: textFor(locale,
|
|
605
|
+
'请打开技术详情查看错误。Codex 的回答仍保留在会话中。',
|
|
606
|
+
'Open Technical Details to inspect the error. Codex\'s answer is preserved in the conversation.'),
|
|
607
|
+
failureCode: null // post-processing path; the real error already attached its own structured failure
|
|
608
|
+
};
|
|
609
|
+
}
|
|
551
610
|
return {
|
|
552
611
|
conclusion: context.mode === 'ask'
|
|
553
612
|
? textFor(locale, '这轮只问不改没有完成:本地 Codex 没有正常完成,因此没有生成最终说明。', 'This Ask run did not complete: local Codex did not finish normally, so no final answer was generated.')
|
|
554
613
|
: textFor(locale, '这轮任务失败:本地 Codex 没有返回可用结果,未确认任何写入。', 'This task failed: local Codex returned no usable result, so no writes were confirmed.'),
|
|
555
|
-
nextStep: textFor(locale, '请查看技术详情,处理本地 Codex 错误后重试。', 'Open Technical Details, resolve the local Codex error, and retry.')
|
|
614
|
+
nextStep: textFor(locale, '请查看技术详情,处理本地 Codex 错误后重试。', 'Open Technical Details, resolve the local Codex error, and retry.'),
|
|
615
|
+
failureCode: 'codex_no_usable_result'
|
|
556
616
|
};
|
|
557
617
|
}
|
|
558
618
|
|
|
@@ -582,10 +642,88 @@
|
|
|
582
642
|
return {
|
|
583
643
|
title: textFor(locale, '本轮完成报告', 'Task report'),
|
|
584
644
|
status: failed ? 'failed' : 'completed',
|
|
585
|
-
text: formatHumanReport(report, locale)
|
|
645
|
+
text: formatHumanReport(report, locale),
|
|
646
|
+
structured: buildStructuredHumanReport(report, locale)
|
|
586
647
|
};
|
|
587
648
|
}
|
|
588
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Thin wrapper that renders just the fallback final-report text for a
|
|
652
|
+
* writeback `apply` payload. Used by tests and by callers that only need the
|
|
653
|
+
* skipped-block formatting without the full completion-report envelope.
|
|
654
|
+
* @param {{ apply: { applied?: any[], skipped?: any[] }, locale?: string, includeWriteResult?: boolean, status?: string }} input
|
|
655
|
+
* @returns {string}
|
|
656
|
+
*/
|
|
657
|
+
function formatFallbackFinalReport(input = {}) {
|
|
658
|
+
const locale = normalizeLocale(input);
|
|
659
|
+
const apply = (input && input.apply) || {};
|
|
660
|
+
const report = buildHumanCompletionReport({
|
|
661
|
+
locale,
|
|
662
|
+
status: input.status || 'failed',
|
|
663
|
+
operations: [],
|
|
664
|
+
applyResults: [apply],
|
|
665
|
+
includeWriteResult: input.includeWriteResult !== false,
|
|
666
|
+
undoCount: 0
|
|
667
|
+
});
|
|
668
|
+
return report.text;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Splits the human report into a conclusion (the human-language outcome),
|
|
673
|
+
* a body of list-style content sections (checked / findings / planned /
|
|
674
|
+
* changes / skipped), and a meta block of run-metadata key/value rows
|
|
675
|
+
* (unchanged reason, write result, undo, next). The renderer uses this to
|
|
676
|
+
* visually demote the meta block — it is system info about the run, not
|
|
677
|
+
* part of Codex's answer. `formatHumanReport` retains the legacy flat-text
|
|
678
|
+
* shape for transcripts, storage fallback, and existing assertions.
|
|
679
|
+
*/
|
|
680
|
+
function buildStructuredHumanReport(report = {}, locale = 'zh') {
|
|
681
|
+
const conclusion = cleanVisibleMarkdownText(report.conclusion || '');
|
|
682
|
+
|
|
683
|
+
const bodySections = [];
|
|
684
|
+
addListSection(bodySections, textFor(locale, '检查范围', 'Checked'), report.checked, locale);
|
|
685
|
+
addListSection(bodySections, textFor(locale, '发现', 'Findings'), report.findings, locale);
|
|
686
|
+
addListSection(bodySections, textFor(locale, '计划修改', 'Planned changes'), report.plannedChanges, locale);
|
|
687
|
+
addListSection(bodySections, textFor(locale, '修改', 'Changes'), report.appliedChanges, locale);
|
|
688
|
+
addStructuredListSection(bodySections, textFor(locale, '跳过原因', 'Skipped'), report.skippedChanges, locale);
|
|
689
|
+
|
|
690
|
+
const meta = [];
|
|
691
|
+
const unchangedReason = localizeVisibleReason(report.unchangedReason || '', locale);
|
|
692
|
+
if (unchangedReason) {
|
|
693
|
+
meta.push({
|
|
694
|
+
key: 'unchangedReason',
|
|
695
|
+
label: textFor(locale, '未修改原因', 'Why nothing changed'),
|
|
696
|
+
value: unchangedReason
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const writeResult = cleanVisibleText(report.writeResult || '');
|
|
700
|
+
if (writeResult) {
|
|
701
|
+
meta.push({
|
|
702
|
+
key: 'writeResult',
|
|
703
|
+
label: textFor(locale, '写入结果', 'Write result'),
|
|
704
|
+
value: writeResult
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
const undo = cleanVisibleText(report.undo || '');
|
|
708
|
+
if (undo) {
|
|
709
|
+
meta.push({
|
|
710
|
+
key: 'undo',
|
|
711
|
+
label: textFor(locale, '可撤销', 'Undo'),
|
|
712
|
+
value: undo
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
const nextStep = cleanVisibleText(report.nextStep || '');
|
|
716
|
+
if (nextStep) {
|
|
717
|
+
meta.push({
|
|
718
|
+
key: 'nextStep',
|
|
719
|
+
label: textFor(locale, '下一步', 'Next'),
|
|
720
|
+
value: nextStep
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return { conclusion, body: bodySections.join('\n\n'), meta };
|
|
725
|
+
}
|
|
726
|
+
|
|
589
727
|
function formatHumanReport(report = {}, locale = 'zh') {
|
|
590
728
|
const sections = [];
|
|
591
729
|
const conclusion = cleanVisibleMarkdownText(report.conclusion || '');
|
|
@@ -604,7 +742,7 @@
|
|
|
604
742
|
if (writeResult) {
|
|
605
743
|
sections.push(textFor(locale, `写入结果:${writeResult}`, `Write result: ${writeResult}`));
|
|
606
744
|
}
|
|
607
|
-
|
|
745
|
+
addStructuredListSection(sections, textFor(locale, '跳过原因', 'Skipped'), report.skippedChanges, locale);
|
|
608
746
|
const undo = cleanVisibleText(report.undo || '');
|
|
609
747
|
if (undo) {
|
|
610
748
|
sections.push(textFor(locale, `可撤销:${undo}`, `Undo: ${undo}`));
|
|
@@ -640,10 +778,36 @@
|
|
|
640
778
|
? formatWriteResult(counts.appliedCount, counts.skippedCount, locale)
|
|
641
779
|
: ''),
|
|
642
780
|
undo: input.undo,
|
|
643
|
-
nextStep: input.nextStep
|
|
781
|
+
nextStep: input.nextStep
|
|
782
|
+
|| counts.translatedError?.nextStep
|
|
783
|
+
|| formatPrimaryFailureNextStep(input.applyResults, locale)
|
|
784
|
+
|| formatFallbackNextStep(input, counts.skippedCount, affectedFiles, locale)
|
|
644
785
|
};
|
|
645
786
|
}
|
|
646
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Derive the run-level next-step from the highest-priority skipped failure
|
|
790
|
+
* via `selectPrimaryFailure`. Returns '' when no usable primary failure
|
|
791
|
+
* exists, so callers can fall through to the generic copy.
|
|
792
|
+
*/
|
|
793
|
+
function formatPrimaryFailureNextStep(applyResults, locale) {
|
|
794
|
+
const failureReasons = getFailureReasonsModule();
|
|
795
|
+
if (!failureReasons || !failureReasons.selectPrimaryFailure || !failureReasons.normalizeFailureReason) {
|
|
796
|
+
return '';
|
|
797
|
+
}
|
|
798
|
+
const skipped = collectSkippedOperations(applyResults);
|
|
799
|
+
if (!skipped.length) {
|
|
800
|
+
return '';
|
|
801
|
+
}
|
|
802
|
+
const failures = skipped.map(item =>
|
|
803
|
+
failureReasons.normalizeFailureReason(item.result, item.operation || {}, { locale })
|
|
804
|
+
).filter(Boolean);
|
|
805
|
+
const primary = failureReasons.selectPrimaryFailure(failures);
|
|
806
|
+
if (!primary) return '';
|
|
807
|
+
const localized = failureReasons.localizeFailureReason(primary, locale, failureI18nLookup(locale));
|
|
808
|
+
return localized.nextAction || primary.nextAction || '';
|
|
809
|
+
}
|
|
810
|
+
|
|
647
811
|
function formatWriteResult(appliedCount, skippedCount, locale = 'zh') {
|
|
648
812
|
return textFor(
|
|
649
813
|
locale,
|
|
@@ -919,6 +1083,27 @@
|
|
|
919
1083
|
sections.push(`${label}${locale === 'en' ? ':' : ':'}\n${items.map(item => `- ${item}`).join('\n')}`);
|
|
920
1084
|
}
|
|
921
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* Same as `addListSection` but preserves item-internal newlines so that
|
|
1088
|
+
* structured FailureReason blocks (Reason/Stage/Code/Next) survive into the
|
|
1089
|
+
* rendered report. Per-item first line gets the `- ` bullet; subsequent
|
|
1090
|
+
* lines pass through verbatim (the formatter already indents them).
|
|
1091
|
+
*/
|
|
1092
|
+
function addStructuredListSection(sections, label, values, locale = 'zh') {
|
|
1093
|
+
const items = normalizeMultilineStringList(values);
|
|
1094
|
+
if (!items.length) {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
sections.push(`${label}${locale === 'en' ? ':' : ':'}\n${items.map(item => `- ${item}`).join('\n')}`);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function normalizeMultilineStringList(value) {
|
|
1101
|
+
const values = Array.isArray(value) ? value : (value ? [value] : []);
|
|
1102
|
+
return values
|
|
1103
|
+
.map(item => cleanVisibleMarkdownText(item))
|
|
1104
|
+
.filter(Boolean);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
922
1107
|
function normalizeStringList(value) {
|
|
923
1108
|
const values = Array.isArray(value) ? value : (value ? [value] : []);
|
|
924
1109
|
return values
|
|
@@ -1034,12 +1219,107 @@
|
|
|
1034
1219
|
const operation = item?.operation || {};
|
|
1035
1220
|
const labels = OPERATION_LABELS[locale] || OPERATION_LABELS.zh;
|
|
1036
1221
|
const label = labels[operation.type] || operation.type || textFor(locale, '处理', 'process');
|
|
1037
|
-
const filePath = operation.path || operation.from || operation.to || textFor(locale, '未知文件', 'unknown file');
|
|
1038
1222
|
const result = item?.result || {};
|
|
1039
|
-
const
|
|
1223
|
+
const filePath = operation.path || operation.from || operation.to || (
|
|
1224
|
+
result?.failure?.stage === 'write' || result?.code === 'editor_project_id_unavailable' || result?.code === 'aborted_project_changed'
|
|
1225
|
+
? textFor(locale, '写入流程', 'writeback process')
|
|
1226
|
+
: textFor(locale, '未知文件', 'unknown file')
|
|
1227
|
+
);
|
|
1228
|
+
const headerLine = locale === 'en'
|
|
1229
|
+
? `${filePath}: ${label} was not written`
|
|
1230
|
+
: `${filePath}:${label}没有写入`;
|
|
1231
|
+
const block = formatFailureBlockForResult(result, operation, locale);
|
|
1232
|
+
if (block) {
|
|
1233
|
+
return `${headerLine}\n${block}`;
|
|
1234
|
+
}
|
|
1235
|
+
// Last-resort fallback: legacy parenthesized form, only when neither
|
|
1236
|
+
// a structured failure nor the normalizer surfaces a usable record.
|
|
1237
|
+
const legacyReason = formatSkippedReason(result, operation, locale);
|
|
1040
1238
|
return locale === 'en'
|
|
1041
|
-
? `${
|
|
1042
|
-
: `${
|
|
1239
|
+
? `${headerLine} (${legacyReason})`
|
|
1240
|
+
: `${headerLine}(${legacyReason})`;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Render a `FailureReason` (already localized) as a four-line indented block:
|
|
1245
|
+
* <indent>Reason: <userMessage>
|
|
1246
|
+
* <indent>Stage: <stage>
|
|
1247
|
+
* <indent>Code: <code>
|
|
1248
|
+
* <indent>Next: <nextAction> (only when present)
|
|
1249
|
+
* The `Next` line is omitted when the failure has no `nextAction`.
|
|
1250
|
+
* @param {{ userMessage: string, stage: string, code: string, nextAction?: string }} failure
|
|
1251
|
+
* @param {string} locale - 'en' or 'zh'.
|
|
1252
|
+
* @param {string} [indent=' '] - String prefix applied to every line.
|
|
1253
|
+
* @returns {string}
|
|
1254
|
+
*/
|
|
1255
|
+
function formatFailureBlock(failure, locale, indent) {
|
|
1256
|
+
const pad = indent === undefined ? ' ' : indent;
|
|
1257
|
+
const i18nModule = getI18nModule();
|
|
1258
|
+
const headingKey = label => (
|
|
1259
|
+
i18nModule && i18nModule.t ? i18nModule.t(locale, label) : null
|
|
1260
|
+
) || defaultSectionHeading(label, locale);
|
|
1261
|
+
const sectionHeading = headingKey('failureReason_section_heading');
|
|
1262
|
+
const sectionStage = headingKey('failureReason_section_stage');
|
|
1263
|
+
const sectionCode = headingKey('failureReason_section_code');
|
|
1264
|
+
const sectionNext = headingKey('failureReason_section_next');
|
|
1265
|
+
const lines = [];
|
|
1266
|
+
lines.push(`${pad}${sectionHeading}: ${failure.userMessage || ''}`);
|
|
1267
|
+
lines.push(`${pad}${sectionStage}: ${failure.stage || ''}`);
|
|
1268
|
+
lines.push(`${pad}${sectionCode}: ${failure.code || ''}`);
|
|
1269
|
+
if (failure.nextAction) {
|
|
1270
|
+
lines.push(`${pad}${sectionNext}: ${failure.nextAction}`);
|
|
1271
|
+
}
|
|
1272
|
+
return lines.join('\n');
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function defaultSectionHeading(key, locale) {
|
|
1276
|
+
const en = {
|
|
1277
|
+
failureReason_section_heading: 'Reason',
|
|
1278
|
+
failureReason_section_stage: 'Stage',
|
|
1279
|
+
failureReason_section_code: 'Code',
|
|
1280
|
+
failureReason_section_next: 'Next'
|
|
1281
|
+
};
|
|
1282
|
+
const zh = {
|
|
1283
|
+
failureReason_section_heading: '原因',
|
|
1284
|
+
failureReason_section_stage: '阶段',
|
|
1285
|
+
failureReason_section_code: '代码',
|
|
1286
|
+
failureReason_section_next: '下一步'
|
|
1287
|
+
};
|
|
1288
|
+
const dict = locale === 'zh' ? zh : en;
|
|
1289
|
+
return dict[key] || key;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Build the localized FailureReason block for a skipped writeback item.
|
|
1294
|
+
* Returns '' when the failureReasons module is unavailable, which makes
|
|
1295
|
+
* callers fall back to the legacy parenthesized form.
|
|
1296
|
+
*/
|
|
1297
|
+
function formatFailureBlockForResult(result, operation, locale) {
|
|
1298
|
+
const failureReasons = getFailureReasonsModule();
|
|
1299
|
+
if (!failureReasons || !failureReasons.normalizeFailureReason) {
|
|
1300
|
+
return '';
|
|
1301
|
+
}
|
|
1302
|
+
const failure = failureReasons.normalizeFailureReason(result, operation || {}, { locale });
|
|
1303
|
+
if (!failure || !failure.code) {
|
|
1304
|
+
return '';
|
|
1305
|
+
}
|
|
1306
|
+
const localized = failureReasons.localizeFailureReason(failure, locale, failureI18nLookup(locale));
|
|
1307
|
+
const rendered = Object.assign({}, failure, {
|
|
1308
|
+
userMessage: localized.userMessage || failure.userMessage,
|
|
1309
|
+
nextAction: localized.nextAction || failure.nextAction
|
|
1310
|
+
});
|
|
1311
|
+
return formatFailureBlock(rendered, locale, ' ');
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function failureI18nLookup(locale) {
|
|
1315
|
+
return function lookup(key) {
|
|
1316
|
+
const i18nModule = getI18nModule();
|
|
1317
|
+
if (!i18nModule || !i18nModule.t) return undefined;
|
|
1318
|
+
const localized = i18nModule.t(locale, key);
|
|
1319
|
+
// i18n.t returns the key itself on miss; treat that as miss so the
|
|
1320
|
+
// catalog fallback wins for codes without bespoke localization.
|
|
1321
|
+
return localized && localized !== key ? localized : undefined;
|
|
1322
|
+
};
|
|
1043
1323
|
}
|
|
1044
1324
|
|
|
1045
1325
|
function formatSkippedReason(result = {}, operation = {}, locale = 'zh') {
|
|
@@ -1204,6 +1484,9 @@
|
|
|
1204
1484
|
|
|
1205
1485
|
return {
|
|
1206
1486
|
buildHumanCompletionReport,
|
|
1487
|
+
buildStructuredHumanReport,
|
|
1488
|
+
formatFallbackFinalReport,
|
|
1489
|
+
formatFailureBlock,
|
|
1207
1490
|
formatHumanReport,
|
|
1208
1491
|
mapAgentEventToActivity,
|
|
1209
1492
|
translateRawError
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '1.3.
|
|
15
|
+
const BUILD_TARGET_VERSION = '1.3.9';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|