codex-overleaf-link 2.3.0 → 2.3.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/README.md +23 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +20 -0
- package/extension/src/content/composerPanel.js +0 -2
- package/extension/src/content/contentRuntime.js +108 -110
- package/extension/src/content/generated/content.bundle.js +566 -331
- package/extension/src/content/generated/content.bundle.meta.json +4 -3
- package/extension/src/content/markdownText.js +12 -10
- package/extension/src/content/mathText.js +155 -9
- package/extension/src/content/nativeCompatibilityController.js +2 -2
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/runTimelineView.js +6 -0
- package/extension/src/content/writebackOrchestrator.js +2 -49
- package/extension/src/page/trackedChangesLifecycle.js +10 -4
- package/extension/src/page/writebackRouter.js +6 -2
- package/extension/src/shared/agentTranscript.js +10 -3
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/i18n.js +2 -4
- package/extension/src/shared/pageRpcContract.js +2 -2
- package/extension/src/shared/runExecutionSnapshot.js +14 -4
- package/extension/src/shared/runExecutionSnapshotCodec.js +5 -2
- package/extension/src/shared/scopedPersistenceQueuePolicy.js +20 -1
- package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
- package/extension/src/shared/scopedPersistenceTransaction.js +50 -47
- package/extension/src/shared/sessionState.js +46 -11
- package/extension/src/shared/storageDb.js +6 -5
- package/extension/src/shared/writebackOperationEvidence.js +16 -6
- package/extension/src/shared/writebackSettlement.js +28 -6
- package/extension/styles/panel.css +18 -8
- package/native-host/src/codexPromptAssembly.js +1 -1
- package/native-host/src/taskRunnerRuntime.js +11 -81
- package/native-host/src/updateManager.js +36 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h1>Codex Overleaf Link</h1>
|
|
4
4
|
<p><strong>Empower Overleaf with Codex.</strong></p>
|
|
5
5
|
<p>
|
|
6
|
-
<img src="https://img.shields.io/badge/version-2.3.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-2.3.1-blue" alt="version">
|
|
7
7
|
<img src="https://img.shields.io/badge/platform-macOS%20%2F%20Windows%20%2F%20Linux-lightgrey" alt="platform">
|
|
8
8
|
<img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
|
|
9
9
|
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
|
|
@@ -38,14 +38,14 @@ One command installs the managed native host **and** managed extension runtime.
|
|
|
38
38
|
macOS / Linux:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
CODEX_OVERLEAF_REF=v2.3.
|
|
41
|
+
CODEX_OVERLEAF_REF=v2.3.1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.1/install.sh)"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Windows PowerShell:
|
|
45
45
|
|
|
46
46
|
```powershell
|
|
47
|
-
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.
|
|
48
|
-
$env:CODEX_OVERLEAF_REF='v2.3.
|
|
47
|
+
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.1/install.ps1 -OutFile install.ps1
|
|
48
|
+
$env:CODEX_OVERLEAF_REF='v2.3.1'
|
|
49
49
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -56,7 +56,7 @@ Then, in the `chrome://extensions` tab the script opened: enable **Developer mod
|
|
|
56
56
|
`npm exec` installs the same managed native host and extension runtime without keeping a source checkout. Use it if you prefer a pinned npm package.
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
59
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- install-managed
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Then, in `chrome://extensions`, enable **Developer mode**, click **Load unpacked**, and select the managed extension path printed by the command. The Release extension zip remains available for explicitly unmanaged/manual installations.
|
|
@@ -88,9 +88,9 @@ npm installs, updates, and uninstalls the coordinated managed extension/native p
|
|
|
88
88
|
|
|
89
89
|
| Action | Command |
|
|
90
90
|
|--------|---------|
|
|
91
|
-
| Install / update | `npm exec --yes codex-overleaf-link@2.3.
|
|
92
|
-
| Diagnose | `npm exec --yes codex-overleaf-link@2.3.
|
|
93
|
-
| Uninstall | `npm exec --yes codex-overleaf-link@2.3.
|
|
91
|
+
| Install / update | `npm exec --yes codex-overleaf-link@2.3.1 -- install-managed` |
|
|
92
|
+
| Diagnose | `npm exec --yes codex-overleaf-link@2.3.1 -- doctor` |
|
|
93
|
+
| Uninstall | `npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed` |
|
|
94
94
|
|
|
95
95
|
Use `--extension-id <chrome-extension-id>` only for a custom/dev unpacked extension id that differs from the official bundled id.
|
|
96
96
|
|
|
@@ -104,13 +104,13 @@ Managed installations discover signed stable updates automatically and apply the
|
|
|
104
104
|
|
|
105
105
|
v2.2.0 introduces Bootstrap protocol 2. Existing v2.1.x managed installations must run the pinned `install-managed` command once, then reload the extension and Overleaf. Later protocol-2 releases use the in-product signed updater for ordinary runtime, style, vendor, and Native Host feature changes.
|
|
106
106
|
|
|
107
|
-
The v2.3.
|
|
107
|
+
The v2.3.1 GitHub Release contains:
|
|
108
108
|
|
|
109
|
-
- `codex-overleaf-link-extension-v2.3.
|
|
110
|
-
- `codex-overleaf-native-host-v2.3.
|
|
111
|
-
- `codex-overleaf-link-2.3.
|
|
112
|
-
- `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.3.
|
|
113
|
-
- `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.3.
|
|
109
|
+
- `codex-overleaf-link-extension-v2.3.1.zip`: loadable Chrome extension package for manual unpacked installation.
|
|
110
|
+
- `codex-overleaf-native-host-v2.3.1.tar.gz`: native host runtime files used by the installer and release verification.
|
|
111
|
+
- `codex-overleaf-link-2.3.1.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
|
|
112
|
+
- `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.3.1` when run directly from the release artifact.
|
|
113
|
+
- `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.3.1` when run directly from the release artifact.
|
|
114
114
|
- `uninstall-native-host.mjs`: native host uninstaller that removes the Chrome Native Messaging manifest, bridge executable, and runtime copy.
|
|
115
115
|
- `nativeHostPlatform.js`, `manifest.js`, `runtimeInstaller.js`: helper files required by the loose uninstaller asset.
|
|
116
116
|
- `SHA256SUMS` and `release-manifest.json`: checksum and artifact metadata for release verification.
|
|
@@ -121,7 +121,7 @@ The v2.3.0 GitHub Release contains:
|
|
|
121
121
|
Remove the native host (use `--browser chromium` on Linux Chromium):
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
124
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
The same command works on Windows PowerShell. If you installed from a manual checkout or source installer, you can also run `npm run uninstall:native` inside the repo, use `node ~/.codex-overleaf/source/scripts/uninstall-native-host.mjs` on macOS / Linux, or use `node $env:LOCALAPPDATA\CodexOverleaf\source\scripts\uninstall-native-host.mjs` on Windows PowerShell.
|
|
@@ -156,13 +156,13 @@ Then remove the extension from `chrome://extensions`. To delete local data: on m
|
|
|
156
156
|
Linux Chromium install or update:
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
CODEX_OVERLEAF_REF=v2.3.
|
|
159
|
+
CODEX_OVERLEAF_REF=v2.3.1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.1/install.sh)" -- --browser chromium
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
Linux Chromium uninstall:
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
165
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed --browser chromium
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
## Features
|
|
@@ -298,7 +298,7 @@ Composer attachments are turn-scoped Codex context. Limits are 8 attachments per
|
|
|
298
298
|
Re-run any [native host installer](#install), reload the extension in `chrome://extensions`, then refresh the Overleaf tab. This also fixes extension/native version mismatch and native protocol mismatch.
|
|
299
299
|
|
|
300
300
|
```bash
|
|
301
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
301
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- install-managed
|
|
302
302
|
```
|
|
303
303
|
|
|
304
304
|
**The Windows popup or panel shows a Bash recovery command**
|
|
@@ -347,8 +347,8 @@ Use this matrix for release-candidate signoff and compatibility reports. Record
|
|
|
347
347
|
| Browser/channel/version | Google Chrome channel and version. | Google Chrome channel and version. | Google Chrome channel and version. | Chromium channel/package and version. |
|
|
348
348
|
| Install mode | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout; native host installed with `--browser chromium`. |
|
|
349
349
|
| Extension id | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. |
|
|
350
|
-
| Installer/update command | `npm exec --yes codex-overleaf-link@2.3.
|
|
351
|
-
| Uninstall command | `npm exec --yes codex-overleaf-link@2.3.
|
|
350
|
+
| Installer/update command | `npm exec --yes codex-overleaf-link@2.3.1 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.1 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.1 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.1 -- install-managed --browser chromium` |
|
|
351
|
+
| Uninstall command | `npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-managed --browser chromium` |
|
|
352
352
|
| Manifest/registry path | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.codex.overleaf.json` | `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.codex.overleaf` -> `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime\com.codex.overleaf.json` | `~/.config/google-chrome/NativeMessagingHosts/com.codex.overleaf.json` | `~/.config/chromium/NativeMessagingHosts/com.codex.overleaf.json` |
|
|
353
353
|
| Bridge/runtime/source path | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `%LOCALAPPDATA%\CodexOverleaf\codex-overleaf-bridge.cmd`; runtime `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime`; source `%LOCALAPPDATA%\CodexOverleaf\source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. |
|
|
354
354
|
| Node/Git/Codex/TeX | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. |
|
|
@@ -384,7 +384,7 @@ Contributions are welcome. Please open an issue before submitting large changes
|
|
|
384
384
|
The recommended v1.9 installation owns one stable unpacked-extension directory and a versioned native-host directory:
|
|
385
385
|
|
|
386
386
|
```bash
|
|
387
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
387
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- install-managed
|
|
388
388
|
```
|
|
389
389
|
|
|
390
390
|
Load the printed extension path once from `chrome://extensions`. Starting with the next signed stable release, Codex Overleaf Link checks GitHub Releases automatically, downloads and verifies the coordinated extension/native-host bundle, waits until every connected Overleaf tab is saved and idle, and then updates both components together. A failed health check restores the previous version automatically.
|
|
@@ -394,11 +394,11 @@ The stable updater ignores drafts and prereleases, performs no telemetry, and ca
|
|
|
394
394
|
The legacy native-only installer remains available for source or unmanaged extension directories:
|
|
395
395
|
|
|
396
396
|
```bash
|
|
397
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
397
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- install-native
|
|
398
398
|
```
|
|
399
399
|
|
|
400
400
|
Legacy native-only installations can be removed with:
|
|
401
401
|
|
|
402
402
|
```bash
|
|
403
|
-
npm exec --yes codex-overleaf-link@2.3.
|
|
403
|
+
npm exec --yes codex-overleaf-link@2.3.1 -- uninstall-native
|
|
404
404
|
```
|
|
@@ -21,6 +21,7 @@ if (!globalThis.CodexOverleafNativeRequestIdentity) {
|
|
|
21
21
|
|
|
22
22
|
const HOST_NAME = 'com.codex.overleaf';
|
|
23
23
|
const MANAGED_UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
|
|
24
|
+
const MANAGED_UPDATE_CONSENT_KEY = 'codex-overleaf-update-consent-v1';
|
|
24
25
|
const MANAGED_UPDATE_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
|
|
25
26
|
const MANAGED_OVERLEAF_MATCHES = [
|
|
26
27
|
'https://www.overleaf.com/project',
|
|
@@ -406,6 +407,25 @@ if (!globalThis.CodexOverleafNativeRequestIdentity) {
|
|
|
406
407
|
}
|
|
407
408
|
|
|
408
409
|
const transaction = response.result?.transaction || null;
|
|
410
|
+
if (transaction?.state === 'superseded') {
|
|
411
|
+
const activeVersion = response.result?.activeVersion || chrome.runtime.getManifest().version;
|
|
412
|
+
await chrome.storage.local.remove([
|
|
413
|
+
MANAGED_UPDATE_CONSENT_KEY,
|
|
414
|
+
MANAGED_UPDATE_TABS_KEY
|
|
415
|
+
]);
|
|
416
|
+
await setManagedUpdateState({
|
|
417
|
+
state: 'idle',
|
|
418
|
+
managed: true,
|
|
419
|
+
currentVersion: activeVersion,
|
|
420
|
+
latestVersion: activeVersion,
|
|
421
|
+
transactionId: '',
|
|
422
|
+
blocker: '',
|
|
423
|
+
blockers: [],
|
|
424
|
+
code: '',
|
|
425
|
+
message: ''
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
409
429
|
const state = await getManagedUpdateState();
|
|
410
430
|
const reconciled = ManagedUpdateProjection.reconcile(state, transaction, {
|
|
411
431
|
currentVersion: chrome.runtime.getManifest().version
|
|
@@ -29,12 +29,10 @@
|
|
|
29
29
|
<span class="codex-mode-label" data-i18n="mode">Mode</span>
|
|
30
30
|
<div class="codex-mode-switch" role="group" aria-label="Write mode">
|
|
31
31
|
<button type="button" data-mode-choice="ask" aria-pressed="false" title="Read and analyze only. Do not write to Overleaf.">Ask</button>
|
|
32
|
-
<button type="button" data-mode-choice="confirm" aria-pressed="false" title="Show a change plan first, then write after approval.">Suggest</button>
|
|
33
32
|
<button type="button" data-mode-choice="auto" aria-pressed="false" title="Write directly after authorization. Deletes still require confirmation.">Auto</button>
|
|
34
33
|
</div>
|
|
35
34
|
<select data-mode aria-label="Mode" hidden>
|
|
36
35
|
<option value="ask">Ask</option>
|
|
37
|
-
<option value="confirm">Suggest</option>
|
|
38
36
|
<option value="auto">Auto</option>
|
|
39
37
|
</select>
|
|
40
38
|
</div>
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
'codex.providers.clear-secret',
|
|
51
51
|
'codex.providers.delete',
|
|
52
52
|
'task.run',
|
|
53
|
-
'task.confirm',
|
|
54
53
|
'mirror.sync',
|
|
55
54
|
'mirror.patchFiles',
|
|
56
55
|
'mirror.confirmWriteback',
|
|
@@ -183,6 +182,7 @@
|
|
|
183
182
|
callPageBridge,
|
|
184
183
|
getCurrentProjectId,
|
|
185
184
|
getCurrentProjectReferenceFiles,
|
|
185
|
+
getCurrentProjectMathSources,
|
|
186
186
|
showPluginToast,
|
|
187
187
|
recordRenderingDiagnostic: detail => appendTechnicalEvent({ type: 'rendering.diagnostic', detail })
|
|
188
188
|
});
|
|
@@ -526,7 +526,6 @@
|
|
|
526
526
|
appendOperationsPreview,
|
|
527
527
|
appendPartialWritebackWarning,
|
|
528
528
|
appendApplyResult,
|
|
529
|
-
renderDiffReview,
|
|
530
529
|
renderReadOnlyDiffReview,
|
|
531
530
|
showPluginConfirm,
|
|
532
531
|
callPageBridge,
|
|
@@ -1512,9 +1511,6 @@
|
|
|
1512
1511
|
if (mode === 'ask') {
|
|
1513
1512
|
return 'Ask';
|
|
1514
1513
|
}
|
|
1515
|
-
if (mode === 'confirm') {
|
|
1516
|
-
return 'Confirm';
|
|
1517
|
-
}
|
|
1518
1514
|
if (mode === 'auto') {
|
|
1519
1515
|
return 'Auto';
|
|
1520
1516
|
}
|
|
@@ -1877,7 +1873,7 @@
|
|
|
1877
1873
|
if (!useExistingMirror) {
|
|
1878
1874
|
appendLog(formatProjectSnapshotUserLog(project));
|
|
1879
1875
|
}
|
|
1880
|
-
|
|
1876
|
+
persistCurrentProjectReferenceFiles(project);
|
|
1881
1877
|
const snapshotWarnings = useExistingMirror
|
|
1882
1878
|
? { blocking: [], nonBlocking: [] }
|
|
1883
1879
|
: getProjectSnapshotWarnings(project);
|
|
@@ -1924,7 +1920,7 @@
|
|
|
1924
1920
|
skippedFiles: [],
|
|
1925
1921
|
blockedFiles: snapshotWarnings.blocking.map(reason => ({ path: 'project', reason }))
|
|
1926
1922
|
});
|
|
1927
|
-
finishRunView(tx('Blocked: full project was not read', '已阻止:没有读到完整项目'), 'failed');
|
|
1923
|
+
await finishRunView(tx('Blocked: full project was not read', '已阻止:没有读到完整项目'), 'failed');
|
|
1928
1924
|
return;
|
|
1929
1925
|
}
|
|
1930
1926
|
|
|
@@ -2011,7 +2007,7 @@
|
|
|
2011
2007
|
reason: finding.detectorId || 'sensitive'
|
|
2012
2008
|
}))
|
|
2013
2009
|
});
|
|
2014
|
-
finishRunView(tx('Blocked: sensitive content review required', '已阻止:需要处理敏感内容'), 'failed');
|
|
2010
|
+
await finishRunView(tx('Blocked: sensitive content review required', '已阻止:需要处理敏感内容'), 'failed');
|
|
2015
2011
|
return;
|
|
2016
2012
|
}
|
|
2017
2013
|
if (sensitiveFindings.findings.length) {
|
|
@@ -2049,7 +2045,7 @@
|
|
|
2049
2045
|
return;
|
|
2050
2046
|
}
|
|
2051
2047
|
project = staleRetry.project;
|
|
2052
|
-
|
|
2048
|
+
persistCurrentProjectReferenceFiles(project);
|
|
2053
2049
|
useExistingMirror = false;
|
|
2054
2050
|
fileOverlays = null;
|
|
2055
2051
|
otWarmStart = false;
|
|
@@ -2104,7 +2100,7 @@
|
|
|
2104
2100
|
} else {
|
|
2105
2101
|
appendRunEvent({ title: tx('Cancelled: user chose not to create a new thread.', '已取消:用户选择不新建线程。'), status: 'rejected' });
|
|
2106
2102
|
await finalizeAuditRecord(runAuditDraft, { resultStatus: 'rejected' });
|
|
2107
|
-
finishRunView(tx('Cancelled', '已取消'), 'rejected');
|
|
2103
|
+
await finishRunView(tx('Cancelled', '已取消'), 'rejected');
|
|
2108
2104
|
return;
|
|
2109
2105
|
}
|
|
2110
2106
|
}
|
|
@@ -2112,7 +2108,7 @@
|
|
|
2112
2108
|
if (!response.ok) {
|
|
2113
2109
|
if (runCancellationRequested || isRunCancellationError(response.error)) {
|
|
2114
2110
|
appendRunCancelledReport();
|
|
2115
|
-
finishRunView(tx('Cancelled', '已中断'), 'rejected');
|
|
2111
|
+
await finishRunView(tx('Cancelled', '已中断'), 'rejected');
|
|
2116
2112
|
void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2117
2113
|
return;
|
|
2118
2114
|
}
|
|
@@ -2199,7 +2195,7 @@
|
|
|
2199
2195
|
sensitiveFindings: sensitiveFindings.findings,
|
|
2200
2196
|
blockedFiles: [{ path: 'codex.run', reason: response.error?.code || response.error?.message || 'native_error' }]
|
|
2201
2197
|
});
|
|
2202
|
-
finishRunView(response.error?.code === 'project_locked'
|
|
2198
|
+
await finishRunView(response.error?.code === 'project_locked'
|
|
2203
2199
|
? tx('Codex task already running', 'Codex 任务正在运行')
|
|
2204
2200
|
: tx('Local Codex error', '本地 Codex 错误'), 'failed');
|
|
2205
2201
|
return;
|
|
@@ -2249,7 +2245,7 @@
|
|
|
2249
2245
|
sensitiveFindings: sensitiveFindings.findings,
|
|
2250
2246
|
blockedFiles: [{ path: 'codex.run', reason: 'codex_no_usable_result' }]
|
|
2251
2247
|
});
|
|
2252
|
-
finishRunView(tx('Local Codex returned nothing usable', '本地 Codex 没有返回可用结果'), 'failed');
|
|
2248
|
+
await finishRunView(tx('Local Codex returned nothing usable', '本地 Codex 没有返回可用结果'), 'failed');
|
|
2253
2249
|
return;
|
|
2254
2250
|
}
|
|
2255
2251
|
const writebackProject = useExistingMirror
|
|
@@ -2272,7 +2268,7 @@
|
|
|
2272
2268
|
resultStatus: syncOutcome.hasSkippedOperations ? 'completed_with_skips' : 'completed'
|
|
2273
2269
|
});
|
|
2274
2270
|
|
|
2275
|
-
finishRunView(
|
|
2271
|
+
await finishRunView(
|
|
2276
2272
|
syncOutcome.hasSkippedOperations ? tx('Sync completed with skipped items', '同步完成但有跳过项') : tx('Sync completed', '同步完成'),
|
|
2277
2273
|
syncOutcome.hasSkippedOperations ? 'failed' : 'completed'
|
|
2278
2274
|
);
|
|
@@ -2366,7 +2362,7 @@
|
|
|
2366
2362
|
} catch (error) {
|
|
2367
2363
|
if (runCancellationRequested || isRunCancellationError(error)) {
|
|
2368
2364
|
appendRunCancelledReport();
|
|
2369
|
-
finishRunView(tx('Cancelled', '已中断'), 'rejected');
|
|
2365
|
+
await finishRunView(tx('Cancelled', '已中断'), 'rejected');
|
|
2370
2366
|
void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2371
2367
|
return;
|
|
2372
2368
|
}
|
|
@@ -2416,7 +2412,7 @@
|
|
|
2416
2412
|
resultStatus: 'failed',
|
|
2417
2413
|
blockedFiles: [{ path: 'task', reason: error.message }]
|
|
2418
2414
|
});
|
|
2419
|
-
finishRunView(tx('Task failed', '任务失败'), 'failed');
|
|
2415
|
+
await finishRunView(tx('Task failed', '任务失败'), 'failed');
|
|
2420
2416
|
} finally {
|
|
2421
2417
|
const settledView = currentRunView;
|
|
2422
2418
|
const settledRecord = settledView ? findRunRecord(settledView.recordId, settledView.sessionId) : null;
|
|
@@ -2489,7 +2485,7 @@
|
|
|
2489
2485
|
if (runCancellationRequested || isRunCancellationError(response.error)) {
|
|
2490
2486
|
appendRunCancelledReport();
|
|
2491
2487
|
await finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2492
|
-
finishRunView(tx('Cancelled', '已中断'), 'rejected');
|
|
2488
|
+
await finishRunView(tx('Cancelled', '已中断'), 'rejected');
|
|
2493
2489
|
return;
|
|
2494
2490
|
}
|
|
2495
2491
|
const translated = translateRawError(response.error.message, { mode: submittedMode, locale: getLocale() });
|
|
@@ -2522,7 +2518,7 @@
|
|
|
2522
2518
|
resultStatus: 'failed',
|
|
2523
2519
|
blockedFiles: [{ path: 'skill-installer', reason: response.error?.code || response.error?.message || 'native_error' }]
|
|
2524
2520
|
});
|
|
2525
|
-
finishRunView(tx('Skill installer failed', 'Skill installer 失败'), 'failed');
|
|
2521
|
+
await finishRunView(tx('Skill installer failed', 'Skill installer 失败'), 'failed');
|
|
2526
2522
|
return;
|
|
2527
2523
|
}
|
|
2528
2524
|
|
|
@@ -2536,7 +2532,7 @@
|
|
|
2536
2532
|
mode: submittedMode
|
|
2537
2533
|
});
|
|
2538
2534
|
await finalizeAuditRecord(runAuditDraft, { resultStatus: 'completed' });
|
|
2539
|
-
finishRunView(tx('Skill installer completed', 'Skill installer 已完成'), 'completed');
|
|
2535
|
+
await finishRunView(tx('Skill installer completed', 'Skill installer 已完成'), 'completed');
|
|
2540
2536
|
|
|
2541
2537
|
const runSessionForHistory = findSessionById(runSessionId) || getActiveSession(state);
|
|
2542
2538
|
const rawAssistantMessage = assistantMessage;
|
|
@@ -2659,7 +2655,7 @@
|
|
|
2659
2655
|
const finishTitle = requireReviewing
|
|
2660
2656
|
? tx('Not started: could not enable Track Changes', '未开始:无法开启留痕')
|
|
2661
2657
|
: tx('Not started: could not switch to Editing', '未开始:无法切换到 Editing');
|
|
2662
|
-
finishRunView(finishTitle, 'failed');
|
|
2658
|
+
await finishRunView(finishTitle, 'failed');
|
|
2663
2659
|
return {
|
|
2664
2660
|
ok: false,
|
|
2665
2661
|
reason,
|
|
@@ -3857,10 +3853,6 @@
|
|
|
3857
3853
|
return diffReviewPanel.createDiffReviewElement(syncChanges, options);
|
|
3858
3854
|
}
|
|
3859
3855
|
|
|
3860
|
-
function renderDiffReview(syncChanges) {
|
|
3861
|
-
return diffReviewPanel.renderDiffReview(syncChanges);
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
3856
|
function renderReadOnlyDiffReview(syncChanges, title = tr('diffWrittenChangesTitle')) {
|
|
3865
3857
|
return diffReviewPanel.renderReadOnlyDiffReview(syncChanges, title);
|
|
3866
3858
|
}
|
|
@@ -4623,80 +4615,11 @@
|
|
|
4623
4615
|
mode,
|
|
4624
4616
|
operations: [],
|
|
4625
4617
|
applyResults: [],
|
|
4626
|
-
nextStep: tx('Continue the conversation, or switch to
|
|
4618
|
+
nextStep: tx('Continue the conversation, or switch to Auto to let Codex edit files.', '可以继续追问,或切换到自动写入后让 Codex 修改文件。')
|
|
4627
4619
|
});
|
|
4628
4620
|
return { status: tr('modeAsk'), summaryLine };
|
|
4629
4621
|
}
|
|
4630
4622
|
|
|
4631
|
-
if (result.status === 'requires_task_confirmation') {
|
|
4632
|
-
appendPlannedChangeSummary(result.summary, tx('Preparing changes', '准备修改'));
|
|
4633
|
-
const approved = await showPluginConfirm({
|
|
4634
|
-
title: tx('Apply these changes?', '应用这些修改?'),
|
|
4635
|
-
message: formatSummary(tx('Change Summary', '修改摘要'), result.summary),
|
|
4636
|
-
confirmLabel: tx('Apply changes', '应用修改'),
|
|
4637
|
-
cancelLabel: tr('confirmDefaultCancel')
|
|
4638
|
-
});
|
|
4639
|
-
if (!approved) {
|
|
4640
|
-
appendLog(tx('Cancelled: Codex did not write any files.', '已取消:Codex 没有写入任何文件。'));
|
|
4641
|
-
const summaryLine = appendChangeSummary({ notes, summary: result.summary, status: 'rejected' });
|
|
4642
|
-
appendCompletionReport({
|
|
4643
|
-
conclusion: tx('You cancelled this change. Codex did not write files.', '你取消了这轮修改,Codex 没有写入文件。'),
|
|
4644
|
-
status: 'rejected',
|
|
4645
|
-
notes,
|
|
4646
|
-
summary: result.summary,
|
|
4647
|
-
userReport: result.userReport,
|
|
4648
|
-
mode,
|
|
4649
|
-
operations: [],
|
|
4650
|
-
applyResults: [],
|
|
4651
|
-
nextStep: tx('Adjust the task and run again, or switch to Ask first so Codex can explain the plan.', '可以调整任务描述后重新运行,或切到只问不改先让 Codex 解释方案。')
|
|
4652
|
-
});
|
|
4653
|
-
return { status: 'rejected', summaryLine };
|
|
4654
|
-
}
|
|
4655
|
-
const confirmed = result.planId
|
|
4656
|
-
? await sendNative({ method: 'task.confirm', params: { planId: result.planId } })
|
|
4657
|
-
: { ok: true, result: { operations: result.operations || [] } };
|
|
4658
|
-
if (!confirmed.ok) {
|
|
4659
|
-
appendLog(`Confirm failed: ${confirmed.error.message}`);
|
|
4660
|
-
const summaryLine = appendChangeSummary({ notes, operations: [], status: 'confirm failed' });
|
|
4661
|
-
appendCompletionReport({
|
|
4662
|
-
conclusion: tx('No writable changes were returned after confirmation.', '确认后没有拿到可写入的修改。'),
|
|
4663
|
-
status: 'confirm failed',
|
|
4664
|
-
notes,
|
|
4665
|
-
userReport: result.userReport,
|
|
4666
|
-
mode,
|
|
4667
|
-
operations: [],
|
|
4668
|
-
applyResults: [],
|
|
4669
|
-
nextStep: confirmed.error.message
|
|
4670
|
-
});
|
|
4671
|
-
return { status: 'confirm failed', summaryLine };
|
|
4672
|
-
}
|
|
4673
|
-
const operations = confirmed.result.operations || [];
|
|
4674
|
-
appendOperationsPreview(operations, tx('Confirmed; preparing to write', '用户已确认,准备写入'));
|
|
4675
|
-
const applied = await applyTaskOperations(project, operations, { allowHighRisk: true });
|
|
4676
|
-
appendApplyResult(applied);
|
|
4677
|
-
recordUndoFromApply(project, applied);
|
|
4678
|
-
const summaryLine = appendChangeSummary({
|
|
4679
|
-
notes,
|
|
4680
|
-
operations,
|
|
4681
|
-
applyResults: [applied],
|
|
4682
|
-
status: 'confirmed and applied'
|
|
4683
|
-
});
|
|
4684
|
-
appendCompletionReport({
|
|
4685
|
-
conclusion: notes || tx('Changes were written after your confirmation.', '已按你的确认写入修改。'),
|
|
4686
|
-
status: 'confirmed and applied',
|
|
4687
|
-
notes,
|
|
4688
|
-
userReport: result.userReport,
|
|
4689
|
-
mode,
|
|
4690
|
-
operations,
|
|
4691
|
-
applyResults: [applied]
|
|
4692
|
-
});
|
|
4693
|
-
return {
|
|
4694
|
-
status: 'confirmed and applied',
|
|
4695
|
-
summaryLine,
|
|
4696
|
-
hasSkippedOperations: hasSkippedApplyOperations([applied])
|
|
4697
|
-
};
|
|
4698
|
-
}
|
|
4699
|
-
|
|
4700
4623
|
if (result.status === 'delete_plan_required') {
|
|
4701
4624
|
const operations = result.operations || [];
|
|
4702
4625
|
const applyResults = [];
|
|
@@ -4769,7 +4692,7 @@
|
|
|
4769
4692
|
|
|
4770
4693
|
const operations = result.operations || [];
|
|
4771
4694
|
appendOperationsPreview(operations, tx('Preparing to write', '准备写入'));
|
|
4772
|
-
const applied = await applyTaskOperations(project, operations
|
|
4695
|
+
const applied = await applyTaskOperations(project, operations);
|
|
4773
4696
|
appendApplyResult(applied);
|
|
4774
4697
|
recordUndoFromApply(project, applied);
|
|
4775
4698
|
appendLog(mode === 'auto' ? tx('Auto write task completed.', '自动写入任务完成。') : tx('Task completed.', '任务完成。'));
|
|
@@ -5089,9 +5012,6 @@
|
|
|
5089
5012
|
if (mode === 'ask') {
|
|
5090
5013
|
return tr('modeAsk');
|
|
5091
5014
|
}
|
|
5092
|
-
if (mode === 'confirm') {
|
|
5093
|
-
return tr('modeConfirm');
|
|
5094
|
-
}
|
|
5095
5015
|
if (mode === 'auto') {
|
|
5096
5016
|
return tr('modeAuto');
|
|
5097
5017
|
}
|
|
@@ -5103,8 +5023,6 @@
|
|
|
5103
5023
|
const labels = {
|
|
5104
5024
|
completed: tx('Completed', '已完成'),
|
|
5105
5025
|
rejected: tx('Cancelled', '已取消'),
|
|
5106
|
-
'confirm failed': tx('Confirmation failed', '确认失败'),
|
|
5107
|
-
'confirmed and applied': tx('Suggested changes applied', '已应用建议修改'),
|
|
5108
5026
|
'applied with delete plan': tx('Applied with confirmed deletes', '已应用并删除确认项'),
|
|
5109
5027
|
'applied without deletes': tx('Applied without deletes', '已应用非删除修改'),
|
|
5110
5028
|
'只问不改': tr('modeAsk')
|
|
@@ -5581,7 +5499,7 @@
|
|
|
5581
5499
|
history: Array.isArray(session.history) ? session.history : [],
|
|
5582
5500
|
task: typeof session.task === 'string' ? session.task : '',
|
|
5583
5501
|
pendingInputs: Array.isArray(session.pendingInputs) ? session.pendingInputs : [],
|
|
5584
|
-
mode: session.mode || prefs.mode || '
|
|
5502
|
+
mode: session.mode || prefs.mode || 'ask', providerId: session.providerId || 'builtin',
|
|
5585
5503
|
model: session.model || prefs.model || 'gpt-5.4',
|
|
5586
5504
|
reasoningEffort: session.reasoningEffort || prefs.reasoningEffort || 'high',
|
|
5587
5505
|
speedTier: session.speedTier || prefs.speedTier || 'standard',
|
|
@@ -5984,7 +5902,7 @@
|
|
|
5984
5902
|
}
|
|
5985
5903
|
|
|
5986
5904
|
async function selectMode(mode) {
|
|
5987
|
-
if (!['ask', '
|
|
5905
|
+
if (!['ask', 'auto'].includes(mode)) {
|
|
5988
5906
|
return;
|
|
5989
5907
|
}
|
|
5990
5908
|
const modeSelect = panel?.querySelector('[data-mode]');
|
|
@@ -6268,7 +6186,11 @@
|
|
|
6268
6186
|
events: root.querySelector('[data-run-events]'),
|
|
6269
6187
|
report: root.querySelector('[data-run-report]'),
|
|
6270
6188
|
status: root.querySelector('[data-run-status]'),
|
|
6271
|
-
|
|
6189
|
+
// A run starts before warm-mirror resolution has produced its lightweight
|
|
6190
|
+
// project snapshot. Seed references from the already-prefetched project
|
|
6191
|
+
// inventory so early stream events and fast Ask responses can still
|
|
6192
|
+
// resolve project-relative locations.
|
|
6193
|
+
projectFiles: getCurrentProjectReferenceFiles(),
|
|
6272
6194
|
startedAt: Date.now(),
|
|
6273
6195
|
queueItemId,
|
|
6274
6196
|
nativeRequestId: '',
|
|
@@ -6289,7 +6211,7 @@
|
|
|
6289
6211
|
return updatedAt;
|
|
6290
6212
|
}
|
|
6291
6213
|
|
|
6292
|
-
function finishRunView(text, status) {
|
|
6214
|
+
async function finishRunView(text, status) {
|
|
6293
6215
|
if (!currentRunView) {
|
|
6294
6216
|
return;
|
|
6295
6217
|
}
|
|
@@ -6316,7 +6238,11 @@
|
|
|
6316
6238
|
const navigationDivergent = currentRunView.runProjectId
|
|
6317
6239
|
&& activeProjectId !== currentRunView.runProjectId;
|
|
6318
6240
|
if (!navigationDivergent) {
|
|
6319
|
-
|
|
6241
|
+
// A terminal badge is a durability promise: once the user can see
|
|
6242
|
+
// Done/Failed/Cancelled, an immediate reload must hydrate that same
|
|
6243
|
+
// terminal state. Drain any older running snapshot and persist the
|
|
6244
|
+
// latest record before exposing the terminal UI.
|
|
6245
|
+
await flushQueuedSaveState().catch(() => {});
|
|
6320
6246
|
}
|
|
6321
6247
|
renderSessionList();
|
|
6322
6248
|
}
|
|
@@ -6636,6 +6562,39 @@
|
|
|
6636
6562
|
|
|
6637
6563
|
|
|
6638
6564
|
|
|
6565
|
+
function getCurrentProjectMathSources() {
|
|
6566
|
+
const sources = [];
|
|
6567
|
+
const seen = new Set();
|
|
6568
|
+
let remainingChars = 256 * 1024;
|
|
6569
|
+
const add = item => {
|
|
6570
|
+
if (!item || typeof item !== 'object' || typeof item.content !== 'string') return;
|
|
6571
|
+
const path = normalizeReferencePathForRuntime(item.path || item.filePath || '');
|
|
6572
|
+
if (!path || seen.has(path) || hasUnsafeRuntimePathSegments(path)
|
|
6573
|
+
|| !/\.(?:tex|sty|cls|ltx)$/i.test(path) || remainingChars <= 0) return;
|
|
6574
|
+
const content = item.content.slice(0, Math.min(64 * 1024, remainingChars));
|
|
6575
|
+
remainingChars -= content.length;
|
|
6576
|
+
seen.add(path);
|
|
6577
|
+
sources.push({ path, content });
|
|
6578
|
+
};
|
|
6579
|
+
for (const collection of [
|
|
6580
|
+
currentRunView?.projectFiles,
|
|
6581
|
+
currentRunView?.files,
|
|
6582
|
+
currentRunView?.project?.files,
|
|
6583
|
+
currentRunView?.snapshot?.files,
|
|
6584
|
+
currentRunView?.projectSnapshot?.files,
|
|
6585
|
+
currentRunView?.contextFiles,
|
|
6586
|
+
state?.projectFiles,
|
|
6587
|
+
state?.files,
|
|
6588
|
+
state?.project?.files,
|
|
6589
|
+
state?.snapshot?.files,
|
|
6590
|
+
state?.projectSnapshot?.files,
|
|
6591
|
+
state?.contextFiles
|
|
6592
|
+
]) {
|
|
6593
|
+
if (Array.isArray(collection)) collection.forEach(add);
|
|
6594
|
+
}
|
|
6595
|
+
return sources;
|
|
6596
|
+
}
|
|
6597
|
+
|
|
6639
6598
|
function getCurrentProjectReferenceFiles() {
|
|
6640
6599
|
const files = [];
|
|
6641
6600
|
const seen = new Set();
|
|
@@ -6686,7 +6645,14 @@
|
|
|
6686
6645
|
}
|
|
6687
6646
|
};
|
|
6688
6647
|
|
|
6648
|
+
const activeSession = typeof getActiveSession === 'function' ? getActiveSession(state) : null;
|
|
6689
6649
|
addFiles(currentRunView?.projectFiles);
|
|
6650
|
+
addFiles(activeSession?.projectReferenceFiles);
|
|
6651
|
+
// ContextTray owns the exact, background-prefetched project file list.
|
|
6652
|
+
// Warm-mirror runs intentionally carry only the active/focused overlay, so
|
|
6653
|
+
// omitting this inventory makes valid references to every other project
|
|
6654
|
+
// file render as plain text.
|
|
6655
|
+
addFiles(contextTrayController?.getContextProject?.()?.files);
|
|
6690
6656
|
addFiles(currentRunView?.files);
|
|
6691
6657
|
addFiles(currentRunView?.project?.files);
|
|
6692
6658
|
addFiles(currentRunView?.snapshot?.files);
|
|
@@ -6700,14 +6666,26 @@
|
|
|
6700
6666
|
addFiles(state?.contextFiles);
|
|
6701
6667
|
addTextPaths(state?.focusFiles);
|
|
6702
6668
|
addTextPaths(state?.session?.focusFiles);
|
|
6703
|
-
const activeSession = typeof getActiveSession === 'function' ? getActiveSession(state) : null;
|
|
6704
6669
|
addTextPaths(activeSession?.focusFiles);
|
|
6705
6670
|
for (const session of Array.isArray(state?.sessions) ? state.sessions : []) {
|
|
6671
|
+
addFiles(session?.projectReferenceFiles);
|
|
6706
6672
|
addTextPaths(session?.focusFiles);
|
|
6707
6673
|
}
|
|
6708
6674
|
return files;
|
|
6709
6675
|
}
|
|
6710
6676
|
|
|
6677
|
+
function persistCurrentProjectReferenceFiles(project) {
|
|
6678
|
+
const projectFiles = captureProjectReferenceFiles(project);
|
|
6679
|
+
if (currentRunView) {
|
|
6680
|
+
currentRunView.projectFiles = projectFiles;
|
|
6681
|
+
}
|
|
6682
|
+
if (getActiveSession(state)) {
|
|
6683
|
+
state = updateActiveSession(state, { projectReferenceFiles: projectFiles });
|
|
6684
|
+
saveStateSoon();
|
|
6685
|
+
}
|
|
6686
|
+
return projectFiles;
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6711
6689
|
function captureProjectReferenceFiles(project) {
|
|
6712
6690
|
const result = [];
|
|
6713
6691
|
const seen = new Set();
|
|
@@ -7167,7 +7145,12 @@
|
|
|
7167
7145
|
record.undoOperations = [];
|
|
7168
7146
|
record.undoBaseFiles = [];
|
|
7169
7147
|
record.undoTrackedChanges = combinedTrackedChanges;
|
|
7170
|
-
record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
|
|
7148
|
+
record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
|
|
7149
|
+
project,
|
|
7150
|
+
combinedAppliedOperations,
|
|
7151
|
+
combinedTrackedChanges,
|
|
7152
|
+
record.undoExpectedFiles
|
|
7153
|
+
);
|
|
7171
7154
|
record.undoStatus = '';
|
|
7172
7155
|
record.partialWriteback = skippedEntries.length > 0;
|
|
7173
7156
|
// Recording tracked-change refs enters the run into the tracked-change
|
|
@@ -7215,7 +7198,12 @@
|
|
|
7215
7198
|
record.undoOperations = checkpoint.undoOperations;
|
|
7216
7199
|
record.undoBaseFiles = checkpoint.undoBaseFiles;
|
|
7217
7200
|
record.undoTrackedChanges = [];
|
|
7218
|
-
record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
|
|
7201
|
+
record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
|
|
7202
|
+
project,
|
|
7203
|
+
combinedAppliedOperations,
|
|
7204
|
+
[],
|
|
7205
|
+
record.undoExpectedFiles
|
|
7206
|
+
);
|
|
7219
7207
|
record.undoStatus = '';
|
|
7220
7208
|
record.partialWriteback = skippedEntries.length > 0;
|
|
7221
7209
|
refreshRunCardControls(record.id);
|
|
@@ -7235,8 +7223,18 @@
|
|
|
7235
7223
|
return WritebackSettlement.normalizeApplyTrackedChanges(changes);
|
|
7236
7224
|
}
|
|
7237
7225
|
|
|
7238
|
-
function selectExpectedFilesForTrackedUndo(
|
|
7239
|
-
|
|
7226
|
+
function selectExpectedFilesForTrackedUndo(
|
|
7227
|
+
project,
|
|
7228
|
+
operations = [],
|
|
7229
|
+
trackedChanges = [],
|
|
7230
|
+
previousExpectedFiles = []
|
|
7231
|
+
) {
|
|
7232
|
+
return WritebackSettlement.selectExpectedFilesForTrackedUndo(
|
|
7233
|
+
project,
|
|
7234
|
+
operations,
|
|
7235
|
+
trackedChanges,
|
|
7236
|
+
previousExpectedFiles
|
|
7237
|
+
);
|
|
7240
7238
|
}
|
|
7241
7239
|
|
|
7242
7240
|
function buildTrackedUndoPostFiles(run) {
|