codex-overleaf-link 1.3.7 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -21
- package/extension/src/shared/agentTranscript.js +273 -10
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/failureReasons.js +578 -0
- package/extension/src/shared/i18n.js +139 -2
- package/extension/src/shared/pathRedaction.js +143 -0
- package/extension/src/shared/sessionState.js +113 -4
- package/extension/src/shared/storageDb.js +217 -4
- package/native-host/src/codexPromptAssembly.js +10 -2
- package/native-host/src/codexSessionRunner.js +33 -1
- package/package.json +1 -1
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
var VALID_TRACKED_CHANGE_STATUSES = {
|
|
42
42
|
pending: true,
|
|
43
43
|
accepted: true,
|
|
44
|
-
rejected: true
|
|
44
|
+
rejected: true,
|
|
45
|
+
needs_review: true
|
|
45
46
|
};
|
|
46
47
|
var SECRET_REDACTION_PATTERNS = [
|
|
47
48
|
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
|
|
@@ -237,6 +238,27 @@
|
|
|
237
238
|
function buildSessionRecord(input) {
|
|
238
239
|
var now = new Date().toISOString();
|
|
239
240
|
var titleSource = input.titleSource === 'manual' ? 'manual' : 'auto';
|
|
241
|
+
var updatedAt = typeof input.updatedAt === 'string' ? input.updatedAt : now;
|
|
242
|
+
// Welcome-panel + write-guard v1.3.8 add-on (Task 3): persist the four
|
|
243
|
+
// Recent-projects fields on every session record so the cross-project
|
|
244
|
+
// query (`listRecentProjectsAcrossAccount`) can filter / sort / render
|
|
245
|
+
// without touching the raw `task` text.
|
|
246
|
+
//
|
|
247
|
+
// `accountScopeId` is derived via the page-side injection point — T4
|
|
248
|
+
// installs the real implementation on `window.codexOverleafDeriveAccountScopeId`.
|
|
249
|
+
// For T3 the fallback is `() => null`; records persisted with a null scope
|
|
250
|
+
// surface `accountScopeUnavailable: true` and are filtered out of the
|
|
251
|
+
// cross-project query (spec §5.2 degraded mode).
|
|
252
|
+
var safeTaskSummary = typeof input.safeTaskSummary === 'string'
|
|
253
|
+
? input.safeTaskSummary
|
|
254
|
+
: deriveSafeTaskSummaryFromInput(input);
|
|
255
|
+
var accountScopeId = resolveAccountScopeId(input);
|
|
256
|
+
var accountScopeUnavailable = typeof input.accountScopeUnavailable === 'boolean'
|
|
257
|
+
? input.accountScopeUnavailable
|
|
258
|
+
: !accountScopeId;
|
|
259
|
+
var lastActivityAt = typeof input.lastActivityAt === 'string' && input.lastActivityAt
|
|
260
|
+
? input.lastActivityAt
|
|
261
|
+
: updatedAt;
|
|
240
262
|
return {
|
|
241
263
|
id: input.id || generateId('ses'),
|
|
242
264
|
projectId: input.projectId || '',
|
|
@@ -254,10 +276,77 @@
|
|
|
254
276
|
speedTier: typeof input.speedTier === 'string' ? input.speedTier : '',
|
|
255
277
|
requireReviewing: input.requireReviewing !== false,
|
|
256
278
|
createdAt: typeof input.createdAt === 'string' ? input.createdAt : now,
|
|
257
|
-
updatedAt:
|
|
279
|
+
updatedAt: updatedAt,
|
|
280
|
+
lastActivityAt: lastActivityAt,
|
|
281
|
+
accountScopeId: accountScopeId,
|
|
282
|
+
accountScopeUnavailable: accountScopeUnavailable,
|
|
283
|
+
safeTaskSummary: safeTaskSummary
|
|
258
284
|
};
|
|
259
285
|
}
|
|
260
286
|
|
|
287
|
+
function resolveAccountScopeId(input) {
|
|
288
|
+
if (input && typeof input.accountScopeId === 'string' && input.accountScopeId) {
|
|
289
|
+
return input.accountScopeId;
|
|
290
|
+
}
|
|
291
|
+
var globalScope = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : null);
|
|
292
|
+
var derive = globalScope && globalScope.window && globalScope.window.codexOverleafDeriveAccountScopeId;
|
|
293
|
+
if (!(derive instanceof Function)) {
|
|
294
|
+
// No injection installed (T3 fallback): tests + early page-load reads
|
|
295
|
+
// both land here. Returning `null` puts the session in degraded mode
|
|
296
|
+
// and excludes it from the cross-project query.
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
var value = derive();
|
|
301
|
+
return typeof value === 'string' && value ? value : null;
|
|
302
|
+
} catch (_error) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function deriveSafeTaskSummaryFromInput(input) {
|
|
308
|
+
var SessionState = loadSessionState();
|
|
309
|
+
if (!SessionState || !(SessionState.computeSafeTaskSummary instanceof Function)) {
|
|
310
|
+
return '';
|
|
311
|
+
}
|
|
312
|
+
var task = typeof input.task === 'string' && input.task ? input.task : '';
|
|
313
|
+
if (!task && Array.isArray(input.runs) && input.runs.length) {
|
|
314
|
+
var firstRun = input.runs[0];
|
|
315
|
+
if (firstRun && typeof firstRun.task === 'string') {
|
|
316
|
+
task = firstRun.task;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return SessionState.computeSafeTaskSummary(task);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Module-local lazy require of the sibling sessionState module. CommonJS
|
|
323
|
+
// resolves once; subsequent calls hit the require cache. We avoid an import
|
|
324
|
+
// at the top of the file so the module surface remains friendly to the
|
|
325
|
+
// page-side IIFE wrapper (no `require` available in that context — the page
|
|
326
|
+
// build uses the global `CodexOverleafSessionState` via the IIFE branch).
|
|
327
|
+
var _sessionStateCache = null;
|
|
328
|
+
function loadSessionState() {
|
|
329
|
+
if (_sessionStateCache !== null) {
|
|
330
|
+
return _sessionStateCache || null;
|
|
331
|
+
}
|
|
332
|
+
var globalScope = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : null);
|
|
333
|
+
if (globalScope && globalScope.CodexOverleafSessionState) {
|
|
334
|
+
_sessionStateCache = globalScope.CodexOverleafSessionState;
|
|
335
|
+
return _sessionStateCache;
|
|
336
|
+
}
|
|
337
|
+
if (typeof require === 'function') {
|
|
338
|
+
try {
|
|
339
|
+
_sessionStateCache = require('./sessionState');
|
|
340
|
+
return _sessionStateCache;
|
|
341
|
+
} catch (_error) {
|
|
342
|
+
_sessionStateCache = false;
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
_sessionStateCache = false;
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
261
350
|
function buildTurnRecord(input) {
|
|
262
351
|
var now = new Date().toISOString();
|
|
263
352
|
return {
|
|
@@ -556,6 +645,7 @@
|
|
|
556
645
|
speedTier: typeof run.speedTier === 'string' ? redactSecretLikeText(run.speedTier) : '',
|
|
557
646
|
status: normalizeRunStatus(run.status),
|
|
558
647
|
statusText: normalizeDisplayTextForStorage(run.statusText, SESSION_STORAGE_LIMITS.statusTextChars),
|
|
648
|
+
runProjectId: normalizeProjectPrefKey(run.runProjectId),
|
|
559
649
|
startedAt: typeof run.startedAt === 'string' ? redactSecretLikeText(run.startedAt) : '',
|
|
560
650
|
finishedAt: typeof run.finishedAt === 'string' ? redactSecretLikeText(run.finishedAt) : '',
|
|
561
651
|
events: compactRunEventsForStorage(run.events),
|
|
@@ -755,7 +845,19 @@
|
|
|
755
845
|
}
|
|
756
846
|
|
|
757
847
|
function normalizeRunStatus(status) {
|
|
758
|
-
|
|
848
|
+
// Welcome-panel + write-guard v1.3.8 add-on (Task 2/3): the run-status
|
|
849
|
+
// enum gained three post-navigation values. The storage normalizer must
|
|
850
|
+
// accept them so a settled run round-trips intact through `buildSessionRecord`.
|
|
851
|
+
// Unknown legacy values fall through to `completed` (the historical default).
|
|
852
|
+
return [
|
|
853
|
+
'pending',
|
|
854
|
+
'running',
|
|
855
|
+
'completed',
|
|
856
|
+
'failed',
|
|
857
|
+
'background_completed',
|
|
858
|
+
'needs_review_after_navigation',
|
|
859
|
+
'abandoned_after_navigation'
|
|
860
|
+
].indexOf(status) !== -1 ? status : 'completed';
|
|
759
861
|
}
|
|
760
862
|
|
|
761
863
|
function normalizeEventStatus(status) {
|
|
@@ -963,6 +1065,113 @@
|
|
|
963
1065
|
return result;
|
|
964
1066
|
}
|
|
965
1067
|
|
|
1068
|
+
// Welcome-panel + write-guard v1.3.8 add-on (Task 3): the Recent-projects
|
|
1069
|
+
// dashboard variant calls `listRecentProjectsAcrossAccount` to get the
|
|
1070
|
+
// sorted, deduped, capped list of projects in the current account scope.
|
|
1071
|
+
//
|
|
1072
|
+
// Contract (spec §5.6.1):
|
|
1073
|
+
// Input: { accountScopeId: string, limit?: number = 10 }
|
|
1074
|
+
// Output: Array<{ projectId, lastActivityAt, safeTaskSummary, primaryStatusBadge }>
|
|
1075
|
+
//
|
|
1076
|
+
// Fail-closed: if `accountScopeId` is falsy, returns `[]`. This is the
|
|
1077
|
+
// privacy floor — degraded mode must never leak across accounts.
|
|
1078
|
+
//
|
|
1079
|
+
// Implementation: full scan of the sessions store, group by `projectId`,
|
|
1080
|
+
// keep the row with the largest `lastActivityAt`, sort desc, cap at
|
|
1081
|
+
// `limit`. Full scan is acceptable for v1 given the small session count
|
|
1082
|
+
// (max ~12 per project × small number of projects). A future index on
|
|
1083
|
+
// `accountScopeId + lastActivityAt` is possible without a contract change.
|
|
1084
|
+
function listRecentProjectsAcrossAccount(options) {
|
|
1085
|
+
var accountScopeId = options && options.accountScopeId;
|
|
1086
|
+
var limit = options && Number.isFinite(options.limit) ? options.limit : 10;
|
|
1087
|
+
if (!accountScopeId) {
|
|
1088
|
+
return Promise.resolve([]);
|
|
1089
|
+
}
|
|
1090
|
+
return getAllSessions().then(function (all) {
|
|
1091
|
+
return filterRecentProjectsAcrossAccount(all, { accountScopeId: accountScopeId, limit: limit });
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Pure helper extracted from `listRecentProjectsAcrossAccount` so the
|
|
1096
|
+
// filtering / dedupe / sort / cap behavior can be tested without an
|
|
1097
|
+
// IndexedDB stub. Same contract as the async wrapper; takes the raw session
|
|
1098
|
+
// records as an array instead of opening the database.
|
|
1099
|
+
function filterRecentProjectsAcrossAccount(sessions, options) {
|
|
1100
|
+
var accountScopeId = options && options.accountScopeId;
|
|
1101
|
+
var limit = options && Number.isFinite(options.limit) ? options.limit : 10;
|
|
1102
|
+
if (!accountScopeId) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
var byProject = {}; // projectId → session
|
|
1106
|
+
var all = Array.isArray(sessions) ? sessions : [];
|
|
1107
|
+
for (var i = 0; i < all.length; i++) {
|
|
1108
|
+
var s = all[i];
|
|
1109
|
+
if (!s) continue;
|
|
1110
|
+
if (s.accountScopeId !== accountScopeId) continue;
|
|
1111
|
+
if (typeof s.lastActivityAt !== 'string' || !s.lastActivityAt) continue;
|
|
1112
|
+
if (typeof s.projectId !== 'string' || !s.projectId) continue;
|
|
1113
|
+
var prev = byProject[s.projectId];
|
|
1114
|
+
if (!prev || prev.lastActivityAt < s.lastActivityAt) {
|
|
1115
|
+
byProject[s.projectId] = s;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
var survivors = [];
|
|
1119
|
+
var keys = Object.keys(byProject);
|
|
1120
|
+
for (var j = 0; j < keys.length; j++) {
|
|
1121
|
+
survivors.push(byProject[keys[j]]);
|
|
1122
|
+
}
|
|
1123
|
+
survivors.sort(function (a, b) {
|
|
1124
|
+
return b.lastActivityAt.localeCompare(a.lastActivityAt);
|
|
1125
|
+
});
|
|
1126
|
+
var rows = [];
|
|
1127
|
+
for (var k = 0; k < survivors.length && k < limit; k++) {
|
|
1128
|
+
var session = survivors[k];
|
|
1129
|
+
rows.push({
|
|
1130
|
+
projectId: session.projectId,
|
|
1131
|
+
lastActivityAt: session.lastActivityAt,
|
|
1132
|
+
safeTaskSummary: typeof session.safeTaskSummary === 'string' ? session.safeTaskSummary : '',
|
|
1133
|
+
primaryStatusBadge: derivePrimaryStatusBadge(session)
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
return rows;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Per spec §5.10:
|
|
1140
|
+
// 1. trackedChangeStatus if set (pending/accepted/rejected/needs_review)
|
|
1141
|
+
// 2. else run status (running/completed/failed/background_completed/
|
|
1142
|
+
// needs_review_after_navigation/abandoned_after_navigation)
|
|
1143
|
+
// 3. fallback `pending`.
|
|
1144
|
+
function derivePrimaryStatusBadge(session) {
|
|
1145
|
+
var runs = session && Array.isArray(session.runs) ? session.runs : [];
|
|
1146
|
+
if (!runs.length) return 'pending';
|
|
1147
|
+
var latestRun = runs[runs.length - 1];
|
|
1148
|
+
if (!latestRun) return 'pending';
|
|
1149
|
+
if (typeof latestRun.trackedChangeStatus === 'string' && latestRun.trackedChangeStatus) {
|
|
1150
|
+
return latestRun.trackedChangeStatus;
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof latestRun.status === 'string' && latestRun.status) {
|
|
1153
|
+
return latestRun.status;
|
|
1154
|
+
}
|
|
1155
|
+
return 'pending';
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Full scan of the sessions store, used by the cross-project query above.
|
|
1159
|
+
// We deliberately do not go through `getAllByIndex` because the existing
|
|
1160
|
+
// session indexes are keyed by `projectId` / `updatedAt` — neither matches
|
|
1161
|
+
// the account-scope filter. A direct `getAll` over the whole store is the
|
|
1162
|
+
// simplest correct read.
|
|
1163
|
+
function getAllSessions() {
|
|
1164
|
+
return openDb().then(function (db) {
|
|
1165
|
+
return new Promise(function (resolve, reject) {
|
|
1166
|
+
var tx = db.transaction('sessions', 'readonly');
|
|
1167
|
+
var store = tx.objectStore('sessions');
|
|
1168
|
+
var request = store.getAll();
|
|
1169
|
+
request.onsuccess = function (event) { resolve(event.target.result || []); };
|
|
1170
|
+
request.onerror = function (event) { reject(event.target.error); };
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
966
1175
|
function buildActiveSessionByProject(existing, projectId, sessionId) {
|
|
967
1176
|
var result = {};
|
|
968
1177
|
if (existing && typeof existing === 'object') {
|
|
@@ -1068,6 +1277,10 @@
|
|
|
1068
1277
|
buildAuditLogRecord: buildAuditLogRecord,
|
|
1069
1278
|
extractLightweightPrefs: extractLightweightPrefs,
|
|
1070
1279
|
buildActiveSessionByProject: buildActiveSessionByProject,
|
|
1071
|
-
createEventBuffer: createEventBuffer
|
|
1280
|
+
createEventBuffer: createEventBuffer,
|
|
1281
|
+
listRecentProjectsAcrossAccount: listRecentProjectsAcrossAccount,
|
|
1282
|
+
filterRecentProjectsAcrossAccount: filterRecentProjectsAcrossAccount,
|
|
1283
|
+
derivePrimaryStatusBadge: derivePrimaryStatusBadge,
|
|
1284
|
+
getAllSessions: getAllSessions
|
|
1072
1285
|
};
|
|
1073
1286
|
});
|
|
@@ -193,11 +193,15 @@ function formatWriteExpectation({ mode = 'auto', task = '', skillInvocation = nu
|
|
|
193
193
|
return '- This is read-only. Inspect and explain; do not edit files.';
|
|
194
194
|
}
|
|
195
195
|
if (requestImpliesFileChanges(task)) {
|
|
196
|
-
|
|
196
|
+
const lines = [
|
|
197
197
|
'- The request asks for file changes. You must edit the local workspace when you find concrete fixes.',
|
|
198
198
|
'- Do not stop at a suggestion list or say you will not modify files unless no safe concrete edit exists.',
|
|
199
199
|
'- If you intentionally leave files unchanged, explain the specific blocker in the final answer.'
|
|
200
|
-
]
|
|
200
|
+
];
|
|
201
|
+
if (requestTargetsAbstract(task)) {
|
|
202
|
+
lines.push('- The request targets the abstract/summary. Locate the LaTeX abstract environment or summary paragraph and edit that local project file directly.');
|
|
203
|
+
}
|
|
204
|
+
return lines.join('\n');
|
|
201
205
|
}
|
|
202
206
|
return [
|
|
203
207
|
'- This mode can write. If the request asks for corrections, revisions, fixes, polishing, updates, or implementation, edit the local workspace directly.',
|
|
@@ -209,6 +213,10 @@ function requestImpliesFileChanges(task = '') {
|
|
|
209
213
|
return /修正|修复|修改|改[一-龥]*|完善|补全|补充|润色|重写|改写|整理|调整|应用|写入|fix|correct|repair|revise|edit|update|rewrite|polish|improve|apply/i.test(String(task || ''));
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
function requestTargetsAbstract(task = '') {
|
|
217
|
+
return /摘要|abstract|summary/i.test(String(task || ''));
|
|
218
|
+
}
|
|
219
|
+
|
|
212
220
|
function normalizeFocusFiles(value) {
|
|
213
221
|
const seen = new Set();
|
|
214
222
|
const files = [];
|
|
@@ -821,13 +821,22 @@ function runCodexAppServerSession(input) {
|
|
|
821
821
|
|
|
822
822
|
if (message.method) {
|
|
823
823
|
recordAssistantMessage(message);
|
|
824
|
-
|
|
824
|
+
// For `error` events surface the actual error text as the visible
|
|
825
|
+
// title so the run timeline reads "Reconnecting... 2/5" instead of
|
|
826
|
+
// a generic "error". Other methods continue to use the method name.
|
|
827
|
+
const eventTitle = message.method === 'error'
|
|
828
|
+
? (extractCodexAppServerErrorMessage(message.params) || message.method)
|
|
829
|
+
: message.method;
|
|
830
|
+
emitCodexEvent(input.emit, 'codex.session.event', eventTitle, {
|
|
825
831
|
method: message.method,
|
|
826
832
|
params: message.params || {}
|
|
827
833
|
}, inferNotificationStatus(message));
|
|
828
834
|
if (message.method === 'turn/completed' && (!activeTurnId || message.params?.turn?.id === activeTurnId || message.params?.turnId === activeTurnId)) {
|
|
829
835
|
succeed();
|
|
830
836
|
}
|
|
837
|
+
if (message.method === 'error' && isTransientCodexAppServerError(message.params)) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
831
840
|
if (message.method === 'error') {
|
|
832
841
|
fail(new Error(message.params?.error?.message || 'Codex turn failed'));
|
|
833
842
|
}
|
|
@@ -1211,12 +1220,35 @@ function emitCodexEvent(emit, type, title, detail = {}, status = 'running') {
|
|
|
1211
1220
|
}
|
|
1212
1221
|
|
|
1213
1222
|
function inferNotificationStatus(message) {
|
|
1223
|
+
if (message.method === 'error' && isTransientCodexAppServerError(message.params)) {
|
|
1224
|
+
return 'warning';
|
|
1225
|
+
}
|
|
1214
1226
|
if (/completed|updated|delta|started/.test(message.method || '')) {
|
|
1215
1227
|
return /completed/.test(message.method || '') ? 'completed' : 'running';
|
|
1216
1228
|
}
|
|
1217
1229
|
return 'running';
|
|
1218
1230
|
}
|
|
1219
1231
|
|
|
1232
|
+
function isTransientCodexAppServerError(params = {}) {
|
|
1233
|
+
const message = extractCodexAppServerErrorMessage(params);
|
|
1234
|
+
return /^Reconnecting(?:\.\.\.|…)?\s+\d+\s*\/\s*\d+\.?$/i.test(message);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function extractCodexAppServerErrorMessage(params = {}) {
|
|
1238
|
+
const candidates = [
|
|
1239
|
+
params?.error?.message,
|
|
1240
|
+
params?.message,
|
|
1241
|
+
params?.title,
|
|
1242
|
+
params?.error
|
|
1243
|
+
];
|
|
1244
|
+
for (const candidate of candidates) {
|
|
1245
|
+
if (typeof candidate === 'string' && candidate.trim()) {
|
|
1246
|
+
return candidate.trim();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return '';
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1220
1252
|
function normalizeReasoningEffort(value) {
|
|
1221
1253
|
return ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'].includes(value) ? value : null;
|
|
1222
1254
|
}
|