codex-overleaf-link 2.1.5 → 2.2.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.
Files changed (66) hide show
  1. package/README.md +27 -23
  2. package/extension/bootstrap/background.js +14 -1
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/runtime-manifest.json +10 -0
  5. package/extension/src/background.js +507 -8
  6. package/extension/src/backgroundUpdateCoordinator.js +284 -19
  7. package/extension/src/content/activeTurnControl.js +179 -0
  8. package/extension/src/content/composerPanel.js +14 -5
  9. package/extension/src/content/contentRuntime.js +330 -309
  10. package/extension/src/content/diagnosticsPanel.js +132 -1
  11. package/extension/src/content/markdownDomRenderer.js +451 -0
  12. package/extension/src/content/markdownText.js +86 -7
  13. package/extension/src/content/mathText.js +49 -4
  14. package/extension/src/content/modelPicker.js +14 -5
  15. package/extension/src/content/nativeChannel.js +7 -1
  16. package/extension/src/content/panelRenderer.js +112 -12
  17. package/extension/src/content/pendingInputView.js +106 -0
  18. package/extension/src/content/projectProviderSelection.js +68 -0
  19. package/extension/src/content/projectSessionCleanup.js +4 -0
  20. package/extension/src/content/providerSettingsCoordinator.js +170 -22
  21. package/extension/src/content/providerSettingsDialog.js +2 -2
  22. package/extension/src/content/recentProjects.js +7 -2
  23. package/extension/src/content/runGuidanceController.js +70 -0
  24. package/extension/src/content/runGuidanceView.js +48 -0
  25. package/extension/src/content/runQueueScheduler.js +260 -0
  26. package/extension/src/content/runTimelineView.js +33 -80
  27. package/extension/src/content/sessionManager.js +2 -2
  28. package/extension/src/content/sessionPersistence.js +62 -0
  29. package/extension/src/content/settingsPanel.js +1 -1
  30. package/extension/src/content/updateNotice.js +80 -1
  31. package/extension/src/page/writebackRouter.js +3 -9
  32. package/extension/src/shared/compatibility.js +2 -1
  33. package/extension/src/shared/i18n.js +38 -0
  34. package/extension/src/shared/providerProfiles.js +3 -2
  35. package/extension/src/shared/runInputQueue.js +253 -0
  36. package/extension/src/shared/sessionState.js +45 -22
  37. package/extension/src/shared/storageDb.js +39 -44
  38. package/extension/src/shared/storageMigration.js +123 -3
  39. package/extension/src/shared/storageRunActions.js +34 -1
  40. package/extension/src/shared/updateConsent.js +15 -4
  41. package/extension/styles/panel.css +319 -20
  42. package/extension/vendor/katex/katex.min.css +1 -1
  43. package/extension/vendor/markdown-it/LICENSE +22 -0
  44. package/extension/vendor/markdown-it/SOURCE.json +7 -0
  45. package/extension/vendor/markdown-it/VERSION +1 -0
  46. package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
  47. package/native-host/src/anthropicBridgeRequest.js +3 -3
  48. package/native-host/src/anthropicBridgeResponse.js +58 -17
  49. package/native-host/src/anthropicMessagesBridge.js +156 -23
  50. package/native-host/src/chatBridgeRequest.js +1 -1
  51. package/native-host/src/chatBridgeResponse.js +12 -8
  52. package/native-host/src/chatCompletionsBridge.js +22 -40
  53. package/native-host/src/codexPromptAssembly.js +3 -1
  54. package/native-host/src/codexProviderLaunch.js +6 -3
  55. package/native-host/src/codexSessionRunner.js +65 -65
  56. package/native-host/src/codexSessionTiming.js +45 -0
  57. package/native-host/src/index.js +58 -3
  58. package/native-host/src/managedInstall.js +45 -7
  59. package/native-host/src/providerProfile.js +2 -2
  60. package/native-host/src/providerStreamLifecycle.js +100 -0
  61. package/native-host/src/responsesPassthroughBridge.js +636 -0
  62. package/native-host/src/taskRunnerRuntime.js +81 -2
  63. package/native-host/src/updateArchive.js +4 -3
  64. package/native-host/src/updateManager.js +306 -19
  65. package/native-host/src/updateTrust.js +1 -1
  66. package/package.json +3 -1
@@ -528,15 +528,9 @@
528
528
  }
529
529
  const CANCELLED_RACE_SENTINEL = Symbol('writebackRouter.cancelled');
530
530
  async function raceOpAgainstCancellation(opPromise) {
531
- const racer = createCancellationRacer();
532
- try {
533
- return await Promise.race([
534
- opPromise,
535
- racer.promise.then(() => CANCELLED_RACE_SENTINEL)
536
- ]);
537
- } finally {
538
- racer.dispose();
539
- }
531
+ // Once an editor operation starts, preserve its actual result and undo
532
+ // metadata. The loop's pre-operation cancellation check stops the tail.
533
+ return await opPromise;
540
534
  }
541
535
 
