codex-overleaf-link 2.5.1 → 2.6.0
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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +78 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +11 -2
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +920 -276
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsPanel.js +18 -0
- package/extension/src/content/generated/content.bundle.js +4496 -924
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +2 -2
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +42 -5
- package/extension/src/content/modelPicker.js +1 -0
- package/extension/src/content/nativeCompatibilityController.js +29 -10
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +2 -2
- package/extension/src/content/panelMaintenance.js +6 -2
- package/extension/src/content/panelRenderer.js +51 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +2 -1
- package/extension/src/content/recentProjects.js +4 -3
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +9 -5
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +22 -20
- package/extension/src/content/runReviewController.js +94 -35
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +92 -23
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +4 -3
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +35 -22
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/writebackRouter.js +24 -77
- package/extension/src/pageBridge.js +107 -66
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +350 -17
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +6 -1
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +42 -3
- package/extension/src/shared/i18n.js +299 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +72 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +7 -1
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1106 -490
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +126 -17
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/taskRunnerRuntime.js +18 -6
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +120 -15
- package/package.json +5 -8
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
3
|
module.exports = factory({
|
|
4
4
|
getFailureReasons: function getFailureReasonsCjs() { return require('./failureReasons.js'); },
|
|
5
|
-
getI18n: function getI18nCjs() { return require('./i18n.js'); }
|
|
5
|
+
getI18n: function getI18nCjs() { return require('./i18n.js'); },
|
|
6
|
+
RunActivityModel: require('./runActivityModel.js')
|
|
6
7
|
});
|
|
7
8
|
} else {
|
|
8
9
|
root.CodexOverleafModuleRegistry.define('AgentTranscript', [
|
|
9
10
|
'FailureReasons',
|
|
10
|
-
'I18n'
|
|
11
|
-
|
|
11
|
+
'I18n',
|
|
12
|
+
'RunActivityModel'
|
|
13
|
+
], function createAgentTranscript(FailureReasons, I18n, RunActivityModel) {
|
|
12
14
|
return factory({
|
|
13
15
|
getFailureReasons: function getFailureReasonsWindow() { return FailureReasons; },
|
|
14
|
-
getI18n: function getI18nWindow() { return I18n; }
|
|
16
|
+
getI18n: function getI18nWindow() { return I18n; },
|
|
17
|
+
RunActivityModel
|
|
15
18
|
});
|
|
16
19
|
});
|
|
17
20
|
}
|
|
@@ -25,6 +28,221 @@
|
|
|
25
28
|
? deps.getI18n
|
|
26
29
|
: function noI18n() { return null; };
|
|
27
30
|
|
|
31
|
+
// Only explicit product producers may attach these semantic identities.
|
|
32
|
+
const PRODUCT_CONCLUSIONS = {
|
|
33
|
+
"context_compacted": ["Conversation context compacted.", "会话上下文已压缩。"],
|
|
34
|
+
"compact_busy": ["Wait for the current task to finish before compacting context.", "请等待当前任务结束后再压缩上下文。"],
|
|
35
|
+
"compact_unsupported": ["This local Codex connection does not support manual compaction. Update Codex and reload the extension.", "当前本地 Codex 连接不支持手动压缩,请更新 Codex 并重新加载扩展。"],
|
|
36
|
+
"compact_failed": ["Context compaction did not complete. Check Technical Details before retrying.", "上下文压缩未完成,请查看技术详情后再重试。"],
|
|
37
|
+
"full_project_unread": [
|
|
38
|
+
"This run did not continue: the full Overleaf project was not read.",
|
|
39
|
+
"这轮没有继续:没有读到完整的 Overleaf 项目内容。"
|
|
40
|
+
],
|
|
41
|
+
"empty_result": [
|
|
42
|
+
"Codex completed, but it produced no assistant response and no local file changes.",
|
|
43
|
+
"Codex 已完成,但没有产生助手回复,也没有产生本地文件改动。"
|
|
44
|
+
],
|
|
45
|
+
"skill_installer_completed": [
|
|
46
|
+
"Skill installer completed.",
|
|
47
|
+
"Skill installer 已完成。"
|
|
48
|
+
],
|
|
49
|
+
"launch_not_started": [
|
|
50
|
+
"This task did not start: Codex did not run and no files were written.",
|
|
51
|
+
"这轮任务没有开始:Codex 没有运行,也没有写入文件。"
|
|
52
|
+
],
|
|
53
|
+
"cancelled": [
|
|
54
|
+
"This run was cancelled. It did not continue syncing or writing to Overleaf.",
|
|
55
|
+
"这轮已中断,没有继续同步或写入 Overleaf。"
|
|
56
|
+
],
|
|
57
|
+
"reload_interrupted": [
|
|
58
|
+
"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.",
|
|
59
|
+
"插件重新加载时发现这轮任务还标记为处理中。为了避免继续显示过期状态,已把它标记为中断;可以重新运行任务。"
|
|
60
|
+
],
|
|
61
|
+
"ask_inspected": [
|
|
62
|
+
"This run only inspected and explained; no files were written.",
|
|
63
|
+
"这轮只做了检查和说明,没有写入文件。"
|
|
64
|
+
],
|
|
65
|
+
"delete_handled": [
|
|
66
|
+
"Changes were written, and delete items were handled after your confirmation.",
|
|
67
|
+
"已写入修改,并按你的确认处理删除项。"
|
|
68
|
+
],
|
|
69
|
+
"delete_rejected": [
|
|
70
|
+
"Non-delete changes were kept; delete items were not written.",
|
|
71
|
+
"已保留非删除修改;删除项没有写入。"
|
|
72
|
+
],
|
|
73
|
+
"run_complete": [
|
|
74
|
+
"This run is complete.",
|
|
75
|
+
"这轮任务已完成。"
|
|
76
|
+
],
|
|
77
|
+
"withheld_changes": [
|
|
78
|
+
"Some generated changes were blocked.",
|
|
79
|
+
"部分生成的改动已被阻止。"
|
|
80
|
+
],
|
|
81
|
+
"ask_ignored_changes": [
|
|
82
|
+
"Codex finished in Ask mode. Any local file changes were ignored and were not synced to Overleaf.",
|
|
83
|
+
"Codex 已在 Ask 模式完成;本地文件改动已忽略,未同步到 Overleaf。"
|
|
84
|
+
],
|
|
85
|
+
"no_sync_changes": [
|
|
86
|
+
"Codex finished locally. There are no changes to sync back to Overleaf.",
|
|
87
|
+
"Codex 已完成本地处理,没有需要同步回 Overleaf 的改动。"
|
|
88
|
+
],
|
|
89
|
+
"no_approved_changes": [
|
|
90
|
+
"No approved changes were written back to Overleaf.",
|
|
91
|
+
"没有已确认的改动写回 Overleaf。"
|
|
92
|
+
],
|
|
93
|
+
"reviewing_unavailable": [
|
|
94
|
+
"No files were written: Track is enabled, but Codex could not verify Overleaf Reviewing/Track Changes.",
|
|
95
|
+
"这轮没有写入:已开启“留痕”要求,但 Codex 没能确认 Overleaf 正在用 Reviewing/Track Changes。"
|
|
96
|
+
],
|
|
97
|
+
"sensitive_blocked": [
|
|
98
|
+
"This run was blocked before Codex received project context because sensitive content was detected.",
|
|
99
|
+
"检测到敏感内容,本轮已在向 Codex 发送项目上下文前阻止。"
|
|
100
|
+
],
|
|
101
|
+
"synced": [
|
|
102
|
+
"Local Codex changes were synced back to Overleaf.",
|
|
103
|
+
"本地 Codex 改动已同步回 Overleaf。"
|
|
104
|
+
],
|
|
105
|
+
"partial_sync": [
|
|
106
|
+
"Local Codex changes were sent to Overleaf, but some items were skipped.",
|
|
107
|
+
"本地 Codex 改动已尝试同步回 Overleaf,但有部分项目被跳过。"
|
|
108
|
+
],
|
|
109
|
+
"blocked_sync": [
|
|
110
|
+
"No changes were written to Overleaf; every change was blocked before writing.",
|
|
111
|
+
"没有向 Overleaf 写入任何改动;所有改动都在写入前被拦下。"
|
|
112
|
+
],
|
|
113
|
+
"unconfirmed_sync": [
|
|
114
|
+
"Codex tried to write local changes to Overleaf, but the write result did not confirm any applied or skipped entries.",
|
|
115
|
+
"Codex 已尝试把本地改动写入 Overleaf,但写入结果没有确认任何已写入或已跳过条目。"
|
|
116
|
+
],
|
|
117
|
+
"partial_sync_note": [
|
|
118
|
+
"Sync note: local Codex changes were sent to Overleaf, but some items were skipped.",
|
|
119
|
+
"同步提示:本地 Codex 改动已尝试写回 Overleaf,但有部分项目被跳过。"
|
|
120
|
+
],
|
|
121
|
+
"blocked_sync_note": [
|
|
122
|
+
"Sync note: no changes were written to Overleaf; every change was blocked before writing.",
|
|
123
|
+
"同步提示:没有向 Overleaf 写入任何改动;所有改动都在写入前被拦下。"
|
|
124
|
+
],
|
|
125
|
+
"unconfirmed_sync_note": [
|
|
126
|
+
"Writeback note: Codex could not confirm any Overleaf write result entries, so local mirror refresh and auto compile were skipped.",
|
|
127
|
+
"写入提示:Codex 没能确认任何 Overleaf 写入结果条目,因此已跳过本地 mirror 刷新和自动编译。"
|
|
128
|
+
],
|
|
129
|
+
"retry_sync": [
|
|
130
|
+
"Retried the remaining file transfers for this task.",
|
|
131
|
+
"已重试本轮未完成的文件同步。"
|
|
132
|
+
],
|
|
133
|
+
"default_no_write": [
|
|
134
|
+
"This task completed without writing Overleaf files.",
|
|
135
|
+
"这轮任务已完成,没有写入 Overleaf 文件。"
|
|
136
|
+
],
|
|
137
|
+
"default_complete": [
|
|
138
|
+
"This task completed.",
|
|
139
|
+
"这轮任务已完成。"
|
|
140
|
+
],
|
|
141
|
+
"warm_mirror_stale": [
|
|
142
|
+
"This run did not continue: Overleaf did not provide the full project, and the local workspace is not fresh enough.",
|
|
143
|
+
"这轮没有继续:Overleaf 没有提供完整项目内容,本地 workspace 也不够新。"
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const ERROR_CONCLUSIONS = {
|
|
148
|
+
"local_skill_unavailable": {
|
|
149
|
+
"code": "codex_local_skill_unavailable"
|
|
150
|
+
},
|
|
151
|
+
"local_skills_failed": {
|
|
152
|
+
"code": "codex_local_skills_failed"
|
|
153
|
+
},
|
|
154
|
+
"mode_removed": {
|
|
155
|
+
"message": "suggest_mode_removed"
|
|
156
|
+
},
|
|
157
|
+
"mode_write_required": {
|
|
158
|
+
"message": "Mode must be \"ask\" or \"auto\""
|
|
159
|
+
},
|
|
160
|
+
"invalid_json": {
|
|
161
|
+
"message": "Agent returned invalid JSON"
|
|
162
|
+
},
|
|
163
|
+
"invalid_output": {
|
|
164
|
+
"message": "Could not parse Codex output"
|
|
165
|
+
},
|
|
166
|
+
"timeout": {
|
|
167
|
+
"message": "timed out"
|
|
168
|
+
},
|
|
169
|
+
"output_limit": {
|
|
170
|
+
"message": "output limit exceeded"
|
|
171
|
+
},
|
|
172
|
+
"context_limit": {
|
|
173
|
+
"code": "codex_context_limit"
|
|
174
|
+
},
|
|
175
|
+
"codex_not_found": {
|
|
176
|
+
"message": "codex_not_found"
|
|
177
|
+
},
|
|
178
|
+
"project_locked": {
|
|
179
|
+
"message": "project_locked"
|
|
180
|
+
},
|
|
181
|
+
"reasoning_unsupported": {
|
|
182
|
+
"message": "unsupported_parameter reasoning.summary"
|
|
183
|
+
},
|
|
184
|
+
"storage_quota": {
|
|
185
|
+
"message": "quota"
|
|
186
|
+
},
|
|
187
|
+
"checkpoint": {
|
|
188
|
+
"message": "checkpoint"
|
|
189
|
+
},
|
|
190
|
+
"stale_source": {
|
|
191
|
+
"message": "changed while Codex was working"
|
|
192
|
+
},
|
|
193
|
+
"post_processing": {
|
|
194
|
+
"codexReturned": true
|
|
195
|
+
},
|
|
196
|
+
"ask_failed": {
|
|
197
|
+
"mode": "ask"
|
|
198
|
+
},
|
|
199
|
+
"auto_failed": {
|
|
200
|
+
"mode": "auto"
|
|
201
|
+
},
|
|
202
|
+
"provider_revision_conflict": {
|
|
203
|
+
"message": "provider_revision_conflict"
|
|
204
|
+
},
|
|
205
|
+
"provider_auth_rejected": {
|
|
206
|
+
"message": "provider_auth_rejected"
|
|
207
|
+
},
|
|
208
|
+
"provider_model_not_found": {
|
|
209
|
+
"message": "provider_model_not_found"
|
|
210
|
+
},
|
|
211
|
+
"provider_model_not_configured": {
|
|
212
|
+
"message": "provider_model_not_configured"
|
|
213
|
+
},
|
|
214
|
+
"provider_protocol_unverified": {
|
|
215
|
+
"message": "provider_protocol_unverified"
|
|
216
|
+
},
|
|
217
|
+
"provider_protocol_incompatible": {
|
|
218
|
+
"message": "provider_protocol_incompatible"
|
|
219
|
+
},
|
|
220
|
+
"provider_rate_limited": {
|
|
221
|
+
"message": "provider_rate_limited"
|
|
222
|
+
},
|
|
223
|
+
"provider_connection_timeout": {
|
|
224
|
+
"message": "provider_connection_timeout"
|
|
225
|
+
},
|
|
226
|
+
"provider_dns_failed": {
|
|
227
|
+
"message": "provider_dns_failed"
|
|
228
|
+
},
|
|
229
|
+
"provider_tls_failed": {
|
|
230
|
+
"message": "provider_tls_failed"
|
|
231
|
+
},
|
|
232
|
+
"provider_not_found": {
|
|
233
|
+
"message": "provider_not_found"
|
|
234
|
+
},
|
|
235
|
+
"provider_store_corrupt": {
|
|
236
|
+
"message": "provider_store_corrupt"
|
|
237
|
+
},
|
|
238
|
+
"provider_store_unavailable": {
|
|
239
|
+
"message": "provider_store_unavailable"
|
|
240
|
+
},
|
|
241
|
+
"provider_response_invalid": {
|
|
242
|
+
"message": "provider_response_invalid"
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
28
246
|
const TECHNICAL_EVENT_PATTERNS = [
|
|
29
247
|
/^agent\.command\./,
|
|
30
248
|
/^native\.task\./,
|
|
@@ -436,16 +654,11 @@
|
|
|
436
654
|
}
|
|
437
655
|
|
|
438
656
|
function isContextCompactionEvent(event = {}) {
|
|
439
|
-
|
|
440
|
-
const method = String(event.detail?.method || event.title || '');
|
|
441
|
-
const label = `${type} ${method}`;
|
|
442
|
-
return /(compact|compaction|compacted)/i.test(label)
|
|
443
|
-
&& /(context|thread|turn|conversation|codex)/i.test(label);
|
|
657
|
+
return Boolean(deps.RunActivityModel.contextCompactionState(event));
|
|
444
658
|
}
|
|
445
659
|
|
|
446
660
|
function formatContextCompactionCheckpoint(event = {}, locale = 'en') {
|
|
447
|
-
const
|
|
448
|
-
const running = event.status === 'running' || /(started|starting|begin|prepar)/i.test(method);
|
|
661
|
+
const running = deps.RunActivityModel.contextCompactionState(event) === 'running';
|
|
449
662
|
return {
|
|
450
663
|
kind: 'checkpoint',
|
|
451
664
|
visible: true,
|
|
@@ -728,7 +941,7 @@
|
|
|
728
941
|
};
|
|
729
942
|
}
|
|
730
943
|
|
|
731
|
-
function
|
|
944
|
+
function translateRawErrorCopy(message, context = {}) {
|
|
732
945
|
const locale = normalizeLocale(context);
|
|
733
946
|
const text = String(message || '');
|
|
734
947
|
// Each branch returns the user-visible {conclusion, nextStep} pair and,
|
|
@@ -753,6 +966,13 @@
|
|
|
753
966
|
failureCode: 'codex_local_skills_failed'
|
|
754
967
|
};
|
|
755
968
|
}
|
|
969
|
+
if (getFailureReasonsModule()?.isContextLimitError(text, context.code)) {
|
|
970
|
+
return {
|
|
971
|
+
conclusion: textFor(locale, '本轮已停止:上下文超出模型的容量限制。', 'This run stopped because the model context limit was exceeded.'),
|
|
972
|
+
nextStep: textFor(locale, '请重试任务;如果再次超限,可使用 /compact,或新建会话并减少上下文。', 'Retry the task. If the context limit is reached again, use /compact or start a new conversation with less context.'),
|
|
973
|
+
failureCode: 'codex_context_limit'
|
|
974
|
+
};
|
|
975
|
+
}
|
|
756
976
|
const providerError = translateProviderError(text, locale);
|
|
757
977
|
if (providerError) {
|
|
758
978
|
return providerError;
|
|
@@ -871,12 +1091,107 @@
|
|
|
871
1091
|
};
|
|
872
1092
|
}
|
|
873
1093
|
|
|
1094
|
+
function productConclusionText(key, locale = 'en') {
|
|
1095
|
+
if (Object.hasOwn(PRODUCT_CONCLUSIONS, key)) return PRODUCT_CONCLUSIONS[key][locale === 'zh' ? 1 : 0];
|
|
1096
|
+
const error = key.startsWith('error:') && Object.hasOwn(ERROR_CONCLUSIONS, key.slice(6)) && ERROR_CONCLUSIONS[key.slice(6)];
|
|
1097
|
+
return error ? translateRawErrorCopy(error.message || '', { ...error, locale }).conclusion : null;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function translateRawError(message, context = {}) {
|
|
1101
|
+
const translated = translateRawErrorCopy(message, context), locale = normalizeLocale(context);
|
|
1102
|
+
const key = Object.keys(ERROR_CONCLUSIONS).find(id => productConclusionText(`error:${id}`, locale) === translated.conclusion);
|
|
1103
|
+
return { ...translated, ...(key ? { conclusionKey: `error:${key}` } : {}) };
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Old Native failures retained the real transport error even when their
|
|
1107
|
+
// product summary used the generic empty-result category. Re-project only
|
|
1108
|
+
// those identified product events; model prose and stored records stay intact.
|
|
1109
|
+
function projectContextLimitEvent(event, locale = 'en') {
|
|
1110
|
+
const failure = event?.failure;
|
|
1111
|
+
if (event?.status !== 'failed' || event.streamRole || event.guidanceId || event.subagent) return event;
|
|
1112
|
+
if (failure?.code === 'codex_context_limit' && failure.stage === 'codex'
|
|
1113
|
+
&& (!event.kind || event.kind === 'activity')
|
|
1114
|
+
&& ['en', 'zh'].some(language => productConclusionText('error:context_limit', language) === event.title)) {
|
|
1115
|
+
return { ...event, title: productConclusionText('error:context_limit', locale) };
|
|
1116
|
+
}
|
|
1117
|
+
if (failure?.code !== 'codex_no_usable_result' || failure.stage !== 'codex'
|
|
1118
|
+
|| !getFailureReasonsModule()?.isContextLimitError(failure.technicalMessage)) return event;
|
|
1119
|
+
const generic = ['ask_failed', 'auto_failed'].flatMap(key => ['en', 'zh'].map(language =>
|
|
1120
|
+
productConclusionText(`error:${key}`, language)));
|
|
1121
|
+
const translated = translateRawError(failure.technicalMessage, { locale });
|
|
1122
|
+
const projectedFailure = { ...failure, code: translated.failureCode,
|
|
1123
|
+
userMessage: translated.conclusion, nextAction: translated.nextStep };
|
|
1124
|
+
if ((!event.kind || event.kind === 'activity') && generic.includes(event.title)) {
|
|
1125
|
+
return { ...event, title: translated.conclusion, failure: projectedFailure };
|
|
1126
|
+
}
|
|
1127
|
+
const structured = event.detailStructured, source = structured?.conclusionSource;
|
|
1128
|
+
if (event.kind !== 'report' || source?.origin !== 'product'
|
|
1129
|
+
|| !['error:ask_failed', 'error:auto_failed'].includes(source.key)
|
|
1130
|
+
|| !generic.includes(structured.conclusion)) return event;
|
|
1131
|
+
const oldNext = ['en', 'zh'].map(language => translateRawError('', { locale: language }).nextStep);
|
|
1132
|
+
return { ...event, failure: projectedFailure, detailStructured: { ...structured,
|
|
1133
|
+
conclusion: translated.conclusion, conclusionSource: { origin: 'product', key: translated.conclusionKey },
|
|
1134
|
+
meta: (structured.meta || []).map(row => row.key === 'nextStep' && oldNext.includes(row.value)
|
|
1135
|
+
? { ...row, value: translated.nextStep } : row) } };
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function normalizeConclusionSource(source) {
|
|
1139
|
+
if (source?.origin !== 'product' || typeof source.key !== 'string') return null;
|
|
1140
|
+
if (productConclusionText(source.key) !== null) return { origin: 'product', key: source.key };
|
|
1141
|
+
if (source.key !== 'writeback' || !['synced', 'partial_sync', 'blocked_sync', 'unconfirmed_sync'].includes(source.outcome)) return null;
|
|
1142
|
+
return { origin: 'product', key: 'writeback', outcome: source.outcome,
|
|
1143
|
+
prefixLength: Number.isSafeInteger(source.prefixLength) && source.prefixLength >= 0 ? source.prefixLength : -1,
|
|
1144
|
+
prefixKey: source.prefixKey === 'retry_sync' ? 'retry_sync' : '',
|
|
1145
|
+
hasCompile: source.hasCompile === true,
|
|
1146
|
+
compileStatus: typeof source.compileStatus === 'string' ? source.compileStatus : '',
|
|
1147
|
+
compileReason: typeof source.compileReason === 'string' ? source.compileReason : '',
|
|
1148
|
+
errorCount: Number.isSafeInteger(source.errorCount) && source.errorCount >= 0 ? source.errorCount : 0,
|
|
1149
|
+
errors: Array.isArray(source.errors) ? source.errors.slice(0, 3).filter(value => typeof value === 'string') : [],
|
|
1150
|
+
warningCount: Number.isSafeInteger(source.warningCount) && source.warningCount >= 0 ? source.warningCount : 0 };
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function projectProductConclusion(source, locale = 'en', localizeCompileReason = value => value, raw = '') {
|
|
1154
|
+
const identity = normalizeConclusionSource(source);
|
|
1155
|
+
if (!identity) return null;
|
|
1156
|
+
if (identity.key !== 'writeback') return productConclusionText(identity.key, locale);
|
|
1157
|
+
if (identity.prefixLength < 0 || identity.prefixLength > raw.length) return null;
|
|
1158
|
+
const originalPrefix = raw.slice(0, identity.prefixLength);
|
|
1159
|
+
if (identity.prefixKey && !['en', 'zh'].some(language => productConclusionText(identity.prefixKey, language) === originalPrefix)) return null;
|
|
1160
|
+
const compose = (language, projectReason) => {
|
|
1161
|
+
const tx = (en, zh) => language === 'zh' ? zh : en;
|
|
1162
|
+
const prefix = projectReason && identity.prefixKey ? productConclusionText(identity.prefixKey, language) : originalPrefix;
|
|
1163
|
+
const note = productConclusionText(identity.outcome + (prefix && identity.outcome !== 'synced' ? '_note' : ''), language);
|
|
1164
|
+
const conclusion = prefix ? [prefix, identity.outcome === 'synced' ? '' : note].filter(Boolean).join('\n\n') : note;
|
|
1165
|
+
const { compileStatus: status, errors, errorCount, warningCount: warnings } = identity;
|
|
1166
|
+
if (!identity.hasCompile) return conclusion;
|
|
1167
|
+
const reason = projectReason ? localizeCompileReason(identity.compileReason) : identity.compileReason;
|
|
1168
|
+
const summary = errorCount
|
|
1169
|
+
? tx(`Post-write compile check: ${errorCount} remaining error(s): ${errors.slice(0, 3).join('; ')}`,
|
|
1170
|
+
`写后编译检查:仍有 ${errorCount} 个错误:${errors.slice(0, 3).join(';')}`)
|
|
1171
|
+
: status === 'success' ? (warnings
|
|
1172
|
+
? tx(`Post-write compile check succeeded with ${warnings} warning(s).`, `写后编译检查已通过,但仍有 ${warnings} 个警告。`)
|
|
1173
|
+
: tx('Post-write compile check succeeded with no reported errors.', '写后编译检查已通过,未发现错误。'))
|
|
1174
|
+
: status === 'triggered' ? (warnings
|
|
1175
|
+
? tx(`Post-write compile was triggered; latest log shows ${warnings} warning(s).`, `已触发写后编译;最新日志显示 ${warnings} 个警告。`)
|
|
1176
|
+
: tx('Post-write compile was triggered; Overleaf may still be updating the result.', '已触发写后编译;Overleaf 可能仍在更新结果。'))
|
|
1177
|
+
: status === 'unconfirmed' ? getI18nModule().t(language, 'runCompileUnconfirmed')
|
|
1178
|
+
: status === 'failed' ? tx(`Post-write compile check failed: ${reason || 'unknown reason'}`,
|
|
1179
|
+
`写后编译检查失败:${reason || '未知原因'}`)
|
|
1180
|
+
: tx(`Post-write compile finished with status: ${status || 'unknown'}.`, `写后编译完成,状态:${status || '未知'}。`);
|
|
1181
|
+
return [conclusion, summary].filter(Boolean).join('\n');
|
|
1182
|
+
};
|
|
1183
|
+
// A compacted or changed raw answer must never be reconstructed from metadata.
|
|
1184
|
+
if (!['en', 'zh'].some(language => compose(language, false) === raw)) return null;
|
|
1185
|
+
return compose(locale, true);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
874
1188
|
function buildHumanCompletionReport(input = {}) {
|
|
875
1189
|
const locale = normalizeLocale(input);
|
|
876
1190
|
const applyResults = normalizeApplyResults(input.applyResults);
|
|
877
1191
|
const appliedCount = countApplyResultEntries(applyResults, 'applied');
|
|
878
1192
|
const skippedCount = countApplyResultEntries(applyResults, 'skipped');
|
|
879
|
-
const translatedError = input.errorMessage ? translateRawError(input.errorMessage, { mode: input.mode, locale
|
|
1193
|
+
const translatedError = input.errorMessage ? translateRawError(input.errorMessage, { mode: input.mode, locale, code: input.failure?.code,
|
|
1194
|
+
codexReturned: input.conclusionKey === 'error:post_processing' }) : null;
|
|
880
1195
|
const userReport = normalizeUserReport(input.userReport);
|
|
881
1196
|
const report = userReport || buildFallbackReport(input, {
|
|
882
1197
|
appliedCount,
|
|
@@ -928,6 +1243,16 @@
|
|
|
928
1243
|
return report.text;
|
|
929
1244
|
}
|
|
930
1245
|
|
|
1246
|
+
function completionConclusionText(report) {
|
|
1247
|
+
const source = report.conclusionSource;
|
|
1248
|
+
// The new blocked composite preserves its exact model prefix. Keep the
|
|
1249
|
+
// historical formatter for every other report shape.
|
|
1250
|
+
if (source?.key === 'writeback' && source.outcome === 'blocked_sync'
|
|
1251
|
+
&& typeof report.conclusion === 'string' && ['en', 'zh'].some(locale =>
|
|
1252
|
+
projectProductConclusion(source, locale, undefined, report.conclusion) === report.conclusion)) return report.conclusion;
|
|
1253
|
+
return cleanVisibleMarkdownText(report.conclusion || '');
|
|
1254
|
+
}
|
|
1255
|
+
|
|
931
1256
|
/**
|
|
932
1257
|
* Splits the human report into a conclusion (the human-language outcome),
|
|
933
1258
|
* a body of list-style content sections (checked / findings / planned /
|
|
@@ -938,7 +1263,7 @@
|
|
|
938
1263
|
* shape for transcripts, storage fallback, and existing assertions.
|
|
939
1264
|
*/
|
|
940
1265
|
function buildStructuredHumanReport(report = {}, locale = 'en') {
|
|
941
|
-
const conclusion =
|
|
1266
|
+
const conclusion = completionConclusionText(report);
|
|
942
1267
|
|
|
943
1268
|
const bodySections = [];
|
|
944
1269
|
addListSection(bodySections, textFor(locale, '检查范围', 'Checked'), report.checked, locale);
|
|
@@ -982,12 +1307,13 @@
|
|
|
982
1307
|
}
|
|
983
1308
|
|
|
984
1309
|
if (report.saveState) meta.push({ key: 'saveState', label: textFor(locale, '保存', 'Save'), value: report.saveState });
|
|
985
|
-
return { conclusion, body: bodySections.join('\n\n'), meta
|
|
1310
|
+
return { conclusion, body: bodySections.join('\n\n'), meta,
|
|
1311
|
+
...(normalizeConclusionSource(report.conclusionSource) ? { conclusionSource: normalizeConclusionSource(report.conclusionSource) } : {}) };
|
|
986
1312
|
}
|
|
987
1313
|
|
|
988
1314
|
function formatHumanReport(report = {}, locale = 'en') {
|
|
989
1315
|
const sections = [];
|
|
990
|
-
const conclusion =
|
|
1316
|
+
const conclusion = completionConclusionText(report);
|
|
991
1317
|
if (conclusion) {
|
|
992
1318
|
sections.push(textFor(locale, `结论:${conclusion}`, `Conclusion: ${conclusion}`));
|
|
993
1319
|
}
|
|
@@ -1028,8 +1354,13 @@
|
|
|
1028
1354
|
? textFor(locale, '这轮任务已完成,没有写入 Overleaf 文件。', 'This task completed without writing Overleaf files.')
|
|
1029
1355
|
: textFor(locale, '这轮任务已完成。', 'This task completed.'));
|
|
1030
1356
|
|
|
1357
|
+
const conclusionSource = counts.translatedError?.conclusionKey
|
|
1358
|
+
? { origin: 'product', key: counts.translatedError.conclusionKey }
|
|
1359
|
+
: input.conclusionKey ? { origin: 'product', key: input.conclusionKey }
|
|
1360
|
+
: input.conclusionSource || (!input.conclusion && !input.notes
|
|
1361
|
+
? { origin: 'product', key: noWrites ? 'default_no_write' : 'default_complete' } : null);
|
|
1031
1362
|
return {
|
|
1032
|
-
conclusion,
|
|
1363
|
+
conclusion, conclusionSource: normalizeConclusionSource(conclusionSource),
|
|
1033
1364
|
checked: normalizeStringList(input.checked || input.userReport?.checked),
|
|
1034
1365
|
findings: normalizeStringList(input.findings),
|
|
1035
1366
|
plannedChanges: counts.appliedCount ? [] : operations.map(operation => formatOperationLine(operation, locale)),
|
|
@@ -1756,6 +2087,8 @@
|
|
|
1756
2087
|
|
|
1757
2088
|
return {
|
|
1758
2089
|
buildHumanCompletionReport,
|
|
2090
|
+
projectProductConclusion,
|
|
2091
|
+
projectContextLimitEvent,
|
|
1759
2092
|
buildStructuredHumanReport,
|
|
1760
2093
|
formatFallbackFinalReport,
|
|
1761
2094
|
formatFailureBlock,
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
createdAt: redactSecretLikeText(stringField(input.createdAt)) || now,
|
|
33
33
|
completedAt: '',
|
|
34
34
|
mode: redactSecretLikeText(stringField(input.mode)),
|
|
35
|
+
requireReviewing: typeof input.requireReviewing === 'boolean' ? input.requireReviewing : undefined,
|
|
36
|
+
trackingDecision: ['continue', 'disable'].includes(input.trackingDecision) ? input.trackingDecision : '',
|
|
35
37
|
providerId: redactSecretLikeText(stringField(input.providerId)) || 'builtin',
|
|
36
38
|
providerName: redactSecretLikeText(stringField(input.providerName)),
|
|
37
39
|
providerRevision: finiteNumber(input.providerRevision) || 0,
|
|
@@ -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 = '2.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.6.0';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -11,31 +11,46 @@
|
|
|
11
11
|
const COMPILABLE_EXTENSIONS = new Set(['.tex', '.bib', '.sty', '.cls', '.bst', '.bbx', '.cbx']);
|
|
12
12
|
function parseCompileResponse(response) {
|
|
13
13
|
if (!response || typeof response !== 'object') return { ok: false, reason: 'Invalid compile response: not an object' };
|
|
14
|
-
if (!Array.isArray(response.outputFiles)) return { ok: false, reason: 'Invalid compile response: outputFiles is not an array' };
|
|
15
|
-
const logFile = response.outputFiles.find(function (file) {
|
|
14
|
+
if (!Array.isArray(response.outputFiles)) return { ok: false, status: response.status, reason: 'Invalid compile response: outputFiles is not an array' };
|
|
15
|
+
const logFile = response.outputFiles.find(file => /(^|\/)output\.log$/.test(file?.path || '')) || response.outputFiles.find(function (file) {
|
|
16
16
|
if (!file) return false;
|
|
17
17
|
if (file.type === 'log') return true;
|
|
18
18
|
if (typeof file.path === 'string' && file.path.endsWith('.log')) return true;
|
|
19
19
|
return false;
|
|
20
20
|
});
|
|
21
|
-
if (!logFile) return { ok: false, reason: 'No log file found in compile output' };
|
|
22
|
-
var logUrl = logFile.url
|
|
23
|
-
if (!logUrl) return { ok: false, reason: 'Log file has no URL' };
|
|
21
|
+
if (!logFile) return { ok: false, status: response.status, reason: 'No log file found in compile output' };
|
|
22
|
+
var logUrl = typeof logFile.url === 'string' ? logFile.url : null;
|
|
23
|
+
if (!logUrl) return { ok: false, status: response.status, reason: 'Log file has no URL' };
|
|
24
24
|
return {
|
|
25
25
|
ok: true,
|
|
26
26
|
status: response.status,
|
|
27
|
+
fromCache: response.fromCache === true,
|
|
27
28
|
logUrl: logUrl,
|
|
28
|
-
logPath: logFile.path || null
|
|
29
|
+
logPath: logFile.path || null,
|
|
30
|
+
build: logFile.build || null,
|
|
31
|
+
pdfDownloadDomain: normalizePdfDownloadDomain(response.pdfDownloadDomain),
|
|
32
|
+
clsiServerId: typeof response.clsiServerId === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(response.clsiServerId)
|
|
33
|
+
? response.clsiServerId : null
|
|
29
34
|
};
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
function normalizePdfDownloadDomain(value) {
|
|
38
|
+
if (typeof value !== 'string' || !/^https:\/\/[^\s\\]+$/i.test(value)) return null;
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(value);
|
|
41
|
+
return url.protocol === 'https:' && !url.username && !url.password && !url.search && !url.hash ? value : null;
|
|
42
|
+
} catch (_error) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
function extractErrorBlocks(lines) {
|
|
33
48
|
var blocks = [];
|
|
34
49
|
var i = 0;
|
|
35
50
|
|
|
36
51
|
while (i < lines.length && blocks.length < 20) {
|
|
37
52
|
var line = lines[i];
|
|
38
|
-
if (line
|
|
53
|
+
if (isLogErrorLine(line) || /^l\.\d+/.test(line)) {
|
|
39
54
|
var block = [];
|
|
40
55
|
// Collect surrounding context: go back up to 2 lines for pre-context
|
|
41
56
|
var start = Math.max(0, i - 2);
|
|
@@ -106,6 +121,12 @@
|
|
|
106
121
|
return COMPILABLE_EXTENSIONS.has(ext);
|
|
107
122
|
}
|
|
108
123
|
|
|
124
|
+
function isLogErrorLine(line) {
|
|
125
|
+
// TeX's -file-line-error output (used by Overleaf) has no leading '!'.
|
|
126
|
+
return line.startsWith('!') || /^Runaway argument/.test(line)
|
|
127
|
+
|| (/^(?:\.{0,2}\/|[A-Za-z]:[\\/])?.+?:\d+:\s+\S/.test(line) && !/\bWarning\b/i.test(line));
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
function parseLogErrors(logContent) {
|
|
110
131
|
var result = { errors: [], warnings: [] };
|
|
111
132
|
|
|
@@ -115,20 +136,23 @@
|
|
|
115
136
|
|
|
116
137
|
var lines = logContent.split('\n');
|
|
117
138
|
|
|
118
|
-
//
|
|
139
|
+
// Retain the bounded error list; consumers limit displayed snippets separately.
|
|
119
140
|
for (var i = 0; i < lines.length && result.errors.length < 50; i++) {
|
|
120
|
-
if (lines[i]
|
|
141
|
+
if (isLogErrorLine(lines[i])) {
|
|
121
142
|
var errorLines = [lines[i]];
|
|
122
143
|
for (var j = 1; j <= 3 && (i + j) < lines.length; j++) {
|
|
144
|
+
if (!lines[i + j].trim() || isLogErrorLine(lines[i + j])) break;
|
|
123
145
|
errorLines.push(lines[i + j]);
|
|
124
146
|
}
|
|
125
147
|
result.errors.push(errorLines.join('\n'));
|
|
126
148
|
}
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
//
|
|
151
|
+
// Count only the warning forms Overleaf's own log parser reports, so
|
|
152
|
+
// informational lines such as "Package: infwarerr … info/warning/error
|
|
153
|
+
// messages" are not shown as warnings.
|
|
130
154
|
for (var k = 0; k < lines.length && result.warnings.length < 50; k++) {
|
|
131
|
-
if (
|
|
155
|
+
if (/^LaTeX(?:3| Font)? Warning: /.test(lines[k]) || /^(?:Package|Class|Module) \b.+\b Warning:/.test(lines[k])) {
|
|
132
156
|
result.warnings.push(lines[k]);
|
|
133
157
|
}
|
|
134
158
|
}
|
|
@@ -137,18 +161,35 @@
|
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
function buildPostWriteCompileSummary({ result = null, logResult = null, error = null } = {}) {
|
|
140
|
-
const
|
|
164
|
+
const captured = result?.ok === true && result.resultCaptured === true
|
|
165
|
+
&& Number.isSafeInteger(result.compile?.captureId) && result.compile.captureId > 0;
|
|
166
|
+
const logAvailable = captured && logResult?.ok === true && logResult.fresh === true
|
|
167
|
+
&& result.compile?.captureId === logResult.captureId;
|
|
141
168
|
const errors = logAvailable && Array.isArray(logResult.errors)
|
|
142
|
-
? logResult.errors.
|
|
169
|
+
? logResult.errors.map(formatCompileDiagnosticForSummary)
|
|
143
170
|
: [];
|
|
144
171
|
const warnings = logAvailable && Array.isArray(logResult.warnings)
|
|
145
172
|
? logResult.warnings.slice(0, 5).map(formatCompileDiagnosticForSummary)
|
|
146
173
|
: [];
|
|
147
|
-
const
|
|
174
|
+
const compilerFailed = captured && logResult?.fresh !== false
|
|
175
|
+
&& ['failed', 'failure', 'error', 'timedout', 'timeout'].includes(result.compile?.status);
|
|
176
|
+
if (compilerFailed && !errors.length) errors.push('Post-write compile failed.');
|
|
177
|
+
const failed = compilerFailed || errors.length > 0;
|
|
178
|
+
const success = captured && result.compile?.ok === true && result.compile.status === 'success'
|
|
179
|
+
&& result.compile.fromCache !== true && logAvailable;
|
|
180
|
+
const status = failed ? 'failed' : success ? 'success' : result?.ok && !captured ? 'triggered' : 'unconfirmed';
|
|
181
|
+
let reason = error ? error?.message || 'Post-write compile failed.'
|
|
182
|
+
: !result?.ok ? result?.reason || 'Post-write compile did not succeed.' : result.reason || '';
|
|
183
|
+
if (status === 'unconfirmed' || status === 'triggered') {
|
|
184
|
+
// Technical evidence belongs in the existing activity output, not the outcome label.
|
|
185
|
+
reason ||= logResult?.reason || (!captured ? 'compile_capture_missing'
|
|
186
|
+
: result.compile?.reason || (result.compile.fromCache ? 'compile_response_from_cache'
|
|
187
|
+
: logResult?.captureId !== result.compile.captureId ? 'compile_log_capture_mismatch'
|
|
188
|
+
: logResult?.fresh === false ? 'compile_log_stale' : 'compile_result_unconfirmed'));
|
|
189
|
+
}
|
|
148
190
|
return {
|
|
149
|
-
status
|
|
150
|
-
reason
|
|
151
|
-
: !result?.ok ? result?.reason || 'Post-write compile did not succeed.' : result.reason || '',
|
|
191
|
+
status,
|
|
192
|
+
reason,
|
|
152
193
|
errors,
|
|
153
194
|
warnings,
|
|
154
195
|
logAvailable
|
|
@@ -51,7 +51,12 @@
|
|
|
51
51
|
if (!attachments.length) return;
|
|
52
52
|
throw failure('composer_draft_cleared');
|
|
53
53
|
}
|
|
54
|
-
if (value.revision !== revision)
|
|
54
|
+
if (value.revision !== revision) {
|
|
55
|
+
// Writing what another tab already stored changes nothing (for example
|
|
56
|
+
// both tabs clearing an empty draft on send); only a real edit conflicts.
|
|
57
|
+
if (JSON.stringify(attachments) === JSON.stringify(result.attachments)) return;
|
|
58
|
+
throw failure('composer_draft_conflict');
|
|
59
|
+
}
|
|
55
60
|
if (attachments.length && value.unbased && clearedAt && value.changedAt <= clearedAt) {
|
|
56
61
|
throw failure('composer_draft_cleared');
|
|
57
62
|
}
|