codex-overleaf-link 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +5 -71
- package/extension/src/background.js +53 -69
- package/extension/src/backgroundUpdateCoordinator.js +128 -66
- package/extension/src/content/applyResultFormatters.js +2 -3
- package/extension/src/content/contentRuntime.js +650 -1449
- package/extension/src/content/contextTray.js +4 -6
- package/extension/src/content/diagnosticsController.js +1 -2
- package/extension/src/content/diffReviewPanel.js +14 -13
- package/extension/src/content/generated/content.bundle.js +41370 -0
- package/extension/src/content/generated/content.bundle.meta.json +102 -0
- package/extension/src/content/legacyGlobalRegistry.js +129 -0
- package/extension/src/content/markdownText.js +5 -7
- package/extension/src/content/modelPicker.js +1 -1
- package/extension/src/content/modelPickerSupport.js +10 -5
- package/extension/src/content/moduleRegistryKernel.js +125 -0
- package/extension/src/content/nativeCompatibilityController.js +313 -0
- package/extension/src/content/otWarmMirror.js +7 -5
- package/extension/src/content/pageBridgeClient.js +224 -0
- package/extension/src/content/panelMaintenance.js +8 -3
- package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
- package/extension/src/content/projectProviderSelection.js +4 -12
- package/extension/src/content/projectSessionCleanup.js +3 -3
- package/extension/src/content/providerSettingsCoordinator.js +59 -26
- package/extension/src/content/providerSettingsDialog.js +30 -18
- package/extension/src/content/recentProjects.js +15 -23
- package/extension/src/content/runGuidanceController.js +1 -1
- package/extension/src/content/runQueueScheduler.js +129 -29
- package/extension/src/content/runSettlementPersistence.js +51 -0
- package/extension/src/content/runTimelineView.js +11 -8
- package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
- package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
- package/extension/src/content/scopedPersistencePanelState.js +111 -0
- package/extension/src/content/sessionManager.js +15 -12
- package/extension/src/content/sessionPersistence.js +67 -8
- package/extension/src/content/updateNotice.js +10 -4
- package/extension/src/content/writebackOrchestrator.js +91 -23
- package/extension/src/contentScript.js +5 -1
- package/extension/src/page/trackedChangesLifecycle.js +6 -1
- package/extension/src/pageBridge.js +47 -98
- package/extension/src/shared/agentTranscript.js +8 -5
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/governanceRules.js +1 -1
- package/extension/src/shared/managedUpdateProjection.js +337 -0
- package/extension/src/shared/nativeRequestIdentity.js +60 -0
- package/extension/src/shared/otText.js +0 -19
- package/extension/src/shared/pageRpcContract.js +136 -0
- package/extension/src/shared/runExecutionSnapshot.js +177 -0
- package/extension/src/shared/runExecutionSnapshotCodec.js +168 -0
- package/extension/src/shared/runInputQueue.js +81 -34
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
- package/extension/src/shared/scopedPersistenceQueuePolicy.js +181 -0
- package/extension/src/shared/scopedPersistenceTransaction.js +238 -0
- package/extension/src/shared/sessionState.js +64 -48
- package/extension/src/shared/settlementFacts.js +351 -0
- package/extension/src/shared/storageDb.js +19 -5
- package/extension/src/shared/storageMigration.js +275 -61
- package/extension/src/shared/storageSessionClaims.js +60 -0
- package/extension/src/shared/updateConsent.js +4 -0
- package/extension/src/shared/updateRevocationIntent.js +109 -0
- package/extension/src/shared/writebackEvidenceProjection.js +150 -0
- package/extension/src/shared/writebackOperationEvidence.js +143 -0
- package/extension/src/shared/writebackSettlement.js +705 -0
- package/native-host/src/codexRuntimeIdentity.js +1 -1
- package/native-host/src/codexSessionRunner.js +7 -13
- package/native-host/src/index.js +3 -69
- package/native-host/src/managedInstall.js +50 -8
- package/native-host/src/mirrorWorkspace.js +15 -8
- package/native-host/src/nativeTransportEnvelope.js +148 -0
- package/native-host/src/taskRunnerRuntime.js +3 -3
- package/package.json +13 -5
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
var api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafScopedPersistenceDetachedCommit = api;
|
|
5
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function create(options) {
|
|
9
|
+
options = options || {};
|
|
10
|
+
var Transaction = options.Transaction;
|
|
11
|
+
if (!Transaction
|
|
12
|
+
|| typeof Transaction.createBrowserAdapter !== 'function'
|
|
13
|
+
|| typeof Transaction.createCoordinator !== 'function') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
var adapter = Transaction.createBrowserAdapter({ chromeApi: options.chromeApi });
|
|
17
|
+
return async function commitDetached(projectId, accountScopeId, commitOptions, action) {
|
|
18
|
+
var nextScope = {
|
|
19
|
+
accountScopeId: String(accountScopeId || '').trim(),
|
|
20
|
+
projectId: String(projectId || '').trim()
|
|
21
|
+
};
|
|
22
|
+
if (!nextScope.accountScopeId) {
|
|
23
|
+
return { ok: false, reason: 'account_scope_unavailable' };
|
|
24
|
+
}
|
|
25
|
+
if (!nextScope.projectId) {
|
|
26
|
+
return { ok: false, reason: 'project_scope_unavailable' };
|
|
27
|
+
}
|
|
28
|
+
var coordinator = Transaction.createCoordinator({
|
|
29
|
+
adapter: adapter,
|
|
30
|
+
writerId: options.writerId
|
|
31
|
+
});
|
|
32
|
+
var hydration = await coordinator.beginHydration(nextScope);
|
|
33
|
+
if (!coordinator.isCurrent(hydration)) {
|
|
34
|
+
return { ok: false, reason: 'stale_view' };
|
|
35
|
+
}
|
|
36
|
+
var view = await coordinator.captureCommitView(nextScope, { detached: true });
|
|
37
|
+
return coordinator.commit(view, action, {
|
|
38
|
+
rebase: !commitOptions || commitOptions.rebase !== false,
|
|
39
|
+
queueMutation: commitOptions && commitOptions.queueMutation,
|
|
40
|
+
queueMutations: commitOptions && commitOptions.queueMutations,
|
|
41
|
+
sessionTombstones: commitOptions && commitOptions.sessionTombstones
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Object.freeze({ create: create });
|
|
47
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
var api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafScopedPersistencePanelState = api;
|
|
5
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function cloneSnapshot(value) {
|
|
9
|
+
if (!value || typeof value !== 'object') {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
if (typeof structuredClone === 'function') {
|
|
13
|
+
return structuredClone(value);
|
|
14
|
+
}
|
|
15
|
+
return JSON.parse(JSON.stringify(value));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function persistPanelState(input) {
|
|
19
|
+
var state = cloneSnapshot(input.state || {});
|
|
20
|
+
var compactState = cloneSnapshot(input.compactState || {});
|
|
21
|
+
var projectId = input.projectId;
|
|
22
|
+
var Migration = input.Migration;
|
|
23
|
+
var StorageDb = input.StorageDb;
|
|
24
|
+
var scope = input.persistenceContext?.scope || {};
|
|
25
|
+
var accountScopeId = typeof scope.accountScopeId === 'string'
|
|
26
|
+
? scope.accountScopeId.trim()
|
|
27
|
+
: '';
|
|
28
|
+
if (!accountScopeId) {
|
|
29
|
+
throw Object.assign(new Error('Scoped persistence requires an account identity.'), {
|
|
30
|
+
code: 'account_scope_unavailable'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (scope.projectId !== projectId) {
|
|
34
|
+
throw Object.assign(new Error('Scoped persistence project changed before commit.'), {
|
|
35
|
+
code: 'stale_view'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
var latestPrefs = typeof Migration.loadPrefs === 'function'
|
|
39
|
+
? await Migration.loadPrefs(accountScopeId, projectId)
|
|
40
|
+
: {};
|
|
41
|
+
var prefs = Object.assign(
|
|
42
|
+
{},
|
|
43
|
+
latestPrefs && typeof latestPrefs === 'object' ? latestPrefs : {},
|
|
44
|
+
StorageDb.extractLightweightPrefs(compactState, projectId)
|
|
45
|
+
);
|
|
46
|
+
prefs.activeSessionByProject = StorageDb.buildActiveSessionByProject(
|
|
47
|
+
latestPrefs && latestPrefs.activeSessionByProject || {},
|
|
48
|
+
projectId,
|
|
49
|
+
compactState.activeSessionId || state.activeSessionId || ''
|
|
50
|
+
);
|
|
51
|
+
prefs.experimentalOtByProject = Object.assign(
|
|
52
|
+
{},
|
|
53
|
+
latestPrefs && latestPrefs.experimentalOtByProject || {},
|
|
54
|
+
{ [projectId]: input.normalizeExperimentalOtByProject(state.experimentalOtByProject)[projectId] === true }
|
|
55
|
+
);
|
|
56
|
+
prefs.governanceRulesByProject = Object.assign(
|
|
57
|
+
{},
|
|
58
|
+
latestPrefs && latestPrefs.governanceRulesByProject || {},
|
|
59
|
+
input.normalizeGovernanceRulesByProject(state.governanceRulesByProject)
|
|
60
|
+
);
|
|
61
|
+
var normalizedCustomProject = input.normalizeCustomInstructionsByProject({ [projectId]: '' });
|
|
62
|
+
var customProjectId = Object.keys(normalizedCustomProject)[0] || '';
|
|
63
|
+
var currentCustom = input.normalizeCustomInstructionsByProject(state.customInstructionsByProject);
|
|
64
|
+
prefs.customInstructionsByProject = Object.assign(
|
|
65
|
+
{},
|
|
66
|
+
latestPrefs && latestPrefs.customInstructionsByProject || {}
|
|
67
|
+
);
|
|
68
|
+
if (customProjectId) {
|
|
69
|
+
prefs.customInstructionsByProject[customProjectId] =
|
|
70
|
+
Object.prototype.hasOwnProperty.call(currentCustom, customProjectId)
|
|
71
|
+
? currentCustom[customProjectId]
|
|
72
|
+
: '';
|
|
73
|
+
}
|
|
74
|
+
await Migration.savePrefs(prefs, accountScopeId, projectId);
|
|
75
|
+
var persistenceMeta = input.persistenceContext?.nextMeta || {};
|
|
76
|
+
var queueTombstones = persistenceMeta.queueTombstones || {};
|
|
77
|
+
var sessionTombstones = persistenceMeta.sessionTombstones || {};
|
|
78
|
+
var deletedSessionIds = Array.from(new Set([
|
|
79
|
+
...(Array.isArray(input.deletedSessionIds) ? input.deletedSessionIds : []),
|
|
80
|
+
...Object.keys(sessionTombstones)
|
|
81
|
+
]));
|
|
82
|
+
var sessionRecords = (state.sessions || []).map(function (session) {
|
|
83
|
+
return StorageDb.buildSessionRecord(Object.assign({}, session, {
|
|
84
|
+
projectId: projectId,
|
|
85
|
+
accountScopeId: accountScopeId,
|
|
86
|
+
title: session.title || '',
|
|
87
|
+
titleSource: session.titleSource || 'auto',
|
|
88
|
+
codexThreadId: session.codexThreadId || '',
|
|
89
|
+
status: 'active',
|
|
90
|
+
focusFiles: Array.isArray(session.focusFiles) ? session.focusFiles : [],
|
|
91
|
+
createdAt: session.createdAt,
|
|
92
|
+
updatedAt: session.updatedAt,
|
|
93
|
+
pendingInputs: (Array.isArray(session.pendingInputs) ? session.pendingInputs : [])
|
|
94
|
+
.filter(function (item) { return !queueTombstones[item?.id]; })
|
|
95
|
+
}), { preserveRunActionPayload: true });
|
|
96
|
+
});
|
|
97
|
+
await input.SessionPersistence.writeSessions({
|
|
98
|
+
Migration: Migration,
|
|
99
|
+
StorageDb: StorageDb,
|
|
100
|
+
projectId: projectId,
|
|
101
|
+
accountScopeId: accountScopeId,
|
|
102
|
+
deletedSessionIds: deletedSessionIds,
|
|
103
|
+
sessionRecords: sessionRecords,
|
|
104
|
+
queueClaims: persistenceMeta.queueClaims || {},
|
|
105
|
+
queueTombstones: queueTombstones,
|
|
106
|
+
writerId: input.persistenceContext?.writerId || ''
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return Object.freeze({ persistPanelState: persistPanelState });
|
|
111
|
+
});
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
(function initCodexOverleafSessionManager() {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const SessionPanel = window.CodexOverleafSessionPanel;
|
|
5
|
-
const {
|
|
6
|
-
createSession,
|
|
7
|
-
deleteSession,
|
|
8
|
-
setActiveSession,
|
|
9
|
-
getActiveSession,
|
|
10
|
-
isDisplayableSession,
|
|
11
|
-
normalizePanelState,
|
|
12
|
-
deriveSessionTitle
|
|
13
|
-
} = window.CodexOverleafSessionState;
|
|
14
|
-
|
|
15
4
|
// Session manager — the session lifecycle + list/delete surface
|
|
16
5
|
// carved out of contentRuntime.js (v1.4.7 structural-debt phase 3). Code
|
|
17
6
|
// moved verbatim except that mutable runtime state is read through
|
|
@@ -19,6 +8,20 @@
|
|
|
19
8
|
// setState() (sessions are immutable-update: every mutation rebuilds state
|
|
20
9
|
// via the shared sessionState helpers and hands it back to the runtime).
|
|
21
10
|
function create(deps = {}) {
|
|
11
|
+
const {
|
|
12
|
+
SessionPanel,
|
|
13
|
+
SessionState,
|
|
14
|
+
SessionMenuView
|
|
15
|
+
} = deps;
|
|
16
|
+
const {
|
|
17
|
+
createSession,
|
|
18
|
+
deleteSession,
|
|
19
|
+
setActiveSession,
|
|
20
|
+
getActiveSession,
|
|
21
|
+
isDisplayableSession,
|
|
22
|
+
normalizePanelState,
|
|
23
|
+
deriveSessionTitle
|
|
24
|
+
} = SessionState;
|
|
22
25
|
const {
|
|
23
26
|
tr,
|
|
24
27
|
showPluginConfirm,
|
|
@@ -36,7 +39,7 @@
|
|
|
36
39
|
formatSessionTime
|
|
37
40
|
} = deps;
|
|
38
41
|
|
|
39
|
-
const sessionMenuView =
|
|
42
|
+
const sessionMenuView = SessionMenuView.create({
|
|
40
43
|
commitSessionRename,
|
|
41
44
|
deleteSessionWithConfirm,
|
|
42
45
|
document,
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
(function initCodexOverleafSessionPersistence() {
|
|
1
|
+
(function initCodexOverleafSessionPersistence(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory(require('../shared/storageMigration'));
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('SessionPersistence', ['StorageMigration'], factory);
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function sessionPersistenceFactory(Migration) {
|
|
2
8
|
'use strict';
|
|
3
9
|
|
|
4
10
|
function migration() {
|
|
5
|
-
return
|
|
11
|
+
return Migration;
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
async function loadTombstones() {
|
|
@@ -21,11 +27,20 @@
|
|
|
21
27
|
function isVisibleRecord(record, deletedIds, accountScopeId) {
|
|
22
28
|
return Boolean(record)
|
|
23
29
|
&& !(deletedIds || []).includes(record.id)
|
|
24
|
-
&& (
|
|
30
|
+
&& Boolean(accountScopeId)
|
|
31
|
+
&& record.accountScopeId === accountScopeId;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
async function writeSessions(options = {}) {
|
|
28
35
|
const { StorageDb, projectId } = options;
|
|
36
|
+
const accountScopeId = typeof options.accountScopeId === 'string'
|
|
37
|
+
? options.accountScopeId.trim()
|
|
38
|
+
: '';
|
|
39
|
+
if (!accountScopeId) {
|
|
40
|
+
throw Object.assign(new Error('Session persistence requires an account identity.'), {
|
|
41
|
+
code: 'account_scope_unavailable'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
29
44
|
const Migration = options.Migration || migration();
|
|
30
45
|
const requested = (Array.isArray(options.deletedSessionIds) ? options.deletedSessionIds : [])
|
|
31
46
|
.filter(id => typeof id === 'string' && id);
|
|
@@ -34,8 +49,23 @@
|
|
|
34
49
|
: await Migration?.loadSessionTombstones?.() || {};
|
|
35
50
|
const deleted = new Set(requested);
|
|
36
51
|
for (const id of Migration?.getDeletedSessionIds?.(tombstones, projectId) || []) deleted.add(id);
|
|
37
|
-
const
|
|
38
|
-
|
|
52
|
+
const queueTombstones = options.queueTombstones && typeof options.queueTombstones === 'object'
|
|
53
|
+
? options.queueTombstones
|
|
54
|
+
: {};
|
|
55
|
+
const rawSessionRecords = (options.sessionRecords || [])
|
|
56
|
+
.filter(record => !deleted.has(record.id) && record.accountScopeId === accountScopeId);
|
|
57
|
+
const rawExistingSessions = (await StorageDb.getAllByIndex('sessions', 'projectId', projectId))
|
|
58
|
+
.filter(record => record?.accountScopeId === accountScopeId);
|
|
59
|
+
const rawExistingById = new Map(rawExistingSessions.map(record => [record.id, record]));
|
|
60
|
+
const sessionRecords = rawSessionRecords
|
|
61
|
+
.map(record => applyQueuePolicy(
|
|
62
|
+
record,
|
|
63
|
+
rawExistingById.get(record.id),
|
|
64
|
+
options,
|
|
65
|
+
queueTombstones
|
|
66
|
+
));
|
|
67
|
+
const existingSessions = rawExistingSessions
|
|
68
|
+
.map(record => applyQueuePolicy(record, record, options, queueTombstones));
|
|
39
69
|
const deletedExisting = existingSessions.filter(record => deleted.has(record.id));
|
|
40
70
|
if (deletedExisting.length) {
|
|
41
71
|
await Promise.all(deletedExisting.map(record => StorageDb.deleteRecord('sessions', record.id)));
|
|
@@ -48,15 +78,44 @@
|
|
|
48
78
|
if (!existing) return true;
|
|
49
79
|
return (Date.parse(record.updatedAt || '') || 0) >= (Date.parse(existing.updatedAt || '') || 0);
|
|
50
80
|
});
|
|
51
|
-
|
|
81
|
+
const cleanedExisting = existingSessions.filter((record, index) =>
|
|
82
|
+
record !== rawExistingSessions[index] && !writable.some(item => item.id === record.id)
|
|
83
|
+
);
|
|
84
|
+
if (cleanedExisting.length || writable.length) {
|
|
85
|
+
await StorageDb.putRecords('sessions', [...cleanedExisting, ...writable]);
|
|
86
|
+
}
|
|
52
87
|
return writable;
|
|
53
88
|
}
|
|
54
89
|
|
|
55
|
-
|
|
90
|
+
function applyQueuePolicy(record, existingRecord, options, queueTombstones) {
|
|
91
|
+
const original = Array.isArray(record?.pendingInputs) ? record.pendingInputs : [];
|
|
92
|
+
let pendingInputs = original.filter(item => item?.id && !queueTombstones[item.id]);
|
|
93
|
+
const existingById = new Map(
|
|
94
|
+
(Array.isArray(existingRecord?.pendingInputs) ? existingRecord.pendingInputs : [])
|
|
95
|
+
.filter(item => item?.id && !queueTombstones[item.id])
|
|
96
|
+
.map(item => [item.id, item])
|
|
97
|
+
);
|
|
98
|
+
const claims = options.queueClaims && typeof options.queueClaims === 'object'
|
|
99
|
+
? options.queueClaims
|
|
100
|
+
: {};
|
|
101
|
+
for (const claim of Object.values(claims)) {
|
|
102
|
+
if (!claim?.itemId || claim.ownerId === options.writerId) continue;
|
|
103
|
+
const index = pendingInputs.findIndex(item => item.id === claim.itemId);
|
|
104
|
+
const authoritative = existingById.get(claim.itemId);
|
|
105
|
+
if (authoritative && index >= 0) pendingInputs[index] = authoritative;
|
|
106
|
+
else if (authoritative) pendingInputs.push(authoritative);
|
|
107
|
+
else if (index >= 0) pendingInputs.splice(index, 1);
|
|
108
|
+
}
|
|
109
|
+
const unchanged = pendingInputs.length === original.length
|
|
110
|
+
&& pendingInputs.every((item, index) => item === original[index]);
|
|
111
|
+
return unchanged ? record : { ...record, pendingInputs };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
56
115
|
loadTombstones,
|
|
57
116
|
getDeletedSessionIds,
|
|
58
117
|
addTombstones,
|
|
59
118
|
isVisibleRecord,
|
|
60
119
|
writeSessions
|
|
61
120
|
};
|
|
62
|
-
})
|
|
121
|
+
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
(function initCodexOverleafUpdateNotice(root) {
|
|
1
|
+
(function initCodexOverleafUpdateNotice(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory(require('../shared/managedUpdateProjection'));
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('UpdateNotice', ['ManagedUpdateProjection'], factory);
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function updateNoticeFactory(UpdateProjection) {
|
|
2
8
|
'use strict';
|
|
3
9
|
|
|
4
10
|
let notice = null;
|
|
@@ -11,7 +17,7 @@
|
|
|
11
17
|
let settingsActionInFlight = false;
|
|
12
18
|
let restartWatchdog = null;
|
|
13
19
|
let manualCommandCopied = false;
|
|
14
|
-
const ACTIVE_UPDATE_STATES = new Set(
|
|
20
|
+
const ACTIVE_UPDATE_STATES = new Set(UpdateProjection.activePhases('panel'));
|
|
15
21
|
const RESTART_WATCHDOG_MS = 30 * 1000;
|
|
16
22
|
|
|
17
23
|
function mount(panelLike, options = {}) {
|
|
@@ -467,5 +473,5 @@
|
|
|
467
473
|
return /^zh\b/i.test(locale) ? chinese : english;
|
|
468
474
|
}
|
|
469
475
|
|
|
470
|
-
|
|
471
|
-
})
|
|
476
|
+
return Object.freeze({ mount });
|
|
477
|
+
});
|
|
@@ -53,11 +53,21 @@
|
|
|
53
53
|
let buildReviewingBlockedApplyResult;
|
|
54
54
|
let ensureReviewingBeforeWrite;
|
|
55
55
|
let confirmBinaryOperations;
|
|
56
|
+
let compileAdapter;
|
|
56
57
|
let filterSyncChangesByOperations;
|
|
57
58
|
let writebackController;
|
|
58
59
|
let RUN_SNAPSHOT_ZIP_TIMEOUT_MS;
|
|
59
60
|
let getState;
|
|
60
61
|
let getCurrentRunView;
|
|
62
|
+
let onMirrorRefreshSettled;
|
|
63
|
+
let writebackSettlement;
|
|
64
|
+
let injectedWritebackSettlement;
|
|
65
|
+
|
|
66
|
+
function buildSettlement(input = {}) {
|
|
67
|
+
return writebackSettlement?.settle instanceof Function
|
|
68
|
+
? writebackSettlement.settle(input)
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
61
71
|
|
|
62
72
|
async function applySyncChangesToOverleaf(syncChanges = [], project = {}, options = {}) {
|
|
63
73
|
const runMode = options.mode || getState().mode;
|
|
@@ -93,6 +103,10 @@
|
|
|
93
103
|
return {
|
|
94
104
|
summaryLine: assistantMessage || tx('Ask mode completed without Overleaf changes', 'Ask 模式已完成,未修改 Overleaf'),
|
|
95
105
|
hasSkippedOperations: false,
|
|
106
|
+
settlement: buildSettlement({
|
|
107
|
+
operations: [],
|
|
108
|
+
applyResult: { ok: true, applied: [], skipped: [] }
|
|
109
|
+
}),
|
|
96
110
|
audit: buildAuditSummaryFromApply({
|
|
97
111
|
operations: [],
|
|
98
112
|
resultStatus: 'ask_ignored_local_changes',
|
|
@@ -138,16 +152,23 @@
|
|
|
138
152
|
conclusion: assistantMessage || tx('Codex finished locally. There are no changes to sync back to Overleaf.', 'Codex 已完成本地处理,没有需要同步回 Overleaf 的改动。'),
|
|
139
153
|
status: runMode === 'ask' ? tr('modeAsk') : 'completed',
|
|
140
154
|
operations: [],
|
|
141
|
-
applyResults: [],
|
|
142
|
-
unchangedReason:
|
|
143
|
-
? tx('No file changes need to sync back to Overleaf.', '没有产生需要同步回 Overleaf 的文件改动。')
|
|
144
|
-
: formatUnsupportedLocalChangeSummary(unsupportedChanges),
|
|
155
|
+
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
156
|
+
unchangedReason: additionalSkippedEntries.find(item => item?.result?.reason)?.result.reason
|
|
157
|
+
|| (assistantMessage ? tx('No file changes need to sync back to Overleaf.', '没有产生需要同步回 Overleaf 的文件改动。') : formatUnsupportedLocalChangeSummary(unsupportedChanges)),
|
|
145
158
|
mode: runMode,
|
|
146
|
-
nextStep: tx('You can continue the conversation, or adjust @context and run again.', '可以继续追问,或调整 @context 后重新运行。')
|
|
159
|
+
nextStep: additionalSkippedEntries.length ? formatWritebackSkippedNextStep({ ok: false, applied: [], skipped: additionalSkippedEntries }) : tx('You can continue the conversation, or adjust @context and run again.', '可以继续追问,或调整 @context 后重新运行。')
|
|
147
160
|
});
|
|
148
161
|
return {
|
|
149
162
|
summaryLine: assistantMessage || tx('No changes to sync', '没有需要同步的改动'),
|
|
150
163
|
hasSkippedOperations: additionalSkippedEntries.length > 0,
|
|
164
|
+
settlement: buildSettlement({
|
|
165
|
+
operations: buildSyncApplyOperations(syncChanges, project),
|
|
166
|
+
applyResult: {
|
|
167
|
+
ok: additionalSkippedEntries.length === 0,
|
|
168
|
+
applied: [],
|
|
169
|
+
skipped: additionalSkippedEntries
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
151
172
|
audit: buildAuditSummaryFromApply({
|
|
152
173
|
operations: buildSyncApplyOperations(syncChanges, project),
|
|
153
174
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
@@ -182,6 +203,10 @@
|
|
|
182
203
|
return {
|
|
183
204
|
summaryLine: tx('Sync cancelled', '已取消同步'),
|
|
184
205
|
hasSkippedOperations: additionalSkippedEntries.length > 0,
|
|
206
|
+
settlement: buildSettlement({
|
|
207
|
+
operations,
|
|
208
|
+
applyResult: { ok: false, applied: [], skipped: additionalSkippedEntries }
|
|
209
|
+
}),
|
|
185
210
|
audit: buildAuditSummaryFromApply({
|
|
186
211
|
operations,
|
|
187
212
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
@@ -239,14 +264,23 @@
|
|
|
239
264
|
conclusion: assistantMessage || tx('No approved changes were written back to Overleaf.', '没有已确认的改动写回 Overleaf。'),
|
|
240
265
|
status: runMode === 'ask' ? tr('modeAsk') : 'completed',
|
|
241
266
|
operations: [],
|
|
242
|
-
applyResults: [],
|
|
243
|
-
unchangedReason:
|
|
267
|
+
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
268
|
+
unchangedReason: additionalSkippedEntries.find(item => item?.result?.reason)?.result.reason
|
|
269
|
+
|| tx('No approved file changes remain to sync back to Overleaf.', '没有剩余已确认的文件改动需要同步回 Overleaf。'),
|
|
244
270
|
mode: runMode,
|
|
245
|
-
nextStep: tx('You can continue the conversation, or adjust @context and run again.', '可以继续追问,或调整 @context 后重新运行。')
|
|
271
|
+
nextStep: additionalSkippedEntries.length ? formatWritebackSkippedNextStep({ ok: false, applied: [], skipped: additionalSkippedEntries }) : tx('You can continue the conversation, or adjust @context and run again.', '可以继续追问,或调整 @context 后重新运行。')
|
|
246
272
|
});
|
|
247
273
|
return {
|
|
248
274
|
summaryLine: tx('No changes applied', '没有应用改动'),
|
|
249
275
|
hasSkippedOperations: additionalSkippedEntries.length > 0,
|
|
276
|
+
settlement: buildSettlement({
|
|
277
|
+
operations: buildSyncApplyOperations(syncChanges, project),
|
|
278
|
+
applyResult: {
|
|
279
|
+
ok: additionalSkippedEntries.length === 0,
|
|
280
|
+
applied: [],
|
|
281
|
+
skipped: additionalSkippedEntries
|
|
282
|
+
}
|
|
283
|
+
}),
|
|
250
284
|
audit: buildAuditSummaryFromApply({
|
|
251
285
|
operations: buildSyncApplyOperations(syncChanges, project),
|
|
252
286
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
@@ -283,6 +317,7 @@
|
|
|
283
317
|
'已阻止写入:未确认 Overleaf Reviewing/Track Changes'
|
|
284
318
|
),
|
|
285
319
|
hasSkippedOperations: true,
|
|
320
|
+
settlement: buildSettlement({ operations, applyResult: blocked }),
|
|
286
321
|
audit: buildAuditSummaryFromApply({
|
|
287
322
|
operations,
|
|
288
323
|
applyResults: [blocked],
|
|
@@ -342,6 +377,7 @@
|
|
|
342
377
|
// pending promise is exposed so undoRun and the next runTask barrier on
|
|
343
378
|
// it — a mirror.sync landing AFTER an undo would otherwise push the
|
|
344
379
|
// pre-undo snapshot as the local baseline.
|
|
380
|
+
const mirrorSettlementTarget = getCurrentRunView?.();
|
|
345
381
|
pendingMirrorRefresh = refreshProjectMirrorAfterWriteback(project, applied, saveVerification)
|
|
346
382
|
.catch(error => {
|
|
347
383
|
appendRunEvent({
|
|
@@ -351,12 +387,25 @@
|
|
|
351
387
|
),
|
|
352
388
|
status: 'failed'
|
|
353
389
|
});
|
|
390
|
+
return 'failed';
|
|
391
|
+
})
|
|
392
|
+
.then(state => {
|
|
393
|
+
if (typeof onMirrorRefreshSettled !== 'function') return;
|
|
394
|
+
return onMirrorRefreshSettled({
|
|
395
|
+
recordId: mirrorSettlementTarget?.recordId || '',
|
|
396
|
+
sessionId: mirrorSettlementTarget?.sessionId || '',
|
|
397
|
+
runProjectId: mirrorSettlementTarget?.runProjectId || '',
|
|
398
|
+
state: state || 'not-attempted'
|
|
399
|
+
});
|
|
354
400
|
})
|
|
355
401
|
.finally(() => {
|
|
356
402
|
pendingMirrorRefresh = null;
|
|
357
403
|
});
|
|
358
404
|
const compileSummary = appliedPaths.length
|
|
359
|
-
? await autoRecompileAfterWriteback(appliedPaths, saveVerification
|
|
405
|
+
? await autoRecompileAfterWriteback(appliedPaths, saveVerification, {
|
|
406
|
+
autoRecompile: options.autoRecompile,
|
|
407
|
+
mode: runMode
|
|
408
|
+
}).catch(error => {
|
|
360
409
|
appendRunEvent({
|
|
361
410
|
title: tx(`Post-write compile failed: ${error.message}`, `写后编译出错:${error.message}`),
|
|
362
411
|
status: 'failed'
|
|
@@ -419,6 +468,17 @@
|
|
|
419
468
|
// Existing call sites that only read `summaryLine` / `hasSkippedOperations`
|
|
420
469
|
// / `audit` are unaffected.
|
|
421
470
|
applied,
|
|
471
|
+
settlement: buildSettlement({
|
|
472
|
+
operations,
|
|
473
|
+
applyResult: applied,
|
|
474
|
+
saveVerification,
|
|
475
|
+
mirror: {
|
|
476
|
+
state: appliedPaths.length && saveVerification?.state === 'verified_saved'
|
|
477
|
+
? 'pending'
|
|
478
|
+
: 'not_started'
|
|
479
|
+
},
|
|
480
|
+
compile: compileSummary
|
|
481
|
+
}),
|
|
422
482
|
audit: buildAuditSummaryFromApply({
|
|
423
483
|
operations,
|
|
424
484
|
applyResults: [applied],
|
|
@@ -447,8 +507,8 @@
|
|
|
447
507
|
if (result?.ok === true && !result?.state) {
|
|
448
508
|
return {
|
|
449
509
|
...result,
|
|
450
|
-
ok:
|
|
451
|
-
state: '
|
|
510
|
+
ok: false,
|
|
511
|
+
state: 'verified_quiet'
|
|
452
512
|
};
|
|
453
513
|
}
|
|
454
514
|
if (result?.state === 'unknown_timeout' || result?.state === 'unavailable') {
|
|
@@ -522,10 +582,10 @@
|
|
|
522
582
|
|
|
523
583
|
async function refreshProjectMirrorAfterWriteback(project = {}, applied = {}, saveVerification = {}) {
|
|
524
584
|
if (saveVerification?.state !== 'verified_saved') {
|
|
525
|
-
return;
|
|
585
|
+
return 'not-attempted';
|
|
526
586
|
}
|
|
527
587
|
if (!Array.isArray(applied?.applied) || !applied.applied.length) {
|
|
528
|
-
return;
|
|
588
|
+
return 'not-attempted';
|
|
529
589
|
}
|
|
530
590
|
|
|
531
591
|
if (await tryConfirmMirrorWriteback(applied)) {
|
|
@@ -536,7 +596,7 @@
|
|
|
536
596
|
),
|
|
537
597
|
status: 'completed'
|
|
538
598
|
});
|
|
539
|
-
return;
|
|
599
|
+
return 'complete';
|
|
540
600
|
}
|
|
541
601
|
|
|
542
602
|
appendRunEvent({
|
|
@@ -567,7 +627,7 @@
|
|
|
567
627
|
),
|
|
568
628
|
status: 'failed'
|
|
569
629
|
});
|
|
570
|
-
return;
|
|
630
|
+
return 'failed';
|
|
571
631
|
}
|
|
572
632
|
|
|
573
633
|
const syncedProject = mergeVerifiedAppliedFiles(freshProject, project, applied);
|
|
@@ -584,7 +644,7 @@
|
|
|
584
644
|
title: tx('Local Codex workspace refreshed. The next run will start from the latest Overleaf content.', '已刷新本地 Codex workspace,下一轮会从最新 Overleaf 内容开始。'),
|
|
585
645
|
status: 'completed'
|
|
586
646
|
});
|
|
587
|
-
return;
|
|
647
|
+
return 'complete';
|
|
588
648
|
}
|
|
589
649
|
|
|
590
650
|
const message = response?.error?.message || tx('native host did not respond', 'native host 没有响应');
|
|
@@ -595,6 +655,7 @@
|
|
|
595
655
|
),
|
|
596
656
|
status: 'failed'
|
|
597
657
|
});
|
|
658
|
+
return 'failed';
|
|
598
659
|
}
|
|
599
660
|
|
|
600
661
|
function mergeVerifiedAppliedFiles(freshProject = {}, originalProject = {}, applied = {}) {
|
|
@@ -615,11 +676,12 @@
|
|
|
615
676
|
return writebackController.formatUnsupportedLocalChangeSummary(changes, getLocale());
|
|
616
677
|
}
|
|
617
678
|
|
|
618
|
-
async function autoRecompileAfterWriteback(writtenPaths = [], saveVerification = {}) {
|
|
619
|
-
if (
|
|
620
|
-
|
|
679
|
+
async function autoRecompileAfterWriteback(writtenPaths = [], saveVerification = {}, options = {}) {
|
|
680
|
+
if (options.autoRecompile === false
|
|
681
|
+
|| (options.autoRecompile === undefined && getState().autoRecompile === false)) return null;
|
|
682
|
+
if ((options.mode || getState().mode) === 'ask') return null;
|
|
621
683
|
|
|
622
|
-
const CompileAdapter =
|
|
684
|
+
const CompileAdapter = compileAdapter;
|
|
623
685
|
if (!CompileAdapter) return null;
|
|
624
686
|
|
|
625
687
|
const hasCompilableFile = writtenPaths.some(filePath => CompileAdapter.isCompilableFile(filePath));
|
|
@@ -642,7 +704,8 @@
|
|
|
642
704
|
const result = await callPageBridge('triggerCompile', {
|
|
643
705
|
preferUiClick: true,
|
|
644
706
|
waitForSaveMs: 5000,
|
|
645
|
-
requireVerifiedSave: saveVerification?.state === 'verified_saved'
|
|
707
|
+
requireVerifiedSave: saveVerification?.state === 'verified_saved',
|
|
708
|
+
runProjectId: getCurrentRunView()?.runProjectId || getCurrentProjectId()
|
|
646
709
|
});
|
|
647
710
|
if (result?.ok) {
|
|
648
711
|
let logResult = null;
|
|
@@ -650,7 +713,8 @@
|
|
|
650
713
|
logResult = await callPageBridge('getCompileLog', {
|
|
651
714
|
triggerIfStale: false,
|
|
652
715
|
maxAgeMs: 30000,
|
|
653
|
-
waitForSaveMs: 0
|
|
716
|
+
waitForSaveMs: 0,
|
|
717
|
+
runProjectId: getCurrentRunView()?.runProjectId || getCurrentProjectId()
|
|
654
718
|
});
|
|
655
719
|
} catch (_error) {
|
|
656
720
|
logResult = null;
|
|
@@ -775,7 +839,8 @@
|
|
|
775
839
|
const result = await callPageBridge('getCompileLog', {
|
|
776
840
|
triggerIfStale: true,
|
|
777
841
|
maxAgeMs: 30000,
|
|
778
|
-
waitForSaveMs: 5000
|
|
842
|
+
waitForSaveMs: 5000,
|
|
843
|
+
runProjectId: getCurrentRunView()?.runProjectId || getCurrentProjectId()
|
|
779
844
|
});
|
|
780
845
|
|
|
781
846
|
if (!result?.ok) {
|
|
@@ -841,6 +906,9 @@
|
|
|
841
906
|
getState,
|
|
842
907
|
getCurrentRunView,
|
|
843
908
|
} = deps);
|
|
909
|
+
onMirrorRefreshSettled = deps.onMirrorRefreshSettled;
|
|
910
|
+
writebackSettlement = deps.writebackSettlement;
|
|
911
|
+
compileAdapter = deps.compileAdapter;
|
|
844
912
|
return {
|
|
845
913
|
applySyncChangesToOverleaf,
|
|
846
914
|
resolveCompileLogContext,
|
|
@@ -4,5 +4,9 @@
|
|
|
4
4
|
if (!window.CodexOverleafContentRuntime || typeof window.CodexOverleafContentRuntime.init !== 'function') {
|
|
5
5
|
throw new Error('Codex Overleaf content runtime did not load.');
|
|
6
6
|
}
|
|
7
|
-
window.
|
|
7
|
+
const registry = window.CodexOverleafLegacyGlobalRegistry;
|
|
8
|
+
if (!registry || typeof registry.create !== 'function') {
|
|
9
|
+
throw new Error('Codex Overleaf dependency registry did not load.');
|
|
10
|
+
}
|
|
11
|
+
window.CodexOverleafContentRuntime.init(registry.create(window));
|
|
8
12
|
})();
|
|
@@ -1008,7 +1008,12 @@
|
|
|
1008
1008
|
result: {
|
|
1009
1009
|
ok: true,
|
|
1010
1010
|
method: 'overleaf-editor-undo',
|
|
1011
|
-
undoClicks: result.clicks
|
|
1011
|
+
undoClicks: result.clicks,
|
|
1012
|
+
// undoEditorHistoryUntilContent returns ok only after the active
|
|
1013
|
+
// editor exactly matches this run's pre-write checkpoint. Preserve
|
|
1014
|
+
// that proof for the content-side lifecycle settlement.
|
|
1015
|
+
verified: true,
|
|
1016
|
+
verifiedContent: expectedByPath.get(path)
|
|
1012
1017
|
}
|
|
1013
1018
|
});
|
|
1014
1019
|
}
|