542
536
  for (const rawOperation of operations) {
@@ -12,7 +12,7 @@
12
12
  const MIN_NATIVE_VERSION = '1.0.0';
13
13
  const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
14
14
  const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
15
- const BUILD_TARGET_VERSION = '2.1.5';
15
+ const BUILD_TARGET_VERSION = '2.2.0';
16
16
  const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
17
17
  const REQUIRED_CAPABILITIES = Object.freeze([
18
18
  'bridgePing',
@@ -21,6 +21,7 @@
21
21
  'mirrorStatus',
22
22
  'codexRun',
23
23
  'codexCancel',
24
+ 'codexSteer',
24
25
  'codexModels',
25
26
  'historyClearPlugin',
26
27
  'localSkills',
@@ -292,6 +292,7 @@
292
292
  providerSettingsHelp: 'Configure experimental third-party model APIs. Compatibility varies by provider and gateway.',
293
293
  providerSettingsConfigure: 'Configure',
294
294
  providerSettingsBuiltin: 'Built-in Codex is active',
295
+ providerSettingsLoading: 'Checking current project provider…',
295
296
  storageUsageLoading: 'Calculating usage\u2026',
296
297
  storageClearAll: 'Clear all history\u2026',
297
298
  storageClearAllHelp: 'Removes every stored session, run and audit record for all projects. Project settings and rules are kept.',
@@ -344,6 +345,24 @@
344
345
  warmMirrorStaleBlockedTitle: 'Blocked: local workspace is stale',
345
346
  send: 'Send',
346
347
  cancelRun: 'Cancel Current Task',
348
+ queueNextInput: 'Queue follow-up',
349
+ queuedInputQueued: 'Queued',
350
+ queuedInputPaused: 'Queue paused',
351
+ queuedInputGuiding: 'Sending guidance',
352
+ queuedInputExecuting: 'Running',
353
+ queuedInputGuide: 'Guide',
354
+ queuedInputGuideTitle: 'Send this message into the active Codex turn now.',
355
+ queuedInputGuideUnavailable: 'The active turn is not ready for guidance yet.',
356
+ queuedInputGuideFailed: 'Guidance could not be delivered. The message remains queued.',
357
+ queuedInputGuided: 'Follow-up guidance was delivered to the active Codex turn.',
358
+ queuedInputAdded: 'Follow-up queued. It will run after this task settles.',
359
+ queuedInputResume: 'Resume',
360
+ queuedInputRemove: 'Remove',
361
+ queuedInputAttachmentsUnsupported: 'Queued follow-ups currently support text and @context. Remove binary attachments before queuing.',
362
+ queuedInputError_empty_input: 'Enter a follow-up first.',
363
+ queuedInputError_input_too_long: 'This follow-up is too long to queue.',
364
+ queuedInputError_queue_full: 'The follow-up queue is full.',
365
+ queuedInputError_queue_too_large: 'The follow-up queue reached its storage limit.',
347
366
  contextTitle: '@ Context',
348
367
  refreshFileList: 'Refresh file list',
349
368
  contextStatus: 'Type @ to add files, or click +: files, attachments (PDF/images), @compile-log.',
@@ -786,6 +805,7 @@
786
805
  providerSettingsHelp: '配置实验性第三方模型 API;兼容性取决于具体模型服务与网关。',
787
806
  providerSettingsConfigure: '配置',
788
807
  providerSettingsBuiltin: '当前使用内置 Codex',
808
+ providerSettingsLoading: '正在读取当前项目的模型服务…',
789
809
  storageUsageLoading: '正在计算占用…',
790
810
  storageClearAll: '清空全部历史…',
791
811
  storageClearAllHelp: '删除所有项目已存储的全部会话、运行与审计记录。项目设置与规则会保留。',
@@ -838,6 +858,24 @@
838
858
  warmMirrorStaleBlockedTitle: '已阻止:本地 workspace 过期',
839
859
  send: '发送',
840
860
  cancelRun: '中断当前任务',
861
+ queueNextInput: '加入后续队列',
862
+ queuedInputQueued: '已排队',
863
+ queuedInputPaused: '队列已暂停',
864
+ queuedInputGuiding: '正在发送引导',
865
+ queuedInputExecuting: '正在运行',
866
+ queuedInputGuide: '立即引导',
867
+ queuedInputGuideTitle: '立即把这条消息发送到当前 Codex turn。',
868
+ queuedInputGuideUnavailable: '当前 turn 尚未进入可引导状态。',
869
+ queuedInputGuideFailed: '引导消息未送达,消息已保留在队列中。',
870
+ queuedInputGuided: '后续引导已发送到当前 Codex turn。',
871
+ queuedInputAdded: '后续任务已排队,将在当前任务完整结束后执行。',
872
+ queuedInputResume: '恢复队列',
873
+ queuedInputRemove: '删除',
874
+ queuedInputAttachmentsUnsupported: '当前排队消息支持文本和 @context;请先移除二进制附件。',
875
+ queuedInputError_empty_input: '请先输入后续任务。',
876
+ queuedInputError_input_too_long: '这条后续任务太长,无法排队。',
877
+ queuedInputError_queue_full: '后续任务队列已满。',
878
+ queuedInputError_queue_too_large: '后续任务队列已达到存储上限。',
841
879
  contextTitle: '@ 上下文',
842
880
  refreshFileList: '刷新文件列表',
843
881
  contextStatus: '输入 @ 添加文件,或点 +:文件、附件(PDF/图片)、@compile-log。',
@@ -159,7 +159,7 @@
159
159
  anthropicThinkingMode: 'budget',
160
160
  anthropicPromptCaching: false,
161
161
  impersonateClaudeCode: false,
162
- maxOutputTokens: 32768,
162
+ maxOutputTokens: 65536,
163
163
  hasSecret: false,
164
164
  secretUpdatedAt: 0,
165
165
  endpointDisclosureHost: '',
@@ -280,7 +280,8 @@
280
280
 
281
281
  function normalizeMaxOutputTokens(value) {
282
282
  const number = normalizeInteger(value);
283
- return !number || number === 8192 ? 32768 : number;
283
+ if (!number || number === 8192 || number === 32768) return 65536;
284
+ return Math.min(65536, Math.max(256, number));
284
285
  }
285
286
 
286
287
  return {
@@ -0,0 +1,253 @@
1
+ (function initCodexOverleafRunInputQueue(root, factory) {
2
+ if (typeof module === 'object' && module.exports) {
3
+ module.exports = factory();
4
+ } else {
5
+ root.CodexOverleafRunInputQueue = factory();
6
+ }
7
+ })(typeof window !== 'undefined' ? window : globalThis, function runInputQueueFactory() {
8
+ 'use strict';
9
+
10
+ const MAX_ITEMS = 20;
11
+ const MAX_TEXT_CHARS = 12000;
12
+ const MAX_QUEUE_BYTES = 128 * 1024;
13
+ const ACTIVE_STATUSES = new Set(['claimed', 'executing', 'steering']);
14
+ const VALID_STATUSES = new Set(['queued', 'paused', ...ACTIVE_STATUSES]);
15
+
16
+ function normalizeQueue(items, options = {}) {
17
+ const seen = new Set();
18
+ const normalized = [];
19
+ for (const raw of Array.isArray(items) ? items : []) {
20
+ const text = String(raw?.text || '').trim().slice(0, MAX_TEXT_CHARS);
21
+ const id = String(raw?.id || '').trim();
22
+ if (!id || !text || seen.has(id)) {
23
+ continue;
24
+ }
25
+ seen.add(id);
26
+ let status = VALID_STATUSES.has(raw.status) ? raw.status : 'queued';
27
+ if (options.recoverActive === true && ACTIVE_STATUSES.has(status)) {
28
+ status = 'paused';
29
+ }
30
+ normalized.push({
31
+ id,
32
+ clientUserMessageId: String(raw.clientUserMessageId || id),
33
+ text,
34
+ status,
35
+ createdAt: normalizeTimestamp(raw.createdAt),
36
+ updatedAt: normalizeTimestamp(raw.updatedAt),
37
+ sourceRunId: String(raw.sourceRunId || ''),
38
+ linkedRunId: status === 'executing' ? String(raw.linkedRunId || '') : '',
39
+ claimToken: status === 'claimed' || status === 'executing' ? String(raw.claimToken || '') : '',
40
+ pauseReason: status === 'paused' ? String(raw.pauseReason || '') : '',
41
+ payload: normalizePayload(raw.payload)
42
+ });
43
+ if (normalized.length >= MAX_ITEMS) {
44
+ break;
45
+ }
46
+ }
47
+ return trimToBudget(normalized);
48
+ }
49
+
50
+ function enqueue(items, input = {}, deps = {}) {
51
+ const queue = normalizeQueue(items);
52
+ const text = String(input.text || '').trim();
53
+ if (!text) {
54
+ return failure(queue, 'empty_input');
55
+ }
56
+ if (text.length > MAX_TEXT_CHARS) {
57
+ return failure(queue, 'input_too_long');
58
+ }
59
+ if (queue.length >= MAX_ITEMS) {
60
+ return failure(queue, 'queue_full');
61
+ }
62
+ const idFactory = deps.randomUUID || (() => 'pending_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8));
63
+ const nowFactory = deps.now || (() => new Date().toISOString());
64
+ const id = String(input.id || idFactory());
65
+ const timestamp = String(nowFactory());
66
+ const item = {
67
+ id,
68
+ clientUserMessageId: String(input.clientUserMessageId || id),
69
+ text,
70
+ status: input.status === 'paused' ? 'paused' : 'queued',
71
+ createdAt: timestamp,
72
+ updatedAt: timestamp,
73
+ sourceRunId: String(input.sourceRunId || ''),
74
+ linkedRunId: '',
75
+ claimToken: '',
76
+ pauseReason: input.status === 'paused' ? String(input.pauseReason || '') : '',
77
+ payload: normalizePayload(input.payload)
78
+ };
79
+ const next = [...queue, item];
80
+ if (estimateBytes(next) > MAX_QUEUE_BYTES) {
81
+ return failure(queue, 'queue_too_large');
82
+ }
83
+ return { ok: true, queue: next, item };
84
+ }
85
+
86
+ function remove(items, id) {
87
+ return normalizeQueue(items).filter(item => item.id !== id);
88
+ }
89
+
90
+ function markSteering(items, id) {
91
+ return update(items, id, item => ({
92
+ ...item,
93
+ status: 'steering',
94
+ updatedAt: new Date().toISOString(),
95
+ pauseReason: ''
96
+ }));
97
+ }
98
+
99
+ function returnToQueue(items, id, reason = '') {
100
+ return update(items, id, item => ({
101
+ ...item,
102
+ status: 'queued',
103
+ updatedAt: new Date().toISOString(),
104
+ linkedRunId: '',
105
+ claimToken: '',
106
+ pauseReason: String(reason || '')
107
+ }));
108
+ }
109
+
110
+ function claimNext(items, deps = {}) {
111
+ const queue = normalizeQueue(items);
112
+ const index = queue.findIndex(item => item.status === 'queued');
113
+ if (index < 0) {
114
+ return { ok: false, queue, item: null };
115
+ }
116
+ const tokenFactory = deps.randomUUID || (() => 'claim_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8));
117
+ const item = {
118
+ ...queue[index],
119
+ status: 'claimed',
120
+ claimToken: String(tokenFactory()),
121
+ updatedAt: new Date().toISOString(),
122
+ pauseReason: ''
123
+ };
124
+ queue[index] = item;
125
+ return { ok: true, queue, item };
126
+ }
127
+
128
+ function markExecuting(items, id, linkedRunId) {
129
+ return update(items, id, item => ({
130
+ ...item,
131
+ status: 'executing',
132
+ linkedRunId: String(linkedRunId || ''),
133
+ updatedAt: new Date().toISOString()
134
+ }));
135
+ }
136
+
137
+ function pauseAll(items, reason = '') {
138
+ return normalizeQueue(items).map(item => ({
139
+ ...item,
140
+ status: 'paused',
141
+ linkedRunId: '',
142
+ claimToken: '',
143
+ pauseReason: String(reason || ''),
144
+ updatedAt: new Date().toISOString()
145
+ }));
146
+ }
147
+
148
+ function resumeAll(items) {
149
+ return normalizeQueue(items).map(item => ({
150
+ ...item,
151
+ status: 'queued',
152
+ linkedRunId: '',
153
+ claimToken: '',
154
+ pauseReason: '',
155
+ updatedAt: new Date().toISOString()
156
+ }));
157
+ }
158
+
159
+ function update(items, id, updater) {
160
+ return normalizeQueue(items).map(item => item.id === id ? updater(item) : item);
161
+ }
162
+
163
+ function normalizePayload(payload = {}) {
164
+ return {
165
+ mode: ['ask', 'confirm', 'auto'].includes(payload.mode) ? payload.mode : 'ask',
166
+ providerId: String(payload.providerId || 'builtin').slice(0, 160),
167
+ providerRevision: String(payload.providerRevision || '').slice(0, 160),
168
+ model: String(payload.model || '').slice(0, 160),
169
+ reasoningEffort: String(payload.reasoningEffort || '').slice(0, 32),
170
+ speedTier: payload.speedTier === 'fast' ? 'fast' : 'standard',
171
+ autoRecompile: payload.autoRecompile !== false,
172
+ requireReviewing: payload.requireReviewing !== false,
173
+ focusFiles: (Array.isArray(payload.focusFiles) ? payload.focusFiles : [])
174
+ .map(path => String(path || '').replace(/\\/g, '/').replace(/^\/+/, '').trim())
175
+ .filter(Boolean)
176
+ .slice(0, 100)
177
+ };
178
+ }
179
+
180
+ function normalizeTimestamp(value) {
181
+ return typeof value === 'string' && value ? value : new Date().toISOString();
182
+ }
183
+
184
+ function trimToBudget(items) {
185
+ const queue = items.slice(-MAX_ITEMS);
186
+ while (queue.length && estimateBytes(queue) > MAX_QUEUE_BYTES) {
187
+ queue.shift();
188
+ }
189
+ return queue;
190
+ }
191
+
192
+ function estimateBytes(value) {
193
+ const text = JSON.stringify(value);
194
+ return typeof TextEncoder !== 'undefined'
195
+ ? new TextEncoder().encode(text).length
196
+ : text.length * 2;
197
+ }
198
+
199
+ function compactForStorage(items, helpers = {}) {
200
+ const normalizeField = helpers.normalizeField || (value => String(value || ''));
201
+ const normalizeDisplay = helpers.normalizeDisplay || normalizeField;
202
+ const normalizePaths = helpers.normalizePaths || (value => normalizePayload({ focusFiles: value }).focusFiles);
203
+ return (Array.isArray(items) ? items : [])
204
+ .slice(-MAX_ITEMS)
205
+ .map(item => {
206
+ const payload = item?.payload && typeof item.payload === 'object' ? item.payload : {};
207
+ return {
208
+ id: normalizeField(item?.id, 160),
209
+ clientUserMessageId: normalizeField(item?.clientUserMessageId, 160),
210
+ text: normalizeDisplay(item?.text, MAX_TEXT_CHARS),
211
+ status: VALID_STATUSES.has(item?.status) ? item.status : 'queued',
212
+ createdAt: typeof item?.createdAt === 'string' ? item.createdAt : '',
213
+ updatedAt: typeof item?.updatedAt === 'string' ? item.updatedAt : '',
214
+ sourceRunId: normalizeField(item?.sourceRunId, 160),
215
+ linkedRunId: normalizeField(item?.linkedRunId, 160),
216
+ claimToken: normalizeField(item?.claimToken, 160),
217
+ pauseReason: normalizeField(item?.pauseReason, 160),
218
+ payload: {
219
+ mode: typeof payload.mode === 'string' ? payload.mode : 'ask',
220
+ providerId: normalizeField(payload.providerId, 160) || 'builtin',
221
+ providerRevision: normalizeField(payload.providerRevision, 160),
222
+ model: normalizeField(payload.model, 160),
223
+ reasoningEffort: normalizeField(payload.reasoningEffort, 32),
224
+ speedTier: payload.speedTier === 'fast' ? 'fast' : 'standard',
225
+ autoRecompile: payload.autoRecompile !== false,
226
+ requireReviewing: payload.requireReviewing !== false,
227
+ focusFiles: normalizePaths(payload.focusFiles)
228
+ }
229
+ };
230
+ })
231
+ .filter(item => item.id && item.text);
232
+ }
233
+
234
+ function failure(queue, code) {
235
+ return { ok: false, queue, item: null, error: { code } };
236
+ }
237
+
238
+ return {
239
+ MAX_ITEMS,
240
+ MAX_QUEUE_BYTES,
241
+ MAX_TEXT_CHARS,
242
+ claimNext,
243
+ compactForStorage,
244
+ enqueue,
245
+ markExecuting,
246
+ markSteering,
247
+ normalizeQueue,
248
+ pauseAll,
249
+ remove,
250
+ resumeAll,
251
+ returnToQueue
252
+ };
253
+ });
@@ -127,6 +127,9 @@
127
127
  if (!VALID_SPEED_TIERS.has(state.speedTier)) {
128
128
  state.speedTier = DEFAULT_PANEL_STATE.speedTier;
129
129
  }
130
+ state.providerId = typeof state.providerId === 'string' && state.providerId.trim()
131
+ ? state.providerId.trim()
132
+ : 'builtin';
130
133
  if (!VALID_LOCALES.has(state.locale)) {
131
134
  state.locale = DEFAULT_PANEL_STATE.locale;
132
135
  }
@@ -178,7 +181,7 @@
178
181
  ...legacySession,
179
182
  task: state.task,
180
183
  mode: state.mode,
181
- providerId: 'builtin',
184
+ providerId: state.providerId,
182
185
  model: state.model,
183
186
  reasoningEffort: state.reasoningEffort,
184
187
  speedTier: state.speedTier,
@@ -212,7 +215,7 @@
212
215
  mode: VALID_MODES.has(session.mode) ? session.mode : fallbackState.mode,
213
216
  providerId: typeof session.providerId === 'string' && session.providerId.trim()
214
217
  ? session.providerId.trim()
215
- : 'builtin',
218
+ : (fallbackState.providerId || 'builtin'),
216
219
  model: typeof session.model === 'string' && session.model ? session.model : fallbackState.model,
217
220
  reasoningEffort: VALID_REASONING.has(session.reasoningEffort)
218
221
  ? session.reasoningEffort
@@ -222,7 +225,8 @@
222
225
  : fallbackState.speedTier,
223
226
  requireReviewing: session.requireReviewing !== false,
224
227
  focusFiles: normalizeFocusFiles(session.focusFiles),
225
- codexThreadId: typeof session.codexThreadId === 'string' ? session.codexThreadId : ''
228
+ codexThreadId: typeof session.codexThreadId === 'string' ? session.codexThreadId : '',
229
+ pendingInputs: normalizePendingInputs(session.pendingInputs, options.restoreRunningRuns === true)
226
230
  };
227
231
  }
228
232
 
@@ -291,22 +295,12 @@
291
295
  history: Array.isArray(active.history) ? active.history.slice(-10) : [],
292
296
  focusFiles: normalizeFocusFiles(active.focusFiles),
293
297
  codexThreadId: active.codexThreadId || '',
294
- providerId: active.providerId || 'builtin'
298
+ providerId: state.providerId || 'builtin'
295
299
  };
296
300
  state.runs = Array.isArray(active.runs) ? active.runs : [];
297
301
  state.task = typeof active.task === 'string' ? active.task : '';
298
302
  state.focusFiles = normalizeFocusFiles(active.focusFiles);
299
303
  state.mode = VALID_MODES.has(active.mode) ? active.mode : DEFAULT_PANEL_STATE.mode;
300
- state.providerId = typeof active.providerId === 'string' && active.providerId
301
- ? active.providerId
302
- : 'builtin';
303
- state.model = typeof active.model === 'string' && active.model ? active.model : DEFAULT_PANEL_STATE.model;
304
- state.reasoningEffort = VALID_REASONING.has(active.reasoningEffort)
305
- ? active.reasoningEffort
306
- : DEFAULT_PANEL_STATE.reasoningEffort;
307
- state.speedTier = VALID_SPEED_TIERS.has(active.speedTier)
308
- ? active.speedTier
309
- : DEFAULT_PANEL_STATE.speedTier;
310
304
  state.requireReviewing = active.requireReviewing !== false;
311
305
 
312
306
  return state;
@@ -341,7 +335,8 @@
341
335
  : DEFAULT_PANEL_STATE.speedTier,
342
336
  requireReviewing: overrides.requireReviewing !== false,
343
337
  focusFiles: normalizeFocusFiles(overrides.focusFiles),
344
- codexThreadId: typeof overrides.codexThreadId === 'string' ? overrides.codexThreadId : ''
338
+ codexThreadId: typeof overrides.codexThreadId === 'string' ? overrides.codexThreadId : '',
339
+ pendingInputs: normalizePendingInputs(overrides.pendingInputs)
345
340
  };
346
341
  }
347
342
 
@@ -623,7 +618,12 @@
623
618
  undoBaseFiles: normalizeRunFiles(run.undoBaseFiles),
624
619
  undoTrackedChanges: normalizeRunTrackedChanges(run.undoTrackedChanges),
625
620
  undoExpectedFiles: normalizeRunFiles(run.undoExpectedFiles),
626
- undoStatus: sanitizeAssistantVisibleText(run.undoStatus)
621
+ undoStatus: sanitizeAssistantVisibleText(run.undoStatus),
622
+ nativeRequestId: sanitizeAssistantVisibleText(run.nativeRequestId),
623
+ codexTurnId: sanitizeAssistantVisibleText(run.codexTurnId),
624
+ nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
625
+ queueItemId: sanitizeAssistantVisibleText(run.queueItemId),
626
+ interruptedDraft: run.interruptedDraft ? sanitizeAssistantVisibleValue(run.interruptedDraft) : undefined
627
627
  };
