codex-overleaf-link 1.1.1
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/LICENSE +21 -0
- package/README.md +457 -0
- package/bin/codex-overleaf-link.mjs +223 -0
- package/extension/src/shared/agentTranscript.js +1175 -0
- package/extension/src/shared/auditRecords.js +568 -0
- package/extension/src/shared/compatibility.js +372 -0
- package/extension/src/shared/compileAdapter.js +176 -0
- package/extension/src/shared/governanceRules.js +252 -0
- package/extension/src/shared/i18n.js +565 -0
- package/extension/src/shared/models.js +106 -0
- package/extension/src/shared/otText.js +505 -0
- package/extension/src/shared/projectFiles.js +180 -0
- package/extension/src/shared/reviewing.js +99 -0
- package/extension/src/shared/sensitiveScan.js +116 -0
- package/extension/src/shared/sessionState.js +1084 -0
- package/extension/src/shared/staleGuard.js +150 -0
- package/extension/src/shared/storageDb.js +986 -0
- package/extension/src/shared/storageKeys.js +29 -0
- package/extension/src/shared/storageMigration.js +168 -0
- package/extension/src/shared/summary.js +248 -0
- package/extension/src/shared/undoOperations.js +369 -0
- package/native-host/src/codexArgs.js +43 -0
- package/native-host/src/codexHome.js +538 -0
- package/native-host/src/codexModels.js +247 -0
- package/native-host/src/codexPrompt.js +192 -0
- package/native-host/src/codexPromptAssembly.js +411 -0
- package/native-host/src/codexSessionRunner.js +1247 -0
- package/native-host/src/commandApproval.js +914 -0
- package/native-host/src/debugLog.js +78 -0
- package/native-host/src/diffEngine.js +247 -0
- package/native-host/src/index.js +132 -0
- package/native-host/src/launcher.js +81 -0
- package/native-host/src/localSkills.js +476 -0
- package/native-host/src/manifest.js +226 -0
- package/native-host/src/mirrorSensitiveScan.js +119 -0
- package/native-host/src/mirrorWorkspace.js +1019 -0
- package/native-host/src/nativeDoctor.js +826 -0
- package/native-host/src/nativeEnvironment.js +315 -0
- package/native-host/src/nativeHostPlatform.js +112 -0
- package/native-host/src/nativeMessaging.js +60 -0
- package/native-host/src/nativeQuotas.js +294 -0
- package/native-host/src/nativeResponseBudget.js +194 -0
- package/native-host/src/runtimeInstaller.js +357 -0
- package/native-host/src/taskRunner.js +3 -0
- package/native-host/src/taskRunnerRuntime.js +1083 -0
- package/native-host/src/textPatch.js +287 -0
- package/package.json +40 -0
- package/scripts/codex-json-agent.mjs +269 -0
- package/scripts/install-native-host.mjs +255 -0
- package/scripts/npm-package-files-v1.1.1.txt +52 -0
- package/scripts/uninstall-native-host.mjs +298 -0
- package/scripts/verify-npm-package.mjs +296 -0
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
(function initAgentTranscript(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafAgentTranscript = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function agentTranscriptFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const TECHNICAL_EVENT_PATTERNS = [
|
|
11
|
+
/^agent\.command\./,
|
|
12
|
+
/^native\.task\./,
|
|
13
|
+
/^codex\.prompt\./,
|
|
14
|
+
/^codex\.stdout\./,
|
|
15
|
+
/^codex\.item\./,
|
|
16
|
+
/^codex\.turn\./
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const OPERATION_LABELS = {
|
|
20
|
+
zh: {
|
|
21
|
+
edit: '编辑',
|
|
22
|
+
create: '新建',
|
|
23
|
+
rename: '重命名',
|
|
24
|
+
move: '移动',
|
|
25
|
+
delete: '删除'
|
|
26
|
+
},
|
|
27
|
+
en: {
|
|
28
|
+
edit: 'edit',
|
|
29
|
+
create: 'create',
|
|
30
|
+
rename: 'rename',
|
|
31
|
+
move: 'move',
|
|
32
|
+
delete: 'delete'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function normalizeLocale(options = {}) {
|
|
37
|
+
return options?.locale === 'en' ? 'en' : 'zh';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function textFor(locale, zh, en) {
|
|
41
|
+
return locale === 'en' ? en : zh;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function mapAgentEventToActivity(event = {}, options = {}) {
|
|
45
|
+
const locale = normalizeLocale(options);
|
|
46
|
+
const type = String(event.type || '');
|
|
47
|
+
|
|
48
|
+
if (isContextCompactionEvent(event)) {
|
|
49
|
+
return formatContextCompactionCheckpoint(event, locale);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (type === 'overleaf.sync.started') {
|
|
53
|
+
const fileCount = Number(event.detail?.fileCount) || 0;
|
|
54
|
+
return {
|
|
55
|
+
kind: 'activity',
|
|
56
|
+
visible: true,
|
|
57
|
+
title: fileCount
|
|
58
|
+
? textFor(locale, `正在同步 Overleaf 项目到本地 Codex workspace:${fileCount} 个文本文件。`, `Syncing the Overleaf project into the local Codex workspace: ${fileCount} text files.`)
|
|
59
|
+
: textFor(locale, '正在同步 Overleaf 项目到本地 Codex workspace。', 'Syncing the Overleaf project into the local Codex workspace.'),
|
|
60
|
+
status: 'running',
|
|
61
|
+
technicalDetail: normalizeRawEvent(event)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (type === 'overleaf.sync.completed') {
|
|
66
|
+
const fileCount = Number(event.detail?.fileCount) || 0;
|
|
67
|
+
return {
|
|
68
|
+
kind: 'activity',
|
|
69
|
+
visible: true,
|
|
70
|
+
title: fileCount
|
|
71
|
+
? textFor(locale, `已同步 ${fileCount} 个文本文件,本地 Codex 将直接处理这份 workspace。`, `Synced ${fileCount} text files. Local Codex will work from this workspace.`)
|
|
72
|
+
: textFor(locale, '已同步 Overleaf 项目,本地 Codex 将直接处理这份 workspace。', 'Synced the Overleaf project. Local Codex will work from this workspace.'),
|
|
73
|
+
status: 'completed',
|
|
74
|
+
technicalDetail: normalizeRawEvent(event)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (type === 'overleaf.sync.changes') {
|
|
79
|
+
const files = normalizeStringList(event.detail?.files);
|
|
80
|
+
const changedCount = Number(event.detail?.changedCount) || files.length;
|
|
81
|
+
return {
|
|
82
|
+
kind: 'activity',
|
|
83
|
+
visible: true,
|
|
84
|
+
title: changedCount
|
|
85
|
+
? textFor(locale, `Codex 本地改动已收集:${formatFilesInline(files, locale)}。`, `Collected local Codex changes: ${formatFilesInline(files, locale)}.`)
|
|
86
|
+
: textFor(locale, 'Codex 没有产生需要同步回 Overleaf 的文件改动。', 'Codex did not produce file changes to sync back to Overleaf.'),
|
|
87
|
+
status: 'completed',
|
|
88
|
+
technicalDetail: normalizeRawEvent(event)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (type === 'codex.session.event' || type === 'codex.session.request') {
|
|
93
|
+
return mapCodexSessionEvent(event, locale);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (type === 'agent.snapshot.preparing') {
|
|
97
|
+
const fileCount = Number(event.detail?.fileCount) || 0;
|
|
98
|
+
const totalChars = Number(event.detail?.totalChars) || 0;
|
|
99
|
+
return {
|
|
100
|
+
kind: 'activity',
|
|
101
|
+
visible: true,
|
|
102
|
+
title: fileCount
|
|
103
|
+
? textFor(locale, `正在同步 Overleaf 项目到本地上下文:${fileCount} 个文本文件,约 ${formatCompactNumber(totalChars)} 字符。`, `Syncing the Overleaf project into local context: ${fileCount} text files, about ${formatCompactNumber(totalChars)} characters.`)
|
|
104
|
+
: textFor(locale, '正在同步 Overleaf 项目到本地上下文。', 'Syncing the Overleaf project into local context.'),
|
|
105
|
+
status: 'running',
|
|
106
|
+
technicalDetail: normalizeRawEvent(event)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (type === 'agent.snapshot.ready') {
|
|
111
|
+
const fileCount = Number(event.detail?.fileCount) || 0;
|
|
112
|
+
return {
|
|
113
|
+
kind: 'activity',
|
|
114
|
+
visible: true,
|
|
115
|
+
title: fileCount
|
|
116
|
+
? textFor(locale, `已同步 ${fileCount} 个文本文件,Codex 将基于这份内容继续分析。`, `Synced ${fileCount} text files. Codex will continue from this content.`)
|
|
117
|
+
: textFor(locale, '已同步 Overleaf 项目内容,Codex 将基于这份内容继续分析。', 'Synced the Overleaf project. Codex will continue from this content.'),
|
|
118
|
+
status: 'completed',
|
|
119
|
+
technicalDetail: normalizeRawEvent(event)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (type === 'codex.exec.started') {
|
|
124
|
+
return {
|
|
125
|
+
kind: 'activity',
|
|
126
|
+
visible: true,
|
|
127
|
+
title: textFor(locale, '本地 Codex 已开始处理这轮任务。', 'Local Codex started this task.'),
|
|
128
|
+
status: 'running',
|
|
129
|
+
technicalDetail: normalizeRawEvent(event)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (type === 'codex.exec.completed') {
|
|
134
|
+
const failed = event.status === 'failed' || Number(event.detail?.code) !== 0;
|
|
135
|
+
return {
|
|
136
|
+
kind: 'activity',
|
|
137
|
+
visible: true,
|
|
138
|
+
title: failed ? textFor(locale, '本地 Codex 没有正常完成。', 'Local Codex did not finish normally.') : textFor(locale, '本地 Codex 已完成分析。', 'Local Codex finished analysis.'),
|
|
139
|
+
status: failed ? 'failed' : 'completed',
|
|
140
|
+
technicalDetail: normalizeRawEvent(event)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (type === 'codex.command.started' || type === 'codex.command.completed') {
|
|
145
|
+
return summarizeCommandActivity(event, locale);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (type === 'codex.agent.message') {
|
|
149
|
+
const title = cleanVisibleText(event.detail?.text || event.title || '');
|
|
150
|
+
if (!title || looksTechnical(title)) {
|
|
151
|
+
return technicalOnly(event, locale);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
kind: 'activity',
|
|
155
|
+
visible: true,
|
|
156
|
+
title,
|
|
157
|
+
status: event.status === 'failed' ? 'failed' : 'completed',
|
|
158
|
+
technicalDetail: normalizeRawEvent(event)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (type === 'codex.agent.result') {
|
|
163
|
+
return {
|
|
164
|
+
kind: 'activity',
|
|
165
|
+
visible: true,
|
|
166
|
+
title: textFor(locale, 'Codex 已整理出本轮结果,正在生成报告。', 'Codex prepared this task result and is generating the report.'),
|
|
167
|
+
status: 'completed',
|
|
168
|
+
technicalDetail: normalizeRawEvent(event)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (isTechnicalEventType(type)) {
|
|
173
|
+
return technicalOnly(event, locale);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const title = cleanVisibleText(event.title || '');
|
|
177
|
+
if (!title || looksTechnical(title)) {
|
|
178
|
+
return technicalOnly(event, locale);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
kind: 'activity',
|
|
183
|
+
visible: true,
|
|
184
|
+
title,
|
|
185
|
+
status: event.status || 'running',
|
|
186
|
+
technicalDetail: normalizeRawEvent(event)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function mapCodexSessionEvent(event, locale) {
|
|
191
|
+
const method = String(event.detail?.method || event.title || '');
|
|
192
|
+
const params = event.detail?.params || {};
|
|
193
|
+
|
|
194
|
+
if (isContextCompactionEvent(event)) {
|
|
195
|
+
return formatContextCompactionCheckpoint(event, locale);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (event.type === 'codex.session.request') {
|
|
199
|
+
return {
|
|
200
|
+
kind: 'activity',
|
|
201
|
+
visible: true,
|
|
202
|
+
title: formatCodexApprovalRequest(method, locale),
|
|
203
|
+
status: 'running',
|
|
204
|
+
technicalDetail: normalizeRawEvent(event)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (method === 'thread/started') {
|
|
209
|
+
return {
|
|
210
|
+
kind: 'activity',
|
|
211
|
+
visible: true,
|
|
212
|
+
title: textFor(locale, '本地 Codex session 已创建。', 'Local Codex session created.'),
|
|
213
|
+
status: 'completed',
|
|
214
|
+
technicalDetail: normalizeRawEvent(event)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (method === 'turn/started') {
|
|
218
|
+
return {
|
|
219
|
+
kind: 'activity',
|
|
220
|
+
visible: true,
|
|
221
|
+
title: textFor(locale, 'Codex 开始处理这轮请求。', 'Codex started processing this request.'),
|
|
222
|
+
status: 'running',
|
|
223
|
+
technicalDetail: normalizeRawEvent(event)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (method === 'turn/completed') {
|
|
227
|
+
return {
|
|
228
|
+
kind: 'activity',
|
|
229
|
+
visible: true,
|
|
230
|
+
title: textFor(locale, 'Codex 完成本地处理,正在准备同步改动。', 'Codex finished local processing and is preparing to sync changes.'),
|
|
231
|
+
status: 'completed',
|
|
232
|
+
technicalDetail: normalizeRawEvent(event)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (method === 'turn/plan/updated') {
|
|
236
|
+
return {
|
|
237
|
+
kind: 'activity',
|
|
238
|
+
visible: true,
|
|
239
|
+
title: formatPlanUpdateTitle(params, locale),
|
|
240
|
+
status: 'running',
|
|
241
|
+
detail: formatPlanUpdateDetail(params, locale),
|
|
242
|
+
technicalDetail: normalizeRawEvent(event)
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (method === 'turn/diff/updated') {
|
|
246
|
+
const changedFiles = extractDiffFileCount(params.diff);
|
|
247
|
+
return {
|
|
248
|
+
kind: 'activity',
|
|
249
|
+
visible: true,
|
|
250
|
+
title: changedFiles
|
|
251
|
+
? textFor(locale, `Codex 更新了本地文件差异:${changedFiles} 个文件。`, `Codex updated local file diffs: ${changedFiles} file(s).`)
|
|
252
|
+
: textFor(locale, 'Codex 更新了本地文件差异。', 'Codex updated local file diffs.'),
|
|
253
|
+
status: 'running',
|
|
254
|
+
technicalDetail: normalizeRawEvent(event)
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (method === 'item/started' || method === 'item/completed') {
|
|
258
|
+
return mapThreadItemEvent(params.item, method === 'item/started', event, locale);
|
|
259
|
+
}
|
|
260
|
+
if (method === 'item/agentMessage/delta' || method === 'item/reasoning/summaryTextDelta') {
|
|
261
|
+
const title = cleanStreamDeltaText(params.delta || '');
|
|
262
|
+
if (!title.length) {
|
|
263
|
+
return technicalOnly(event, locale);
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
kind: 'stream',
|
|
267
|
+
visible: true,
|
|
268
|
+
title,
|
|
269
|
+
status: 'running',
|
|
270
|
+
streamKey: getCodexStreamKey(method, params),
|
|
271
|
+
streamRole: method === 'item/agentMessage/delta' ? 'assistant' : 'reasoning',
|
|
272
|
+
appendText: true
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (method === 'item/reasoning/summaryPartAdded') {
|
|
276
|
+
return technicalOnly(event, locale);
|
|
277
|
+
}
|
|
278
|
+
if (method === 'item/reasoning/textDelta') {
|
|
279
|
+
return technicalOnly(event, locale);
|
|
280
|
+
}
|
|
281
|
+
if (method === 'item/fileChange/patchUpdated') {
|
|
282
|
+
const files = getPatchChangeFiles(params.changes);
|
|
283
|
+
return {
|
|
284
|
+
kind: 'activity',
|
|
285
|
+
visible: true,
|
|
286
|
+
title: files.length
|
|
287
|
+
? textFor(locale, `Codex 正在修改本地文件:${formatFilesInline(files, locale)}。`, `Codex is editing local files: ${formatFilesInline(files, locale)}.`)
|
|
288
|
+
: textFor(locale, 'Codex 正在修改本地文件。', 'Codex is editing local files.'),
|
|
289
|
+
status: 'running',
|
|
290
|
+
technicalDetail: normalizeRawEvent(event)
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (method === 'item/fileChange/outputDelta') {
|
|
294
|
+
return {
|
|
295
|
+
kind: 'activity',
|
|
296
|
+
visible: true,
|
|
297
|
+
title: textFor(locale, 'Codex 正在写入本地文件。', 'Codex is writing local files.'),
|
|
298
|
+
status: 'running',
|
|
299
|
+
technicalDetail: normalizeRawEvent(event)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (method === 'model/rerouted') {
|
|
303
|
+
return {
|
|
304
|
+
kind: 'activity',
|
|
305
|
+
visible: true,
|
|
306
|
+
title: textFor(locale, 'Codex 已切换到可用模型继续运行。', 'Codex switched to an available model and continued.'),
|
|
307
|
+
status: 'running',
|
|
308
|
+
technicalDetail: normalizeRawEvent(event)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (method === 'warning' || method === 'guardianWarning' || method === 'configWarning') {
|
|
312
|
+
const title = cleanVisibleText(params.message || params.warning || '');
|
|
313
|
+
return {
|
|
314
|
+
kind: 'activity',
|
|
315
|
+
visible: true,
|
|
316
|
+
title: title || textFor(locale, 'Codex 返回了一条运行提示。', 'Codex returned a runtime notice.'),
|
|
317
|
+
status: 'running',
|
|
318
|
+
technicalDetail: normalizeRawEvent(event)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return technicalOnly(event, locale);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isContextCompactionEvent(event = {}) {
|
|
326
|
+
const type = String(event.type || '');
|
|
327
|
+
const method = String(event.detail?.method || event.title || '');
|
|
328
|
+
const label = `${type} ${method}`;
|
|
329
|
+
return /(compact|compaction|compacted)/i.test(label)
|
|
330
|
+
&& /(context|thread|turn|conversation|codex)/i.test(label);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function formatContextCompactionCheckpoint(event = {}, locale = 'zh') {
|
|
334
|
+
const method = String(event.detail?.method || event.title || '');
|
|
335
|
+
const running = event.status === 'running' || /(started|starting|begin|prepar)/i.test(method);
|
|
336
|
+
return {
|
|
337
|
+
kind: 'checkpoint',
|
|
338
|
+
visible: true,
|
|
339
|
+
title: running
|
|
340
|
+
? textFor(locale, '正在压缩上下文,Codex 会继续处理。', 'Compacting context; Codex will continue.')
|
|
341
|
+
: textFor(locale, '上下文已压缩,Codex 继续处理。', 'Context compacted; Codex continued.'),
|
|
342
|
+
status: running ? 'running' : 'completed',
|
|
343
|
+
technicalDetail: normalizeRawEvent(event)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function mapThreadItemEvent(item = {}, started, event, locale) {
|
|
348
|
+
const status = started ? 'running' : (item.status === 'failed' ? 'failed' : 'completed');
|
|
349
|
+
if (item.type === 'agentMessage') {
|
|
350
|
+
const title = cleanVisibleText(item.text || '');
|
|
351
|
+
if (title) {
|
|
352
|
+
return {
|
|
353
|
+
kind: 'stream',
|
|
354
|
+
visible: true,
|
|
355
|
+
title,
|
|
356
|
+
status,
|
|
357
|
+
streamKey: getItemStreamKey('agent', item),
|
|
358
|
+
streamRole: 'assistant',
|
|
359
|
+
replaceText: true
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (item.type === 'reasoning') {
|
|
364
|
+
const summary = normalizeStringList(item.summary).at(-1);
|
|
365
|
+
if (!started && summary) {
|
|
366
|
+
return {
|
|
367
|
+
kind: 'stream',
|
|
368
|
+
visible: true,
|
|
369
|
+
title: summary,
|
|
370
|
+
status,
|
|
371
|
+
streamKey: getItemStreamKey('reasoning', item),
|
|
372
|
+
streamRole: 'reasoning',
|
|
373
|
+
replaceText: true
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
kind: 'activity',
|
|
378
|
+
visible: true,
|
|
379
|
+
title: started ? textFor(locale, 'Codex 正在分析。', 'Codex is analyzing.') : textFor(locale, 'Codex 完成了一段分析。', 'Codex completed an analysis step.'),
|
|
380
|
+
status,
|
|
381
|
+
technicalDetail: normalizeRawEvent(event)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (item.type === 'plan') {
|
|
385
|
+
const title = cleanVisibleText(item.text || '');
|
|
386
|
+
return {
|
|
387
|
+
kind: 'activity',
|
|
388
|
+
visible: true,
|
|
389
|
+
title: title || textFor(locale, 'Codex 更新了计划。', 'Codex updated its plan.'),
|
|
390
|
+
status,
|
|
391
|
+
technicalDetail: normalizeRawEvent(event)
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (item.type === 'commandExecution') {
|
|
395
|
+
return summarizeCommandActivity({
|
|
396
|
+
type: started ? 'codex.command.started' : 'codex.command.completed',
|
|
397
|
+
status,
|
|
398
|
+
detail: {
|
|
399
|
+
command: item.command,
|
|
400
|
+
output: item.aggregatedOutput,
|
|
401
|
+
exitCode: item.exitCode
|
|
402
|
+
}
|
|
403
|
+
}, locale);
|
|
404
|
+
}
|
|
405
|
+
if (item.type === 'fileChange') {
|
|
406
|
+
const files = getPatchChangeFiles(item.changes);
|
|
407
|
+
return {
|
|
408
|
+
kind: 'activity',
|
|
409
|
+
visible: true,
|
|
410
|
+
title: files.length
|
|
411
|
+
? (started
|
|
412
|
+
? textFor(locale, `Codex 正在修改本地文件:${formatFilesInline(files, locale)}。`, `Codex is editing local files: ${formatFilesInline(files, locale)}.`)
|
|
413
|
+
: textFor(locale, `Codex 已修改本地文件:${formatFilesInline(files, locale)}。`, `Codex edited local files: ${formatFilesInline(files, locale)}.`))
|
|
414
|
+
: (started
|
|
415
|
+
? textFor(locale, 'Codex 正在修改本地文件。', 'Codex is editing local files.')
|
|
416
|
+
: textFor(locale, 'Codex 已完成本地文件修改。', 'Codex finished local file edits.')),
|
|
417
|
+
status,
|
|
418
|
+
technicalDetail: normalizeRawEvent(event)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
if (item.type === 'mcpToolCall' || item.type === 'dynamicToolCall') {
|
|
422
|
+
return {
|
|
423
|
+
kind: 'activity',
|
|
424
|
+
visible: true,
|
|
425
|
+
title: started
|
|
426
|
+
? textFor(locale, `正在使用工具:${cleanVisibleText(item.tool || '本地工具')}。`, `Using tool: ${cleanVisibleText(item.tool || 'local tool')}.`)
|
|
427
|
+
: textFor(locale, `已使用工具:${cleanVisibleText(item.tool || '本地工具')}。`, `Used tool: ${cleanVisibleText(item.tool || 'local tool')}.`),
|
|
428
|
+
status,
|
|
429
|
+
technicalDetail: normalizeRawEvent(event)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return technicalOnly(event, locale);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function formatCodexApprovalRequest(method, locale) {
|
|
437
|
+
if (/fileChange\/requestApproval/.test(method)) {
|
|
438
|
+
return textFor(locale, 'Codex 请求写入本地文件,正在按当前模式处理。', 'Codex requested local file writes; handling according to the current mode.');
|
|
439
|
+
}
|
|
440
|
+
if (/commandExecution\/requestApproval/.test(method)) {
|
|
441
|
+
return textFor(locale, 'Codex 请求运行本地命令,正在按当前模式处理。', 'Codex requested a local command; handling according to the current mode.');
|
|
442
|
+
}
|
|
443
|
+
return textFor(locale, 'Codex 请求继续操作,正在按当前模式处理。', 'Codex requested to continue; handling according to the current mode.');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function getCodexStreamKey(method, params = {}) {
|
|
447
|
+
const itemId = cleanVisibleText(params.itemId || params.item?.id || '');
|
|
448
|
+
if (method === 'item/agentMessage/delta') {
|
|
449
|
+
return `agent:${itemId || 'current'}`;
|
|
450
|
+
}
|
|
451
|
+
if (method === 'item/reasoning/summaryTextDelta') {
|
|
452
|
+
return `reasoning:${itemId || 'current'}`;
|
|
453
|
+
}
|
|
454
|
+
return `${method}:${itemId || 'current'}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getItemStreamKey(prefix, item = {}) {
|
|
458
|
+
const itemId = cleanVisibleText(item.id || item.itemId || '');
|
|
459
|
+
return `${prefix}:${itemId || 'current'}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function formatPlanUpdateTitle(params = {}, locale = 'zh') {
|
|
463
|
+
const steps = Array.isArray(params.plan) ? params.plan : [];
|
|
464
|
+
const active = steps.find(step => step.status === 'in_progress') || steps.find(step => step.status === 'pending');
|
|
465
|
+
const text = cleanVisibleText(active?.step || params.explanation || '');
|
|
466
|
+
return text
|
|
467
|
+
? textFor(locale, `Codex 计划更新:${text}`, `Codex plan update: ${text}`)
|
|
468
|
+
: textFor(locale, 'Codex 更新了执行计划。', 'Codex updated its plan.');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function formatPlanUpdateDetail(params = {}, locale = 'zh') {
|
|
472
|
+
const steps = Array.isArray(params.plan) ? params.plan : [];
|
|
473
|
+
if (!steps.length) {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
const labels = locale === 'en'
|
|
477
|
+
? { pending: 'pending', in_progress: 'in progress', completed: 'completed' }
|
|
478
|
+
: { pending: '待处理', in_progress: '进行中', completed: '已完成' };
|
|
479
|
+
return {
|
|
480
|
+
[textFor(locale, '计划', 'Plan')]: steps
|
|
481
|
+
.map(step => `${labels[step.status] || step.status || '状态'}:${cleanVisibleText(step.step || '')}`)
|
|
482
|
+
.filter(Boolean)
|
|
483
|
+
.join('\n')
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function translateRawError(message, context = {}) {
|
|
488
|
+
const locale = normalizeLocale(context);
|
|
489
|
+
const text = String(message || '');
|
|
490
|
+
if (/Mode must be "confirm" or "auto"/i.test(text)) {
|
|
491
|
+
return {
|
|
492
|
+
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.')
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (/Agent returned invalid JSON/i.test(text)) {
|
|
497
|
+
return {
|
|
498
|
+
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.')
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (/Could not parse Codex output/i.test(text)) {
|
|
503
|
+
return {
|
|
504
|
+
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.')
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (/timed out/i.test(text)) {
|
|
509
|
+
return {
|
|
510
|
+
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.')
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
if (/output limit exceeded/i.test(text)) {
|
|
515
|
+
return {
|
|
516
|
+
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.')
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
if (/codex_not_found|Codex CLI was not found|ENOENT/i.test(text)) {
|
|
521
|
+
return {
|
|
522
|
+
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.')
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (/unsupported[_ ]parameter/i.test(text) && /reasoning\.summary|summary/i.test(text)) {
|
|
527
|
+
return {
|
|
528
|
+
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.')
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (/quota|kQuotaBytes|QUOTA_BYTES/i.test(text)) {
|
|
533
|
+
return {
|
|
534
|
+
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.')
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (/checkpoint/i.test(text)) {
|
|
539
|
+
return {
|
|
540
|
+
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.')
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
if (/changed while Codex was working|任务执行期间被你或协作者改过/i.test(text)) {
|
|
545
|
+
return {
|
|
546
|
+
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.')
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
conclusion: context.mode === 'ask'
|
|
553
|
+
? textFor(locale, '这轮只问不改没有完成:本地 Codex 没有正常完成,因此没有生成最终说明。', 'This Ask run did not complete: local Codex did not finish normally, so no final answer was generated.')
|
|
554
|
+
: 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.')
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function buildHumanCompletionReport(input = {}) {
|
|
560
|
+
const locale = normalizeLocale(input);
|
|
561
|
+
const applyResults = normalizeApplyResults(input.applyResults);
|
|
562
|
+
const appliedCount = countApplyResultEntries(applyResults, 'applied');
|
|
563
|
+
const skippedCount = countApplyResultEntries(applyResults, 'skipped');
|
|
564
|
+
const translatedError = input.errorMessage ? translateRawError(input.errorMessage, { mode: input.mode, locale }) : null;
|
|
565
|
+
const userReport = normalizeUserReport(input.userReport);
|
|
566
|
+
const report = userReport || buildFallbackReport(input, {
|
|
567
|
+
appliedCount,
|
|
568
|
+
skippedCount,
|
|
569
|
+
translatedError
|
|
570
|
+
}, locale);
|
|
571
|
+
|
|
572
|
+
if (!report.writeResult && (applyResults.length || input.includeWriteResult)) {
|
|
573
|
+
report.writeResult = formatWriteResult(appliedCount, skippedCount, locale);
|
|
574
|
+
}
|
|
575
|
+
if (!report.undo && input.undoCount !== undefined) {
|
|
576
|
+
report.undo = input.undoCount
|
|
577
|
+
? textFor(locale, `可撤销本轮 ${input.undoCount} 项写入`, `this run has ${input.undoCount} reversible write${Number(input.undoCount) === 1 ? '' : 's'}`)
|
|
578
|
+
: textFor(locale, '本轮没有可撤销的写入', 'this run has no reversible writes');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const failed = input.status === 'failed' || input.status === 'blocked';
|
|
582
|
+
return {
|
|
583
|
+
title: textFor(locale, '本轮完成报告', 'Task report'),
|
|
584
|
+
status: failed ? 'failed' : 'completed',
|
|
585
|
+
text: formatHumanReport(report, locale)
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function formatHumanReport(report = {}, locale = 'zh') {
|
|
590
|
+
const sections = [];
|
|
591
|
+
const conclusion = cleanVisibleMarkdownText(report.conclusion || '');
|
|
592
|
+
if (conclusion) {
|
|
593
|
+
sections.push(textFor(locale, `结论:${conclusion}`, `Conclusion: ${conclusion}`));
|
|
594
|
+
}
|
|
595
|
+
addListSection(sections, textFor(locale, '检查范围', 'Checked'), report.checked, locale);
|
|
596
|
+
addListSection(sections, textFor(locale, '发现', 'Findings'), report.findings, locale);
|
|
597
|
+
addListSection(sections, textFor(locale, '计划修改', 'Planned changes'), report.plannedChanges, locale);
|
|
598
|
+
addListSection(sections, textFor(locale, '修改', 'Changes'), report.appliedChanges, locale);
|
|
599
|
+
const unchangedReason = localizeVisibleReason(report.unchangedReason || '', locale);
|
|
600
|
+
if (unchangedReason) {
|
|
601
|
+
sections.push(textFor(locale, `未修改原因:${unchangedReason}`, `Why nothing changed: ${unchangedReason}`));
|
|
602
|
+
}
|
|
603
|
+
const writeResult = cleanVisibleText(report.writeResult || '');
|
|
604
|
+
if (writeResult) {
|
|
605
|
+
sections.push(textFor(locale, `写入结果:${writeResult}`, `Write result: ${writeResult}`));
|
|
606
|
+
}
|
|
607
|
+
addListSection(sections, textFor(locale, '跳过原因', 'Skipped'), report.skippedChanges, locale);
|
|
608
|
+
const undo = cleanVisibleText(report.undo || '');
|
|
609
|
+
if (undo) {
|
|
610
|
+
sections.push(textFor(locale, `可撤销:${undo}`, `Undo: ${undo}`));
|
|
611
|
+
}
|
|
612
|
+
const nextStep = cleanVisibleText(report.nextStep || '');
|
|
613
|
+
if (nextStep) {
|
|
614
|
+
sections.push(textFor(locale, `下一步:${nextStep}`, `Next: ${nextStep}`));
|
|
615
|
+
}
|
|
616
|
+
return sections.join('\n\n');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function buildFallbackReport(input, counts, locale = 'zh') {
|
|
620
|
+
const operations = Array.isArray(input.operations) ? input.operations : [];
|
|
621
|
+
const appliedOperations = collectAppliedOperations(input.applyResults);
|
|
622
|
+
const affectedFiles = collectAffectedFiles(operations, input.summary, input.applyResults);
|
|
623
|
+
const noWrites = operations.length === 0 && appliedOperations.length === 0;
|
|
624
|
+
const conclusion = counts.translatedError?.conclusion
|
|
625
|
+
|| input.conclusion
|
|
626
|
+
|| input.notes
|
|
627
|
+
|| (noWrites
|
|
628
|
+
? textFor(locale, '这轮任务已完成,没有写入 Overleaf 文件。', 'This task completed without writing Overleaf files.')
|
|
629
|
+
: textFor(locale, '这轮任务已完成。', 'This task completed.'));
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
conclusion,
|
|
633
|
+
checked: normalizeStringList(input.checked || input.userReport?.checked),
|
|
634
|
+
findings: normalizeStringList(input.findings),
|
|
635
|
+
plannedChanges: counts.appliedCount ? [] : operations.map(operation => formatOperationLine(operation, locale)),
|
|
636
|
+
appliedChanges: appliedOperations.map(operation => formatOperationLine(operation, locale)),
|
|
637
|
+
skippedChanges: collectSkippedOperations(input.applyResults).map(item => formatSkippedOperationLine(item, locale)),
|
|
638
|
+
unchangedReason: input.unchangedReason || (noWrites ? inferUnchangedReason(input, locale) : ''),
|
|
639
|
+
writeResult: input.writeResult || (counts.appliedCount || counts.skippedCount
|
|
640
|
+
? formatWriteResult(counts.appliedCount, counts.skippedCount, locale)
|
|
641
|
+
: ''),
|
|
642
|
+
undo: input.undo,
|
|
643
|
+
nextStep: input.nextStep || counts.translatedError?.nextStep || formatFallbackNextStep(input, counts.skippedCount, affectedFiles, locale)
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function formatWriteResult(appliedCount, skippedCount, locale = 'zh') {
|
|
648
|
+
return textFor(
|
|
649
|
+
locale,
|
|
650
|
+
`已写入 ${appliedCount} 项,跳过 ${skippedCount} 项`,
|
|
651
|
+
`wrote ${appliedCount} item${Number(appliedCount) === 1 ? '' : 's'}, skipped ${skippedCount} item${Number(skippedCount) === 1 ? '' : 's'}`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function inferUnchangedReason(input, locale = 'zh') {
|
|
656
|
+
if (input.mode === 'ask' || input.status === '只问不改') {
|
|
657
|
+
return textFor(locale, '这轮是只问不改。', 'This run was Ask mode.');
|
|
658
|
+
}
|
|
659
|
+
if (input.status === 'rejected') {
|
|
660
|
+
return textFor(locale, '你取消了这轮修改。', 'You cancelled this change.');
|
|
661
|
+
}
|
|
662
|
+
return '';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function formatFallbackNextStep(input, skippedCount, affectedFiles, locale = 'zh') {
|
|
666
|
+
if (skippedCount) {
|
|
667
|
+
return textFor(locale, '请查看本轮报告中的跳过项,处理后可以重试。', 'Review the skipped items in this report, then retry after resolving them.');
|
|
668
|
+
}
|
|
669
|
+
if (input.status === 'blocked' || input.status === 'failed') {
|
|
670
|
+
return textFor(locale, '请处理上面的原因后重试。', 'Resolve the issue above, then retry.');
|
|
671
|
+
}
|
|
672
|
+
if (!affectedFiles.length) {
|
|
673
|
+
return textFor(locale, '可以继续追问,或加入更多 @context 后再检查。', 'You can continue asking, or add more @context and run another check.');
|
|
674
|
+
}
|
|
675
|
+
return textFor(locale, '请在 Overleaf 中查看这些文件的留痕修改。', 'Review these tracked changes in Overleaf.');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function normalizeUserReport(report) {
|
|
679
|
+
if (!report || typeof report !== 'object') {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
conclusion: cleanVisibleMarkdownText(report.conclusion || ''),
|
|
684
|
+
checked: normalizeStringList(report.checked),
|
|
685
|
+
findings: normalizeStringList(report.findings),
|
|
686
|
+
plannedChanges: normalizeStringList(report.plannedChanges),
|
|
687
|
+
appliedChanges: normalizeStringList(report.appliedChanges),
|
|
688
|
+
skippedChanges: normalizeStringList(report.skippedChanges),
|
|
689
|
+
unchangedReason: cleanVisibleText(report.unchangedReason || ''),
|
|
690
|
+
nextStep: cleanVisibleText(report.nextStep || '')
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function technicalOnly(event, locale = 'zh') {
|
|
695
|
+
return {
|
|
696
|
+
kind: 'technical',
|
|
697
|
+
visible: false,
|
|
698
|
+
title: textFor(locale, '技术详情', 'Technical details'),
|
|
699
|
+
status: event?.status || 'info',
|
|
700
|
+
detail: normalizeRawEvent(event)
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function normalizeRawEvent(event = {}) {
|
|
705
|
+
return {
|
|
706
|
+
type: event.type || 'unknown',
|
|
707
|
+
title: event.title || '',
|
|
708
|
+
status: event.status || '',
|
|
709
|
+
timestamp: event.timestamp || '',
|
|
710
|
+
detail: event.detail || {}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function isTechnicalEventType(type) {
|
|
715
|
+
return TECHNICAL_EVENT_PATTERNS.some(pattern => pattern.test(type));
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function summarizeCommandActivity(event, locale = 'zh') {
|
|
719
|
+
const type = String(event.type || '');
|
|
720
|
+
const command = String(event.detail?.command || '');
|
|
721
|
+
const output = String(event.detail?.output || '');
|
|
722
|
+
const commandKind = classifyCommand(command);
|
|
723
|
+
const running = type === 'codex.command.started';
|
|
724
|
+
const failed = event.status === 'failed' || Number(event.detail?.exitCode) > 0;
|
|
725
|
+
|
|
726
|
+
if (running) {
|
|
727
|
+
return {
|
|
728
|
+
kind: 'activity',
|
|
729
|
+
visible: true,
|
|
730
|
+
title: formatCommandStartedTitle(commandKind, command, locale),
|
|
731
|
+
status: 'running',
|
|
732
|
+
detail: formatCommandPublicDetail(commandKind, command, locale),
|
|
733
|
+
technicalDetail: normalizeRawEvent(event)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
kind: 'activity',
|
|
739
|
+
visible: true,
|
|
740
|
+
title: formatCommandCompletedTitle(commandKind, output, failed, locale),
|
|
741
|
+
status: failed ? 'failed' : 'completed',
|
|
742
|
+
detail: formatCommandPublicDetail(commandKind, command, locale),
|
|
743
|
+
technicalDetail: normalizeRawEvent(event)
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function classifyCommand(command) {
|
|
748
|
+
const text = String(command || '').trim();
|
|
749
|
+
if (/^(rg|grep)\b/i.test(text) || /\b(rg|grep)\b/i.test(text)) {
|
|
750
|
+
return 'search';
|
|
751
|
+
}
|
|
752
|
+
if (/^(cat|sed|nl|head|tail|awk)\b/i.test(text)) {
|
|
753
|
+
return 'read';
|
|
754
|
+
}
|
|
755
|
+
if (/\b(latexmk|pdflatex|xelatex|lualatex|bibtex|biber)\b/i.test(text)) {
|
|
756
|
+
return 'compile';
|
|
757
|
+
}
|
|
758
|
+
if (/^(ls|find)\b/i.test(text)) {
|
|
759
|
+
return 'list';
|
|
760
|
+
}
|
|
761
|
+
return 'check';
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function formatCommandStartedTitle(kind, command, locale = 'zh') {
|
|
765
|
+
if (kind === 'search') {
|
|
766
|
+
const query = extractSearchQuery(command);
|
|
767
|
+
return query ? textFor(locale, `正在搜索项目内容:${query}`, `Searching project content: ${query}`) : textFor(locale, '正在搜索项目内容。', 'Searching project content.');
|
|
768
|
+
}
|
|
769
|
+
if (kind === 'read') {
|
|
770
|
+
return textFor(locale, '正在读取相关文件内容。', 'Reading related file content.');
|
|
771
|
+
}
|
|
772
|
+
if (kind === 'compile') {
|
|
773
|
+
return textFor(locale, '正在运行 LaTeX 相关检查。', 'Running LaTeX-related checks.');
|
|
774
|
+
}
|
|
775
|
+
if (kind === 'list') {
|
|
776
|
+
return textFor(locale, '正在查看项目文件结构。', 'Inspecting project file structure.');
|
|
777
|
+
}
|
|
778
|
+
return textFor(locale, '正在执行一次本地检查。', 'Running a local check.');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function formatCommandCompletedTitle(kind, output, failed, locale = 'zh') {
|
|
782
|
+
if (failed) {
|
|
783
|
+
if (kind === 'search') {
|
|
784
|
+
return textFor(locale, '搜索没有正常完成。', 'Search did not finish normally.');
|
|
785
|
+
}
|
|
786
|
+
if (kind === 'compile') {
|
|
787
|
+
return textFor(locale, 'LaTeX 检查没有正常完成。', 'LaTeX check did not finish normally.');
|
|
788
|
+
}
|
|
789
|
+
return textFor(locale, '本地检查没有正常完成。', 'Local check did not finish normally.');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (kind === 'search') {
|
|
793
|
+
const count = countMeaningfulLines(output);
|
|
794
|
+
return count ? textFor(locale, `搜索完成,找到 ${count} 条相关线索。`, `Search complete: found ${count} relevant line(s).`) : textFor(locale, '搜索完成,没有找到明显相关线索。', 'Search complete: no clearly relevant lines found.');
|
|
795
|
+
}
|
|
796
|
+
if (kind === 'read') {
|
|
797
|
+
return textFor(locale, '相关文件内容已读取。', 'Related file content has been read.');
|
|
798
|
+
}
|
|
799
|
+
if (kind === 'compile') {
|
|
800
|
+
return textFor(locale, 'LaTeX 相关检查已完成。', 'LaTeX-related checks completed.');
|
|
801
|
+
}
|
|
802
|
+
if (kind === 'list') {
|
|
803
|
+
return textFor(locale, '项目文件结构已查看。', 'Project file structure inspected.');
|
|
804
|
+
}
|
|
805
|
+
return textFor(locale, '本地检查已完成。', 'Local check completed.');
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function formatCommandPublicDetail(kind, command, locale = 'zh') {
|
|
809
|
+
if (kind !== 'search') {
|
|
810
|
+
return undefined;
|
|
811
|
+
}
|
|
812
|
+
const query = extractSearchQuery(command);
|
|
813
|
+
return query ? { [textFor(locale, '搜索目标', 'Search target')]: query } : undefined;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function extractSearchQuery(command) {
|
|
817
|
+
const text = String(command || '').trim();
|
|
818
|
+
const match = text.match(/\b(?:rg|grep)\s+(?:-[^\s]+\s+)*(['"]?)([^'"\s][^'"]*?)\1(?:\s|$)/i);
|
|
819
|
+
if (!match) {
|
|
820
|
+
return '';
|
|
821
|
+
}
|
|
822
|
+
return cleanVisibleText(match[2])
|
|
823
|
+
.replace(/[\\{}]/g, '')
|
|
824
|
+
.slice(0, 40);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function countMeaningfulLines(output) {
|
|
828
|
+
return String(output || '')
|
|
829
|
+
.split(/\r?\n/)
|
|
830
|
+
.map(line => line.trim())
|
|
831
|
+
.filter(Boolean)
|
|
832
|
+
.length;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function extractDiffFileCount(diff) {
|
|
836
|
+
const files = new Set();
|
|
837
|
+
for (const line of String(diff || '').split(/\r?\n/)) {
|
|
838
|
+
const match = line.match(/^diff --git a\/(.+?) b\//);
|
|
839
|
+
if (match?.[1]) {
|
|
840
|
+
files.add(match[1]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return files.size;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function getPatchChangeFiles(changes = []) {
|
|
847
|
+
const files = [];
|
|
848
|
+
const seen = new Set();
|
|
849
|
+
for (const change of Array.isArray(changes) ? changes : []) {
|
|
850
|
+
const filePath = cleanVisibleText(change?.path || '');
|
|
851
|
+
if (filePath && !seen.has(filePath)) {
|
|
852
|
+
seen.add(filePath);
|
|
853
|
+
files.push(filePath);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return files;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function formatFilesInline(files = [], locale = 'zh') {
|
|
860
|
+
const values = normalizeStringList(files);
|
|
861
|
+
if (!values.length) {
|
|
862
|
+
return textFor(locale, '没有文件', 'no files');
|
|
863
|
+
}
|
|
864
|
+
if (values.length <= 3) {
|
|
865
|
+
return values.join(locale === 'en' ? ', ' : '、');
|
|
866
|
+
}
|
|
867
|
+
return textFor(
|
|
868
|
+
locale,
|
|
869
|
+
`${values.slice(0, 3).join('、')} 等 ${values.length} 个文件`,
|
|
870
|
+
`${values.slice(0, 3).join(', ')} and ${values.length - 3} more`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function formatCompactNumber(value) {
|
|
875
|
+
const number = Number(value) || 0;
|
|
876
|
+
if (number >= 1000000) {
|
|
877
|
+
return `${Math.round(number / 100000) / 10}M`;
|
|
878
|
+
}
|
|
879
|
+
if (number >= 1000) {
|
|
880
|
+
return `${Math.round(number / 100) / 10}k`;
|
|
881
|
+
}
|
|
882
|
+
return String(number);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function looksTechnical(text) {
|
|
886
|
+
const value = String(text || '').trim();
|
|
887
|
+
return /^(\{|\[)/.test(value)
|
|
888
|
+
|| /stdout|stderr|schema|JSON|exit[_ ]?code|CODEX_OVERLEAF_EVENT/i.test(value)
|
|
889
|
+
|| /^(codex|agent|native)\.[a-z0-9_.-]+/i.test(value)
|
|
890
|
+
|| /^[a-z]+(?:\/[a-zA-Z0-9]+)+/.test(value);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function cleanVisibleText(text) {
|
|
894
|
+
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function cleanStreamDeltaText(text) {
|
|
898
|
+
return String(text || '')
|
|
899
|
+
.replace(/\r\n?/g, '\n')
|
|
900
|
+
.replace(/\u00a0/g, ' ');
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function cleanVisibleMarkdownText(text) {
|
|
904
|
+
return String(text || '')
|
|
905
|
+
.replace(/\r\n?/g, '\n')
|
|
906
|
+
.replace(/[^\S\n]+/g, ' ')
|
|
907
|
+
.split('\n')
|
|
908
|
+
.map(line => line.trim())
|
|
909
|
+
.join('\n')
|
|
910
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
911
|
+
.trim();
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function addListSection(sections, label, values, locale = 'zh') {
|
|
915
|
+
const items = normalizeStringList(values);
|
|
916
|
+
if (!items.length) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
sections.push(`${label}${locale === 'en' ? ':' : ':'}\n${items.map(item => `- ${item}`).join('\n')}`);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function normalizeStringList(value) {
|
|
923
|
+
const values = Array.isArray(value) ? value : (value ? [value] : []);
|
|
924
|
+
return values
|
|
925
|
+
.map(item => cleanVisibleText(item))
|
|
926
|
+
.filter(Boolean);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function normalizeApplyResults(applyResults) {
|
|
930
|
+
if (!applyResults) {
|
|
931
|
+
return [];
|
|
932
|
+
}
|
|
933
|
+
return Array.isArray(applyResults) ? applyResults.filter(Boolean) : [applyResults];
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function getApplyResultEntries(result, key) {
|
|
937
|
+
const entries = result?.[key];
|
|
938
|
+
return Array.isArray(entries) ? entries : [];
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function countApplyResultEntries(applyResults, key) {
|
|
942
|
+
return normalizeApplyResults(applyResults)
|
|
943
|
+
.reduce((sum, result) => sum + getApplyResultEntries(result, key).length, 0);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function collectAppliedOperations(applyResults) {
|
|
947
|
+
const operations = [];
|
|
948
|
+
for (const result of normalizeApplyResults(applyResults)) {
|
|
949
|
+
for (const item of getApplyResultEntries(result, 'applied')) {
|
|
950
|
+
if (item?.operation) {
|
|
951
|
+
operations.push(item.operation);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return operations;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function collectSkippedOperations(applyResults) {
|
|
959
|
+
const operations = [];
|
|
960
|
+
for (const result of normalizeApplyResults(applyResults)) {
|
|
961
|
+
for (const item of getApplyResultEntries(result, 'skipped')) {
|
|
962
|
+
if (item?.operation) {
|
|
963
|
+
operations.push({
|
|
964
|
+
operation: item.operation,
|
|
965
|
+
result: item.result || {}
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return operations;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function collectAffectedFiles(operations = [], summary, applyResults = []) {
|
|
974
|
+
const files = [];
|
|
975
|
+
const seen = new Set();
|
|
976
|
+
const add = filePath => {
|
|
977
|
+
if (!filePath || seen.has(filePath)) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
seen.add(filePath);
|
|
981
|
+
files.push(filePath);
|
|
982
|
+
};
|
|
983
|
+
for (const filePath of summary?.affectedFiles || []) {
|
|
984
|
+
add(filePath);
|
|
985
|
+
}
|
|
986
|
+
for (const operation of operations || []) {
|
|
987
|
+
add(operation?.path || operation?.from || operation?.to);
|
|
988
|
+
}
|
|
989
|
+
for (const result of normalizeApplyResults(applyResults)) {
|
|
990
|
+
const entries = [
|
|
991
|
+
...getApplyResultEntries(result, 'applied'),
|
|
992
|
+
...getApplyResultEntries(result, 'skipped')
|
|
993
|
+
];
|
|
994
|
+
for (const item of entries) {
|
|
995
|
+
add(item?.operation?.path || item?.operation?.from || item?.operation?.to);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return files;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function formatOperationLine(operation, locale = 'zh') {
|
|
1002
|
+
const labels = OPERATION_LABELS[locale] || OPERATION_LABELS.zh;
|
|
1003
|
+
const label = labels[operation?.type] || operation?.type || textFor(locale, '处理', 'process');
|
|
1004
|
+
const filePath = operation?.path || operation?.from || operation?.to || textFor(locale, '未知文件', 'unknown file');
|
|
1005
|
+
const reason = formatOperationReason(operation, locale);
|
|
1006
|
+
return locale === 'en'
|
|
1007
|
+
? (reason ? `${filePath}: ${label} (${reason})` : `${filePath}: ${label}`)
|
|
1008
|
+
: (reason ? `${filePath}:${label}(${reason})` : `${filePath}:${label}`);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function formatOperationReason(operation, locale = 'zh') {
|
|
1012
|
+
const key = operation?.reasonKey || '';
|
|
1013
|
+
const count = Number(operation?.reasonParams?.count || 0);
|
|
1014
|
+
if (key === 'localWorkspaceDelete') {
|
|
1015
|
+
return textFor(locale, '本地 Codex workspace 删除了这个文件。', 'Local Codex workspace deleted this file.');
|
|
1016
|
+
}
|
|
1017
|
+
if (key === 'localWorkspacePatch') {
|
|
1018
|
+
return textFor(
|
|
1019
|
+
locale,
|
|
1020
|
+
`同步本地 Codex workspace 中的局部文件改动(${count || 0} 处)。`,
|
|
1021
|
+
`Synced ${count || 0} local Codex workspace edit${Number(count) === 1 ? '' : 's'}.`
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
if (key === 'localWorkspaceContent') {
|
|
1025
|
+
return textFor(locale, '同步本地 Codex workspace 中的文件内容。', 'Synced file content from the local Codex workspace.');
|
|
1026
|
+
}
|
|
1027
|
+
if (key === 'localWorkspaceCreate') {
|
|
1028
|
+
return textFor(locale, '同步本地 Codex workspace 中的新文件。', 'Synced a new file from the local Codex workspace.');
|
|
1029
|
+
}
|
|
1030
|
+
return localizeVisibleReason(operation?.reason || '', locale);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function formatSkippedOperationLine(item, locale = 'zh') {
|
|
1034
|
+
const operation = item?.operation || {};
|
|
1035
|
+
const labels = OPERATION_LABELS[locale] || OPERATION_LABELS.zh;
|
|
1036
|
+
const label = labels[operation.type] || operation.type || textFor(locale, '处理', 'process');
|
|
1037
|
+
const filePath = operation.path || operation.from || operation.to || textFor(locale, '未知文件', 'unknown file');
|
|
1038
|
+
const result = item?.result || {};
|
|
1039
|
+
const reason = formatSkippedReason(result, operation, locale);
|
|
1040
|
+
return locale === 'en'
|
|
1041
|
+
? `${filePath}: ${label} was not written (${reason})`
|
|
1042
|
+
: `${filePath}:${label}没有写入(${reason})`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function formatSkippedReason(result = {}, operation = {}, locale = 'zh') {
|
|
1046
|
+
const key = result.reasonKey || '';
|
|
1047
|
+
const code = result.code || '';
|
|
1048
|
+
const filePath = result.reasonParams?.filePath || operation?.path || operation?.from || operation?.to || '';
|
|
1049
|
+
if (key === 'missingBaseFile' || code === 'missing_base_file') {
|
|
1050
|
+
const target = filePath || textFor(locale, '这个文件', 'this file');
|
|
1051
|
+
return textFor(
|
|
1052
|
+
locale,
|
|
1053
|
+
`${target} 在任务开始时没有被 Codex 读到。Codex 没有覆盖它;请刷新项目内容后重试。`,
|
|
1054
|
+
`${target} was not read when the task started. Codex did not overwrite it; refresh the project content and retry.`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
if (key === 'staleSnapshot' || code === 'stale_snapshot') {
|
|
1058
|
+
const target = filePath || textFor(locale, '这个文件', 'this file');
|
|
1059
|
+
return textFor(
|
|
1060
|
+
locale,
|
|
1061
|
+
`${target} 在任务执行期间被你或协作者改过,Codex 没有覆盖它。请查看差异后重试。`,
|
|
1062
|
+
`${target} changed while Codex was working, so Codex did not overwrite it. Review the diff and retry.`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (key === 'stalePatchLocation') {
|
|
1066
|
+
return textFor(
|
|
1067
|
+
locale,
|
|
1068
|
+
'Codex 要修改的位置已经无法和当前 Overleaf 内容对齐,所以没有写入。请重新运行任务。',
|
|
1069
|
+
'The edit location no longer matches the current Overleaf content, so nothing was written. Rerun the task.'
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (key === 'stalePatchConflict' || code === 'stale_patch_range') {
|
|
1073
|
+
return textFor(
|
|
1074
|
+
locale,
|
|
1075
|
+
'Codex 要修改的具体位置已经被你或协作者改过,所以没有覆盖它。请查看差异后重试。',
|
|
1076
|
+
'The exact edit location was changed by you or a collaborator, so Codex did not overwrite it. Review the diff and retry.'
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
if (code === 'stale_patch') {
|
|
1080
|
+
return textFor(
|
|
1081
|
+
locale,
|
|
1082
|
+
'这处内容已经和 Codex 读取时不同,所以没有写入。请重新运行,让 Codex 先读取你的最新 Overleaf 内容。',
|
|
1083
|
+
'This exact text changed since Codex read it, so nothing was written. Rerun after Codex reads the latest Overleaf content.'
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
if (code === 'invalid_patch') {
|
|
1087
|
+
return textFor(
|
|
1088
|
+
locale,
|
|
1089
|
+
'Codex 生成的局部写入范围无效,所以没有写入。',
|
|
1090
|
+
'Codex produced an invalid local edit range, so nothing was written.'
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
if (code === 'write_verification_failed') {
|
|
1094
|
+
return textFor(
|
|
1095
|
+
locale,
|
|
1096
|
+
'写入后读回内容和 Codex 预期不一致,已停止把这次操作标记为成功。请刷新 Overleaf 后重试。',
|
|
1097
|
+
'After writing, the editor content did not match Codex\'s expected result, so the write was not marked successful. Reload Overleaf and retry.'
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
if (code === 'file_tree_verification_failed') {
|
|
1101
|
+
return textFor(
|
|
1102
|
+
locale,
|
|
1103
|
+
'Overleaf 文件树操作没有被确认,Codex 已停止把这次操作标记为成功。',
|
|
1104
|
+
'Overleaf did not confirm the file-tree operation, so Codex did not mark it successful.'
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
if (code === 'path_exists_in_snapshot') {
|
|
1108
|
+
const target = filePath || textFor(locale, '这个文件', 'this file');
|
|
1109
|
+
return textFor(
|
|
1110
|
+
locale,
|
|
1111
|
+
`${target} 在任务开始前已经存在。Codex 没有覆盖它;请改用修改文件或换一个文件名。`,
|
|
1112
|
+
`${target} already existed when the task started. Codex did not overwrite it; edit the file instead or choose another filename.`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
if (code === 'path_created_since_snapshot') {
|
|
1116
|
+
const target = filePath || textFor(locale, '这个文件', 'this file');
|
|
1117
|
+
return textFor(
|
|
1118
|
+
locale,
|
|
1119
|
+
`${target} 在任务执行期间被你或协作者新建了,Codex 没有覆盖它。请查看差异后重试。`,
|
|
1120
|
+
`${target} was created by you or a collaborator while Codex was working, so Codex did not overwrite it. Review the diff and retry.`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
return localizeVisibleReason(result.reason || result.error || result.code || textFor(locale, '未知原因', 'unknown reason'), locale);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function localizeVisibleReason(reason, locale = 'zh') {
|
|
1127
|
+
const text = cleanVisibleText(reason || '');
|
|
1128
|
+
if (!text || locale !== 'en') {
|
|
1129
|
+
return text;
|
|
1130
|
+
}
|
|
1131
|
+
let match = text.match(/^(.+?) 在任务开始时没有被 Codex 读到。Codex 没有覆盖它;请刷新项目内容后重试。$/);
|
|
1132
|
+
if (match) {
|
|
1133
|
+
return `${match[1]} was not read when the task started. Codex did not overwrite it; refresh the project content and retry.`;
|
|
1134
|
+
}
|
|
1135
|
+
match = text.match(/^(.+?) 在任务执行期间被你或协作者改过,Codex 没有覆盖它。请查看差异后重试。$/);
|
|
1136
|
+
if (match) {
|
|
1137
|
+
return `${match[1]} changed while Codex was working, so Codex did not overwrite it. Review the diff and retry.`;
|
|
1138
|
+
}
|
|
1139
|
+
if (text.includes('Codex 要修改的位置已经无法和当前 Overleaf 内容对齐')) {
|
|
1140
|
+
return 'The edit location no longer matches the current Overleaf content, so nothing was written. Rerun the task.';
|
|
1141
|
+
}
|
|
1142
|
+
if (text.includes('Codex 要修改的具体位置已经被你或协作者改过')) {
|
|
1143
|
+
return 'The exact edit location was changed by you or a collaborator, so Codex did not overwrite it. Review the diff and retry.';
|
|
1144
|
+
}
|
|
1145
|
+
const patchMatch = text.match(/^同步本地 Codex workspace 中的局部文件改动((\d+) 处)。$/);
|
|
1146
|
+
if (patchMatch) {
|
|
1147
|
+
const count = Number(patchMatch[1]) || 0;
|
|
1148
|
+
return `Synced ${count} local Codex workspace edit${count === 1 ? '' : 's'}.`;
|
|
1149
|
+
}
|
|
1150
|
+
if (text === '本地 Codex workspace 删除了这个文件。') {
|
|
1151
|
+
return 'Local Codex workspace deleted this file.';
|
|
1152
|
+
}
|
|
1153
|
+
if (text === '同步本地 Codex workspace 中的文件内容。') {
|
|
1154
|
+
return 'Synced file content from the local Codex workspace.';
|
|
1155
|
+
}
|
|
1156
|
+
if (text === '同步本地 Codex workspace 中的新文件。') {
|
|
1157
|
+
return 'Synced a new file from the local Codex workspace.';
|
|
1158
|
+
}
|
|
1159
|
+
if (text.includes('Codex 在本地生成了这些文件,但插件没有同步回 Overleaf')) {
|
|
1160
|
+
return text
|
|
1161
|
+
.replace('Codex 在本地生成了这些文件,但插件没有同步回 Overleaf:', 'Codex generated these local files, but the extension did not sync them back to Overleaf:')
|
|
1162
|
+
.replace(/:LaTeX 构建产物,默认不写回。/g, ': LaTeX build artifact; not written back by default.')
|
|
1163
|
+
.replace(/:非文本文件,暂不支持自动写回。/g, ': Non-text file; automatic writeback is not supported yet.')
|
|
1164
|
+
.replace(/:当前类型暂不支持自动写回。/g, ': This file type is not supported for automatic writeback yet.');
|
|
1165
|
+
}
|
|
1166
|
+
return text;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
return {
|
|
1170
|
+
buildHumanCompletionReport,
|
|
1171
|
+
formatHumanReport,
|
|
1172
|
+
mapAgentEventToActivity,
|
|
1173
|
+
translateRawError
|
|
1174
|
+
};
|
|
1175
|
+
});
|