628
628
 
629
629
  applyTrackedChangeStatus(normalized, run.trackedChangeStatus);
@@ -720,6 +720,7 @@
720
720
  timestamp: typeof event.timestamp === 'string' ? event.timestamp : '',
721
721
  kind: typeof event.kind === 'string' ? event.kind : 'activity',
722
722
  technicalDetail: sanitizeAssistantVisibleValue(event.technicalDetail),
723
+ guidanceId: sanitizeAssistantVisibleText(event.guidanceId),
723
724
  streamKey: sanitizeAssistantVisibleText(event.streamKey),
724
725
  streamRole: sanitizeAssistantVisibleText(event.streamRole)
725
726
  };
@@ -965,11 +966,14 @@
965
966
  codexOverleafSkills: compactCodexOverleafSkillsForStorage(source.codexOverleafSkills),
966
967
  composerAttachments: normalizeComposerAttachmentsForPersistence(source.composerAttachments),
967
968
  mode: VALID_MODES.has(active?.mode) ? active.mode : normalizeMode(source.mode),
968
- model: normalizeTextField(active?.model || source.model || DEFAULT_PANEL_STATE.model, 80),
969
- reasoningEffort: VALID_REASONING.has(active?.reasoningEffort)
970
- ? active.reasoningEffort
969
+ providerId: typeof source.providerId === 'string' && source.providerId.trim()
970
+ ? source.providerId.trim()
971
+ : 'builtin',
972
+ model: normalizeTextField(source.model || DEFAULT_PANEL_STATE.model, 80),
973
+ reasoningEffort: VALID_REASONING.has(source.reasoningEffort)
974
+ ? source.reasoningEffort
971
975
  : normalizeReasoning(source.reasoningEffort),
972
- speedTier: normalizeSpeedTier(active?.speedTier || source.speedTier),
976
+ speedTier: normalizeSpeedTier(source.speedTier),
973
977
  locale: normalizeLocale(source.locale),
974
978
  requireReviewing: active ? active.requireReviewing !== false : source.requireReviewing !== false,
975
979
  autoOpen: source.autoOpen !== false,
@@ -1058,7 +1062,9 @@
1058
1062
  reasoningEffort: normalizeReasoning(session.reasoningEffort || fallbackState.reasoningEffort),
1059
1063
  speedTier: normalizeSpeedTier(session.speedTier || fallbackState.speedTier),
1060
1064
  requireReviewing: session.requireReviewing !== false,
1061
- focusFiles: normalizeFocusFiles(session.focusFiles)
1065
+ focusFiles: normalizeFocusFiles(session.focusFiles),
1066
+ codexThreadId: typeof session.codexThreadId === 'string' ? session.codexThreadId : '',
1067
+ pendingInputs: normalizePendingInputs(session.pendingInputs)
1062
1068
  };
1063
1069
  // Welcome-panel + write-guard: preserve the four
1064
1070
  // Recent-projects fields through compaction so they round-trip when state
@@ -1139,8 +1145,15 @@
1139
1145
  undoBaseFiles: undoPayload.undoBaseFiles,
1140
1146
  undoTrackedChanges: undoPayload.undoTrackedChanges,
1141
1147
  undoExpectedFiles: undoPayload.undoExpectedFiles,
1142
- undoStatus: summarizeTextForStorage(run.undoStatus, 'undo status')
1148
+ undoStatus: summarizeTextForStorage(run.undoStatus, 'undo status'),
1149
+ nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
1150
+ codexTurnId: normalizeTextField(run.codexTurnId, 160),
1151
+ nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
1152
+ queueItemId: normalizeTextField(run.queueItemId, 160)
1143
1153
  };
1154
+ if (run.interruptedDraft) {
1155
+ compact.interruptedDraft = sanitizeAssistantVisibleValue(run.interruptedDraft);
1156
+ }
1144
1157
  if (VALID_TRACKED_CHANGE_STATUS.has(run.trackedChangeStatus)) {
1145
1158
  compact.trackedChangeStatus = run.trackedChangeStatus;
1146
1159
  }
@@ -1157,6 +1170,7 @@
1157
1170
  status: normalizeEventStatus(event.status),
1158
1171
  timestamp: typeof event.timestamp === 'string' ? event.timestamp : '',
1159
1172
  kind: typeof event.kind === 'string' ? event.kind : 'activity',
1173
+ guidanceId: normalizeTextField(event.guidanceId, 160),
1160
1174
  streamKey: typeof event.streamKey === 'string' ? event.streamKey : '',
1161
1175
  streamRole: typeof event.streamRole === 'string' ? event.streamRole : ''
1162
1176
  };
@@ -1370,6 +1384,15 @@
1370
1384
  }));
1371
1385
  }
1372
1386
 
1387
+ function normalizePendingInputs(value, recoverActive = false) {
1388
+ const Queue = (typeof globalThis !== 'undefined' ? globalThis.CodexOverleafRunInputQueue : null)
1389
+ || (typeof module === 'object' && module.exports ? require('./runInputQueue') : null);
1390
+ if (Queue?.normalizeQueue) {
1391
+ return Queue.normalizeQueue(value, { recoverActive });
1392
+ }
1393
+ return [];
1394
+ }
1395
+
1373
1396
  function sanitizeAssistantVisibleValue(value, depth = 0) {
1374
1397
  if (typeof value === 'string') {
1375
1398
  return sanitizeAssistantVisibleText(value);
@@ -1,12 +1,12 @@
1
1
  (function initStorageDb(root, factory) {
2
2
  if (typeof module === 'object' && module.exports) {
3
- module.exports = factory(require('./storageRunActions'));
3
+ module.exports = factory(require('./storageRunActions'), require('./runInputQueue'));
4
4
  } else {
5
- root.CodexOverleafStorageDb = factory(root.CodexOverleafStorageRunActions);
5
+ root.CodexOverleafStorageDb = factory(root.CodexOverleafStorageRunActions, root.CodexOverleafRunInputQueue);
6
6
  }
7
- })(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions) {
7
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue) {
8
8
  'use strict';
9
- if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot) {
9
+ if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage) {
10
10
  throw new Error('Codex Overleaf storage run-action helpers are unavailable.');
11
11
  }
12
12
  // IndexedDB versions are monotonic; v2.0 RC profiles have already opened v3.
@@ -272,6 +272,11 @@
272
272
  codexThreadId: typeof input.codexThreadId === 'string' ? input.codexThreadId : '',
273
273
  status: typeof input.status === 'string' && input.status ? input.status : 'active',
274
274
  focusFiles: normalizePathList(input.focusFiles),
275
+ pendingInputs: RunInputQueue.compactForStorage(input.pendingInputs, {
276
+ normalizeField: normalizeTextField,
277
+ normalizeDisplay: normalizeDisplayTextForStorage,
278
+ normalizePaths: normalizePathList
279
+ }),
275
280
  history: compactHistoryForStorage(input.history),
276
281
  runs: compactRunsForStorage(input.runs, options),
277
282
  task: normalizeDisplayTextForStorage(input.task, SESSION_STORAGE_LIMITS.taskChars),
@@ -663,8 +668,23 @@
663
668
  undoBaseFiles: actionPayload.undoBaseFiles,
664
669
  undoTrackedChanges: actionPayload.undoTrackedChanges,
665
670
  undoExpectedFiles: actionPayload.undoExpectedFiles,
666
- undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars)
671
+ undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars),
672
+ nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
673
+ codexTurnId: normalizeTextField(run.codexTurnId, 160),
674
+ nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
675
+ queueItemId: normalizeTextField(run.queueItemId, 160)
667
676
  };
677
+ if (run.interruptedDraft && typeof run.interruptedDraft === 'object') {
678
+ compact.interruptedDraft = {
679
+ requestId: normalizeTextField(run.interruptedDraft.requestId, 160),
680
+ threadId: normalizeTextField(run.interruptedDraft.threadId, 160),
681
+ turnId: normalizeTextField(run.interruptedDraft.turnId, 160),
682
+ lastEventSeq: Number.isFinite(Number(run.interruptedDraft.lastEventSeq))
683
+ ? Number(run.interruptedDraft.lastEventSeq)
684
+ : 0,
685
+ reason: normalizeTextField(run.interruptedDraft.reason, 80)
686
+ };
687
+ }
668
688
  if (VALID_TRACKED_CHANGE_STATUSES[run.trackedChangeStatus] === true) {
669
689
  compact.trackedChangeStatus = run.trackedChangeStatus;
670
690
  }
@@ -712,27 +732,11 @@
712
732
  // its shape while redacting + truncating every string field. Bounded depth +
713
733
  // breadth so a pathological value can't blow up the stored record.
714
734
  function compactStructuredEventValueForStorage(value, depth) {
715
- var d = depth || 0;
716
- if (typeof value === 'string') {
717
- return normalizeDisplayTextForStorage(value, SESSION_STORAGE_LIMITS.reportDetailChars);
718
- }
719
- if (value === null || typeof value === 'number' || typeof value === 'boolean') {
720
- return value;
721
- }
722
- if (d > 6 || typeof value !== 'object') {
723
- return null;
724
- }
725
- if (Array.isArray(value)) {
726
- return value.slice(0, 32).map(function (item) {
727
- return compactStructuredEventValueForStorage(item, d + 1);
728
- });
729
- }
730
- var out = {};
731
- var keys = Object.keys(value).slice(0, 32);
732
- for (var i = 0; i < keys.length; i++) {
733
- out[keys[i]] = compactStructuredEventValueForStorage(value[keys[i]], d + 1);
734
- }
735
- return out;
735
+ return StorageRunActions.compactStructuredEventValue(value, {
736
+ normalizeString: function (text) {
737
+ return normalizeDisplayTextForStorage(text, SESSION_STORAGE_LIMITS.reportDetailChars);
738
+ }
739
+ }, depth);
736
740
  }
737
741
 
738
742
  function getEventDetailLimit(event) {
@@ -1142,7 +1146,11 @@
1142
1146
  return Promise.resolve([]);
1143
1147
  }
1144
1148
  return getAllSessions().then(function (all) {
1145
- return filterRecentProjectsAcrossAccount(all, { accountScopeId: accountScopeId, limit: limit });
1149
+ return filterRecentProjectsAcrossAccount(all, {
1150
+ accountScopeId: accountScopeId,
1151
+ limit: limit,
1152
+ deletedSessionIdsByProject: options && options.deletedSessionIdsByProject
1153
+ });
1146
1154
  });
1147
1155
  }
1148
1156
 
@@ -1153,6 +1161,7 @@
1153
1161
  function filterRecentProjectsAcrossAccount(sessions, options) {
1154
1162
  var accountScopeId = options && options.accountScopeId;
1155
1163
  var limit = options && Number.isFinite(options.limit) ? options.limit : 10;
1164
+ var deletedSessionIdsByProject = options && options.deletedSessionIdsByProject;
1156
1165
  if (!accountScopeId) {
1157
1166
  return [];
1158
1167
  }
@@ -1163,6 +1172,8 @@
1163
1172
  var s = all[i];
1164
1173
  if (!s) continue;
1165
1174
  if (s.accountScopeId !== accountScopeId) continue;
1175
+ if (deletedSessionIdsByProject && Array.isArray(deletedSessionIdsByProject[s.projectId])
1176
+ && deletedSessionIdsByProject[s.projectId].indexOf(s.id) !== -1) continue;
1166
1177
  if (typeof s.lastActivityAt !== 'string' || !s.lastActivityAt) continue;
1167
1178
  if (!isValidOverleafProjectId(s.projectId)) continue;
1168
1179
  var prev = byProject[s.projectId];
@@ -1303,23 +1314,7 @@
1303
1314
  }
1304
1315
  }
1305
1316
 
1306
- function hashString(value) {
1307
- var hash = 2166136261;
1308
- var text = String(value || '');
1309
- for (var i = 0; i < text.length; i++) {
1310
- hash ^= text.charCodeAt(i);
1311
- hash = Math.imul(hash, 16777619);
1312
- }
1313
- return (hash >>> 0).toString(16).padStart(8, '0');
1314
- }
1315
-
1316
- function cloneJsonValue(value) {
1317
- try {
1318
- return JSON.parse(JSON.stringify(value));
1319
- } catch (_error) {
1320
- return Array.isArray(value) ? value.slice() : value;
1321
- }
1322
- }
1317
+ var hashString = StorageRunActions.hashString;
1323
1318
 
1324
1319
  return {
1325
1320
  TARGET_SCHEMA_VERSION: TARGET_SCHEMA_VERSION,