codex-overleaf-link 2.5.0 → 2.6.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 (189) hide show
  1. package/README.md +155 -392
  2. package/README.zh-CN.md +154 -393
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +283 -55
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +129 -16
  11. package/extension/src/backgroundUpdateCoordinator.js +242 -29
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +171 -8
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +104 -47
  17. package/extension/src/content/composerPanel.js +110 -12
  18. package/extension/src/content/contentRuntime.js +1555 -926
  19. package/extension/src/content/contextTray.js +8 -2
  20. package/extension/src/content/contextUsageView.js +267 -0
  21. package/extension/src/content/diagnosticsController.js +122 -96
  22. package/extension/src/content/diagnosticsExport.js +19 -5
  23. package/extension/src/content/diagnosticsPanel.js +48 -0
  24. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  25. package/extension/src/content/generated/content.bundle.js +11077 -3412
  26. package/extension/src/content/generated/content.bundle.meta.json +14 -3
  27. package/extension/src/content/globalPreferencesController.js +1 -1
  28. package/extension/src/content/legacyGlobalRegistry.js +10 -0
  29. package/extension/src/content/localSkillsPanel.js +104 -14
  30. package/extension/src/content/markdownDomRenderer.js +5 -3
  31. package/extension/src/content/markdownText.js +47 -11
  32. package/extension/src/content/modelPicker.js +8 -4
  33. package/extension/src/content/nativeCompatibilityController.js +74 -30
  34. package/extension/src/content/otWarmMirror.js +5 -6
  35. package/extension/src/content/otWarmMirrorController.js +17 -20
  36. package/extension/src/content/pageBridgeClient.js +17 -4
  37. package/extension/src/content/panelMaintenance.js +46 -14
  38. package/extension/src/content/panelRenderer.js +57 -27
  39. package/extension/src/content/pendingInputView.js +1 -1
  40. package/extension/src/content/projectProviderSelection.js +13 -4
  41. package/extension/src/content/projectSessionCleanup.js +109 -62
  42. package/extension/src/content/projectSettingsCoordinator.js +67 -6
  43. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  44. package/extension/src/content/providerSettingsDialog.js +73 -14
  45. package/extension/src/content/recentProjects.js +15 -8
  46. package/extension/src/content/runActivitySummary.js +160 -18
  47. package/extension/src/content/runCompletionMeta.js +532 -0
  48. package/extension/src/content/runController.js +39 -15
  49. package/extension/src/content/runFailureNotice.js +29 -13
  50. package/extension/src/content/runGuidanceController.js +72 -27
  51. package/extension/src/content/runGuidanceView.js +59 -10
  52. package/extension/src/content/runPresence.js +18 -8
  53. package/extension/src/content/runQueueScheduler.js +157 -47
  54. package/extension/src/content/runRecoveryActions.js +147 -0
  55. package/extension/src/content/runResultActions.js +28 -143
  56. package/extension/src/content/runReviewController.js +633 -0
  57. package/extension/src/content/runScrollLayout.js +21 -2
  58. package/extension/src/content/runTimelineView.js +367 -175
  59. package/extension/src/content/scopedPersistencePanelState.js +134 -20
  60. package/extension/src/content/selectionContextView.js +1 -1
  61. package/extension/src/content/sensitivePreflight.js +153 -0
  62. package/extension/src/content/sessionForkController.js +80 -17
  63. package/extension/src/content/sessionManager.js +50 -4
  64. package/extension/src/content/sessionMenuView.js +54 -18
  65. package/extension/src/content/sessionPersistence.js +25 -5
  66. package/extension/src/content/settingsPanel.js +22 -5
  67. package/extension/src/content/settingsWorkbench.js +132 -89
  68. package/extension/src/content/sharedSessionViewSync.js +49 -11
  69. package/extension/src/content/subagentActivityView.js +23 -7
  70. package/extension/src/content/updateIdle.js +4 -1
  71. package/extension/src/content/updateNotice.js +118 -12
  72. package/extension/src/content/updateSafetyReceiver.js +58 -0
  73. package/extension/src/content/writebackController.js +53 -4
  74. package/extension/src/content/writebackOrchestrator.js +116 -94
  75. package/extension/src/content/writingStyleSettings.js +35 -11
  76. package/extension/src/page/binaryAssetUploader.js +136 -14
  77. package/extension/src/page/compileBridge.js +193 -180
  78. package/extension/src/page/fileMover.js +256 -0
  79. package/extension/src/page/overleafCapabilities.js +4 -1
  80. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  81. package/extension/src/page/referenceProjects.js +1 -0
  82. package/extension/src/page/reviewContext.js +113 -0
  83. package/extension/src/page/saveState.js +6 -4
  84. package/extension/src/page/snapshotRouter.js +84 -39
  85. package/extension/src/page/textCoordinates.js +17 -1
  86. package/extension/src/page/textFileCreator.js +114 -26
  87. package/extension/src/page/trackedChangeCapture.js +12 -2
  88. package/extension/src/page/trackedChangeOwnership.js +19 -1
  89. package/extension/src/page/trackedChangeReplay.js +41 -21
  90. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  91. package/extension/src/page/treeOperations.js +7 -10
  92. package/extension/src/page/updateSafetyProbe.js +41 -0
  93. package/extension/src/page/writebackReceiptJournal.js +2 -0
  94. package/extension/src/page/writebackRouter.js +103 -155
  95. package/extension/src/pageBridge.js +126 -69
  96. package/extension/src/popup.js +62 -10
  97. package/extension/src/shared/agentTranscript.js +373 -17
  98. package/extension/src/shared/auditRecords.js +4 -0
  99. package/extension/src/shared/compatibility.js +22 -6
  100. package/extension/src/shared/compileAdapter.js +129 -66
  101. package/extension/src/shared/composerAttachmentDraft.js +156 -0
  102. package/extension/src/shared/contextUsage.js +125 -0
  103. package/extension/src/shared/directoryCleanupProof.js +43 -0
  104. package/extension/src/shared/failureReasons.js +349 -4
  105. package/extension/src/shared/globalPreferences.js +30 -13
  106. package/extension/src/shared/governanceRules.js +4 -37
  107. package/extension/src/shared/i18n.js +590 -24
  108. package/extension/src/shared/lineReferences.js +38 -31
  109. package/extension/src/shared/managedUpdateProjection.js +2 -2
  110. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  111. package/extension/src/shared/pageRpcContract.js +5 -4
  112. package/extension/src/shared/panelIcons.js +39 -0
  113. package/extension/src/shared/projectFiles.js +21 -44
  114. package/extension/src/shared/providerProfiles.js +5 -3
  115. package/extension/src/shared/reviewing.js +130 -1
  116. package/extension/src/shared/runActivityModel.js +70 -4
  117. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  118. package/extension/src/shared/runInputQueue.js +48 -5
  119. package/extension/src/shared/selectionContext.js +12 -3
  120. package/extension/src/shared/sessionState.js +132 -28
  121. package/extension/src/shared/sessionTitle.js +14 -1
  122. package/extension/src/shared/settlementFacts.js +10 -2
  123. package/extension/src/shared/sharedSessionBridge.js +118 -27
  124. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  125. package/extension/src/shared/staleGuard.js +166 -52
  126. package/extension/src/shared/storageDb.js +90 -26
  127. package/extension/src/shared/storageMigration.js +78 -112
  128. package/extension/src/shared/storageRunActions.js +267 -30
  129. package/extension/src/shared/subagentActivityModel.js +1 -1
  130. package/extension/src/shared/undoOperations.js +253 -32
  131. package/extension/src/shared/updateConsent.js +6 -4
  132. package/extension/src/shared/updateRuntimeIdentity.js +17 -3
  133. package/extension/src/shared/writebackIntent.js +16 -0
  134. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  135. package/extension/src/shared/writebackSettlement.js +58 -40
  136. package/extension/styles/panel.css +1123 -493
  137. package/native-host/src/anthropicBridgeRequest.js +4 -2
  138. package/native-host/src/anthropicBridgeResponse.js +6 -2
  139. package/native-host/src/anthropicMessagesBridge.js +24 -16
  140. package/native-host/src/bootstrapMigration.js +296 -0
  141. package/native-host/src/bootstrapMigrationContract.js +101 -0
  142. package/native-host/src/bootstrapUpdate.js +269 -0
  143. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  144. package/native-host/src/chatBridgeRequest.js +60 -6
  145. package/native-host/src/chatBridgeResponse.js +20 -1
  146. package/native-host/src/chatCompletionsBridge.js +48 -15
  147. package/native-host/src/codexConfigPaths.js +106 -0
  148. package/native-host/src/codexHome.js +66 -73
  149. package/native-host/src/codexHomeScope.js +124 -0
  150. package/native-host/src/codexInheritedAuth.js +90 -0
  151. package/native-host/src/codexLocalSkills.js +301 -0
  152. package/native-host/src/codexPromptAssembly.js +114 -127
  153. package/native-host/src/codexProviderTest.js +24 -26
  154. package/native-host/src/codexSessionRunner.js +234 -198
  155. package/native-host/src/codexSessionTiming.js +18 -8
  156. package/native-host/src/codexSkillIsolation.js +170 -0
  157. package/native-host/src/codexThreadFork.js +19 -3
  158. package/native-host/src/index.js +19 -7
  159. package/native-host/src/managedInstall.js +110 -35
  160. package/native-host/src/managedInstallRecovery.js +140 -0
  161. package/native-host/src/managedRuntimeEvidence.js +57 -0
  162. package/native-host/src/mirrorWorkspace.js +92 -22
  163. package/native-host/src/nativeDoctor.js +15 -0
  164. package/native-host/src/nativeHelperLifecycle.js +21 -0
  165. package/native-host/src/nativeHostRegistration.js +33 -0
  166. package/native-host/src/nativeRunLifecycle.js +71 -0
  167. package/native-host/src/processStopBarrier.js +42 -7
  168. package/native-host/src/projectPathPlatform.js +30 -0
  169. package/native-host/src/providerContextError.js +20 -0
  170. package/native-host/src/providerHttp.js +80 -0
  171. package/native-host/src/providerProfile.js +4 -2
  172. package/native-host/src/providerReasoning.js +2 -7
  173. package/native-host/src/providerRuntime.js +1 -0
  174. package/native-host/src/providerStore.js +72 -14
  175. package/native-host/src/responsesPassthroughBridge.js +266 -52
  176. package/native-host/src/runtimeInstaller.js +21 -1
  177. package/native-host/src/selectionScope.js +36 -8
  178. package/native-host/src/subagentAdmission.js +26 -0
  179. package/native-host/src/subagentBroker.js +79 -66
  180. package/native-host/src/subagentTelemetry.js +19 -10
  181. package/native-host/src/subagentWorkspacePath.js +5 -2
  182. package/native-host/src/taskRunnerRuntime.js +92 -57
  183. package/native-host/src/textPatch.js +473 -377
  184. package/native-host/src/tomlStatements.js +140 -0
  185. package/native-host/src/updateManager.js +409 -46
  186. package/native-host/src/writingStyleRuntime.js +57 -29
  187. package/native-host/src/writingStyleStore.js +73 -0
  188. package/package.json +5 -8
  189. package/scripts/install-managed.mjs +1 -0
@@ -2,7 +2,12 @@
2
2
 
3
3
  const { buildCodexRuntimeEvent } = require('./codexRuntimeIdentity');
4
4
 
5
+ const { runOwnedNativeHelper } = require('./nativeHelperLifecycle');
6
+ const { stopCodexAppServer, throwIfAborted } = require('./codexSessionTiming');
5
7
  const { spawn } = require('node:child_process');
8
+ const { normalizeSafeProjectPath } = require('../../extension/src/shared/projectFiles');
9
+ const { createHash } = require('node:crypto');
10
+ const NativeRequestIdentity = require('../../extension/src/shared/nativeRequestIdentity');
6
11
  const { buildOperationSummary, splitDeletePlan } = require('../../extension/src/shared/summary');
7
12
  const {
8
13
  MIN_COMPATIBLE_EXTENSION_VERSION,
@@ -36,6 +41,7 @@ const {
36
41
  const { version: PACKAGE_VERSION } = require('../../package.json');
37
42
 
38
43
  const processStopBarrier = require('./processStopBarrier');
44
+ const runLifecycle = require('./nativeRunLifecycle');
39
45
  const activeProjectLocks = new Map();
40
46
  const activeRunControllers = new Map();
41
47
  const activeRunEntries = new Map();
@@ -44,7 +50,7 @@ const activeRunEntries = new Map();
44
50
  // Overleaf tab was reloaded — the requestId lived in content-side JS state
45
51
  // and is gone, but the native-host-side controller is still running).
46
52
  const activeRunByProject = new Map();
47
- const CODEX_RUN_PASSTHROUGH_ERROR_CODES = new Set(['thread_resume_failed', 'codex_no_usable_result', 'codex_process_stop_unconfirmed', 'selected_context_unresolved']);
53
+ const CODEX_RUN_PASSTHROUGH_ERROR_CODES = new Set(['thread_resume_failed', 'codex_no_usable_result', 'codex_context_limit', 'codex_process_stop_unconfirmed', 'selected_context_unresolved', 'codex_local_skill_unavailable']);
48
54
 
49
55
  async function handleRequest(request, env = process.env, emit = () => {}) {
50
56
  if (!request || typeof request !== 'object') {
@@ -66,7 +72,7 @@ async function handleRequest(request, env = process.env, emit = () => {}) {
66
72
  platform: getNativeRuntimePlatform({ env }),
67
73
  protocolVersion: 2,
68
74
  supportedProtocol: { ...SUPPORTED_NATIVE_PROTOCOL },
69
- capabilities: Object.fromEntries(REQUIRED_CAPABILITIES.map(capability => [capability, true])),
75
+ capabilities: { ...Object.fromEntries(REQUIRED_CAPABILITIES.map(capability => [capability, true])), literalProjectPaths: true, threadCompact: true },
70
76
  minExtensionVersion: MIN_COMPATIBLE_EXTENSION_VERSION,
71
77
  version: PACKAGE_VERSION,
72
78
  environment: summarizeNativeEnvironment(env)
@@ -204,7 +210,16 @@ function quotaErrorResponse(id, violation) {
204
210
  }
205
211
 
206
212
  async function handleCodexRun(request, env, emit) {
213
+ const identity = NativeRequestIdentity.validate(request.id);
214
+ if (!identity.ok) return errorResponse(request.id, identity.error.code, identity.error.message, identity.error.details);
207
215
  let params = request.params || {};
216
+ if (params.operation === 'compact') {
217
+ if (typeof params.threadId !== 'string' || !params.threadId.trim() || params.threadId.length > 160
218
+ || typeof params.projectId !== 'string' || !params.projectId.trim()) {
219
+ return errorResponse(request.id, 'codex_compact_thread_required', 'Compaction requires an existing project conversation.');
220
+ }
221
+ params = { ...params, threadId: params.threadId.trim(), mode: 'ask', writingStyleBuild: undefined };
222
+ }
208
223
  if (params.writingStyleBuild != null) {
209
224
  if (typeof params.writingStyleBuild !== 'object' || Array.isArray(params.writingStyleBuild)) {
210
225
  return errorResponse(request.id, 'writing_style_invalid_build', 'Writing style build settings must be an object.');
@@ -215,7 +230,7 @@ async function handleCodexRun(request, env, emit) {
215
230
  useExistingMirror: false,
216
231
  fileOverlays: [],
217
232
  writingStyle: null,
218
- writingStyleBuild: { ...params.writingStyleBuild, requestId: String(request.id || '') }
233
+ writingStyleBuild: { ...params.writingStyleBuild, requestId: String(request.id) }
219
234
  };
220
235
  }
221
236
  if (isCodexMissing(env)) {
@@ -236,17 +251,16 @@ async function handleCodexRun(request, env, emit) {
236
251
  return errorResponse(request.id, 'project_locked', `Project ${projectKey} is currently in use by codex.run`);
237
252
  }
238
253
  const abortController = new AbortController();
254
+ const lifetime = runLifecycle.register({ controller: abortController, projectKey, rootDir: env.CODEX_OVERLEAF_MIRROR_ROOT });
239
255
  const activeEntry = {
240
- id: request.id || '',
256
+ id: request.id,
241
257
  projectKey,
242
258
  controller: abortController,
243
259
  control: null
244
260
  };
245
- if (request.id) {
246
- activeRunControllers.set(request.id, abortController);
247
- activeRunEntries.set(request.id, activeEntry);
248
- activeRunByProject.set(projectKey, activeEntry);
249
- }
261
+ activeRunControllers.set(request.id, abortController);
262
+ activeRunEntries.set(request.id, activeEntry);
263
+ activeRunByProject.set(projectKey, activeEntry);
250
264
  let providerResolution;
251
265
  try {
252
266
  try {
@@ -264,7 +278,10 @@ async function handleCodexRun(request, env, emit) {
264
278
  return providerErrorResponse(request.id, error);
265
279
  }
266
280
 
267
- if (params.useExistingMirror) {
281
+ abortController.signal.throwIfAborted();
282
+ // Compaction only updates the captured thread; it neither refreshes the
283
+ // project mirror nor accepts document overlays as part of this request.
284
+ if (params.operation !== 'compact' && params.useExistingMirror) {
268
285
  const { getMirrorStatus, applyFileOverlays } = require('./mirrorWorkspace');
269
286
  const rootDir = env.CODEX_OVERLEAF_MIRROR_ROOT;
270
287
  const status = getMirrorStatus(projectKey, { rootDir });
@@ -288,10 +305,12 @@ async function handleCodexRun(request, env, emit) {
288
305
  );
289
306
  }
290
307
 
291
- if (Array.isArray(params.fileOverlays) && params.fileOverlays.length) {
308
+ // OT overlays prove that the current page agrees with the trusted OT
309
+ // baseline; they must not rewrite it or supply a replacement hash.
310
+ if (!isOtWarmMirrorReuseRequest(params) && Array.isArray(params.fileOverlays) && params.fileOverlays.length) {
292
311
  await applyFileOverlays({ projectId: projectKey, overlays: params.fileOverlays, rootDir });
293
312
  }
294
- } else if (!params.writingStyleBuild && !isSnapshotlessSkillInstallerRun(params) && !hasRunnableProjectSnapshotEvidence(params)) {
313
+ } else if (params.operation !== 'compact' && !params.writingStyleBuild && !isSnapshotlessSkillInstallerRun(params) && !hasRunnableProjectSnapshotEvidence(params)) {
295
314
  return errorResponse(
296
315
  request.id,
297
316
  'codex_run_requires_snapshot_evidence',
@@ -306,6 +325,7 @@ async function handleCodexRun(request, env, emit) {
306
325
  rootDir: env.CODEX_OVERLEAF_MIRROR_ROOT,
307
326
  providerLaunch: providerResolution.providerLaunch,
308
327
  onStopUnconfirmed: error => processStopBarrier.record(projectKey, env.CODEX_OVERLEAF_MIRROR_ROOT, error),
328
+ onProcessStarted: lifetime.trackProcess,
309
329
  onControlReady: control => {
310
330
  activeEntry.control = control;
311
331
  },
@@ -346,16 +366,17 @@ async function handleCodexRun(request, env, emit) {
346
366
  });
347
367
  return errorResponse(request.id, 'codex_run_failed', truncateText(error.message, 12000));
348
368
  } finally {
349
- if (request.id && activeRunControllers.get(request.id) === abortController) {
369
+ if (activeRunControllers.get(request.id) === abortController) {
350
370
  activeRunControllers.delete(request.id);
351
371
  }
352
- if (request.id && activeRunEntries.get(request.id) === activeEntry) {
372
+ if (activeRunEntries.get(request.id) === activeEntry) {
353
373
  activeRunEntries.delete(request.id);
354
374
  }
355
375
  if (activeRunByProject.get(projectKey)?.controller === abortController) {
356
376
  activeRunByProject.delete(projectKey);
357
377
  }
358
378
  releaseProjectLock(projectKey, lockToken);
379
+ lifetime.complete();
359
380
  }
360
381
  }
361
382
 
@@ -365,6 +386,9 @@ async function handleCodexSteer(request) {
365
386
  if (!entry?.control) {
366
387
  return errorResponse(request.id, 'codex_turn_not_ready', 'The active Codex turn is not ready for guidance.');
367
388
  }
389
+ if (entry.control.operation === 'compact') {
390
+ return errorResponse(request.id, 'codex_compact_not_steerable', 'Wait for context compaction to finish before sending a message.');
391
+ }
368
392
  const expectedThreadId = String(params.threadId || '');
369
393
  const expectedTurnId = String(params.expectedTurnId || params.turnId || '');
370
394
  if ((expectedThreadId && expectedThreadId !== entry.control.threadId)
@@ -400,8 +424,8 @@ async function handleCodexSteer(request) {
400
424
  }
401
425
 
402
426
  function findActiveRunEntry(params = {}) {
403
- const targetId = String(params.requestId || params.id || '');
404
- if (targetId && activeRunEntries.has(targetId)) {
427
+ const targetId = params.requestId ?? params.id;
428
+ if (activeRunEntries.has(targetId)) {
405
429
  return activeRunEntries.get(targetId);
406
430
  }
407
431
  const projectKey = String(params.projectKey || '');
@@ -420,11 +444,11 @@ function findActiveRunEntry(params = {}) {
420
444
  // can't punch through a real live run.
421
445
  function handleCodexCancel(request, env = process.env) {
422
446
  const params = request.params || {};
423
- const targetId = params.requestId || params.id;
447
+ const targetId = params.requestId ?? params.id;
424
448
  const projectKey = typeof params.projectKey === 'string' ? params.projectKey : '';
425
449
  const force = params.force === true;
426
450
 
427
- if (targetId && activeRunControllers.has(targetId)) {
451
+ if (activeRunControllers.has(targetId)) {
428
452
  activeRunControllers.get(targetId).abort(createCancellationError());
429
453
  return okResponse(request.id, {
430
454
  cancelled: true,
@@ -439,7 +463,7 @@ function handleCodexCancel(request, env = process.env) {
439
463
  return okResponse(request.id, {
440
464
  cancelled: true,
441
465
  projectKey,
442
- requestId: entry.id || ''
466
+ requestId: entry.id
443
467
  });
444
468
  }
445
469
  if (processStopBarrier.isBlocked(projectKey, env.CODEX_OVERLEAF_MIRROR_ROOT)) {
@@ -473,8 +497,10 @@ function handleCodexHistoryClear(request, env) {
473
497
  }
474
498
  }
475
499
 
476
- function handleSkillsList(request, env) {
500
+ async function handleSkillsList(request, env) {
477
501
  try {
502
+ if (request.params?.scope === 'codex-local') return okResponse(request.id,
503
+ await require('./codexLocalSkills').listCodexLocalSkills({ env }));
478
504
  const { CODEX_OVERLEAF_SKILL_SCOPE, listCodexOverleafSkills, listProjectSkills } = require('./localSkills');
479
505
  if (request.params?.scope === CODEX_OVERLEAF_SKILL_SCOPE) {
480
506
  // Install/restore official skills before listing so newly shipped ones
@@ -493,9 +519,9 @@ function handleSkillsList(request, env) {
493
519
  return errorResponse(request.id, 'skills_list_failed', error.message);
494
520
  }
495
521
  }
496
-
497
522
  function handleSkillsInstall(request, env) {
498
523
  try {
524
+ if (request.params?.scope === 'codex-local') throw new Error('Local Codex skills are managed by Codex.');
499
525
  const { CODEX_OVERLEAF_SKILL_SCOPE, installCodexOverleafSkill, installProjectSkill } = require('./localSkills');
500
526
  if (request.params?.scope === CODEX_OVERLEAF_SKILL_SCOPE) {
501
527
  return okResponse(request.id, installCodexOverleafSkill({
@@ -514,9 +540,9 @@ function handleSkillsInstall(request, env) {
514
540
  return errorResponse(request.id, 'skills_install_failed', error.message);
515
541
  }
516
542
  }
517
-
518
543
  function handleSkillsRemove(request, env) {
519
544
  try {
545
+ if (request.params?.scope === 'codex-local') throw new Error('Local Codex skills are managed by Codex.');
520
546
  const { CODEX_OVERLEAF_SKILL_SCOPE, removeCodexOverleafSkill, removeProjectSkill } = require('./localSkills');
521
547
  if (request.params?.scope === CODEX_OVERLEAF_SKILL_SCOPE) {
522
548
  return okResponse(request.id, removeCodexOverleafSkill({
@@ -544,13 +570,13 @@ function hasRunnableProjectSnapshotEvidence(params = {}) {
544
570
  if (params.restrictToFocusFiles !== true) {
545
571
  return false;
546
572
  }
547
- const normalizedFocusFiles = normalizeSnapshotEvidencePaths(params.focusFiles);
573
+ const normalizedFocusFiles = normalizeSnapshotEvidencePaths(params.focusFiles, params.focusFilesAreLiteral === true);
548
574
  if (!normalizedFocusFiles.length || !Array.isArray(params.project?.files)) {
549
575
  return false;
550
576
  }
551
577
  const evidenceFiles = new Map();
552
578
  for (const file of params.project.files) {
553
- const filePath = normalizeSnapshotEvidencePath(file?.path);
579
+ const filePath = normalizeSafeProjectPath(file?.path);
554
580
  if (!filePath || !isUsableSnapshotEvidenceContent(file?.content)) {
555
581
  continue;
556
582
  }
@@ -561,19 +587,14 @@ function hasRunnableProjectSnapshotEvidence(params = {}) {
561
587
 
562
588
  function isSnapshotlessSkillInstallerRun(params = {}) {
563
589
  return params.skipMirrorSync === true
564
- && String(params.skillInvocation?.id || '').trim() === 'skill-installer';
590
+ && String(params.skillInvocation?.id || '').trim() === 'skill-installer'
591
+ && (!params.skillInvocation?.scope || params.skillInvocation.scope === 'builtin');
565
592
  }
566
593
 
567
594
  function validateOtFocusedWarmMirrorReuse(params = {}, status = {}) {
568
595
  if (!isOtWarmMirrorReuseRequest(params)) {
569
596
  return { ok: false };
570
597
  }
571
- if (Array.isArray(params.fileOverlays) && params.fileOverlays.length) {
572
- return {
573
- ok: false,
574
- message: 'OT warm mirror reuse does not accept file overlays'
575
- };
576
- }
577
598
  if (status?.exists !== true) {
578
599
  return {
579
600
  ok: false,
@@ -586,7 +607,7 @@ function validateOtFocusedWarmMirrorReuse(params = {}, status = {}) {
586
607
  message: 'OT warm mirror reuse requires restrictToFocusFiles=true'
587
608
  };
588
609
  }
589
- const normalizedFocusFiles = normalizeSnapshotEvidencePaths(params.focusFiles);
610
+ const normalizedFocusFiles = normalizeSnapshotEvidencePaths(params.focusFiles, params.focusFilesAreLiteral === true);
590
611
  if (!normalizedFocusFiles.length) {
591
612
  return {
592
613
  ok: false,
@@ -594,14 +615,14 @@ function validateOtFocusedWarmMirrorReuse(params = {}, status = {}) {
594
615
  };
595
616
  }
596
617
 
597
- const freshFiles = new Set();
618
+ const freshFiles = new Map();
598
619
  for (const file of Array.isArray(status.otFreshFiles) ? status.otFreshFiles : []) {
599
620
  if (file?.state !== 'fresh') {
600
621
  continue;
601
622
  }
602
- const filePath = normalizeSnapshotEvidencePath(file.path);
623
+ const filePath = normalizeSafeProjectPath(file.path);
603
624
  if (filePath) {
604
- freshFiles.add(filePath);
625
+ freshFiles.set(filePath, file);
605
626
  }
606
627
  }
607
628
  const missingFiles = normalizedFocusFiles.filter(filePath => !freshFiles.has(filePath));
@@ -612,6 +633,22 @@ function validateOtFocusedWarmMirrorReuse(params = {}, status = {}) {
612
633
  };
613
634
  }
614
635
 
636
+ if (Array.isArray(params.fileOverlays) && params.fileOverlays.length) {
637
+ const overlays = new Set();
638
+ for (const overlay of params.fileOverlays) {
639
+ const filePath = normalizeSafeProjectPath(overlay?.path);
640
+ if (!normalizedFocusFiles.includes(filePath) || overlays.has(filePath)
641
+ || typeof overlay?.content !== 'string' || !/^[a-f0-9]{64}$/i.test(freshFiles.get(filePath)?.sha256 || '')
642
+ || createHash('sha256').update(overlay.content).digest('hex') !== freshFiles.get(filePath).sha256.toLowerCase()) {
643
+ return { ok: false, message: 'OT warm mirror overlay does not match the current focused OT baseline' };
644
+ }
645
+ overlays.add(filePath);
646
+ }
647
+ if (normalizedFocusFiles.some(filePath => !overlays.has(filePath))) {
648
+ return { ok: false, message: 'OT warm mirror overlays do not cover all focused files' };
649
+ }
650
+ }
651
+
615
652
  return { ok: true };
616
653
  }
617
654
 
@@ -627,11 +664,11 @@ function isUsableSnapshotEvidenceContent(content) {
627
664
  return Boolean(text) && !/^(loading|loading\.{3}|loading…)$/i.test(text);
628
665
  }
629
666
 
630
- function normalizeSnapshotEvidencePaths(value) {
667
+ function normalizeSnapshotEvidencePaths(value, literal = false) {
631
668
  const seen = new Set();
632
669
  const paths = [];
633
670
  for (const item of Array.isArray(value) ? value : []) {
634
- const filePath = normalizeSnapshotEvidencePath(item);
671
+ const filePath = literal ? normalizeSafeProjectPath(item) : normalizeSnapshotEvidencePath(item);
635
672
  if (!filePath || seen.has(filePath)) {
636
673
  continue;
637
674
  }
@@ -656,13 +693,14 @@ function createCancellationError() {
656
693
  }
657
694
 
658
695
  function isCancellationError(error = {}) {
696
+ if (['codex_turn_failed', 'codex_turn_status_invalid', 'codex_context_limit'].includes(error.code)) return false;
659
697
  return error.code === 'codex_cancelled'
660
698
  || error.name === 'AbortError'
661
699
  || /cancelled by the user|was cancelled/i.test(error.message || '');
662
700
  }
663
701
 
664
702
  function shouldPassthroughCodexRunError(error = {}) {
665
- return CODEX_RUN_PASSTHROUGH_ERROR_CODES.has(error.code);
703
+ return CODEX_RUN_PASSTHROUGH_ERROR_CODES.has(error.code) || String(error.code || '').startsWith('codex_compact_');
666
704
  }
667
705
 
668
706
  function isCodexMissing(env = process.env) {
@@ -753,6 +791,7 @@ async function handleMirrorConfirmWriteback(request, env) {
753
791
  const result = await confirmWritebackFiles({
754
792
  projectId,
755
793
  paths: params.paths,
794
+ expectedFiles: params.expectedFiles,
756
795
  rootDir
757
796
  });
758
797
  return okResponse(request.id, result);
@@ -1127,10 +1166,12 @@ function parseAgentArgsJson(value) {
1127
1166
  }
1128
1167
 
1129
1168
  function runExternalAgent(agentSpec, params, emit = () => {}, options = {}) {
1130
- return new Promise((resolve, reject) => {
1169
+ return runOwnedNativeHelper(signal => new Promise((resolve, reject) => {
1170
+ throwIfAborted(signal);
1131
1171
  const child = spawn(agentSpec.file, agentSpec.args || [], {
1132
1172
  env: options.env || process.env,
1133
1173
  shell: false,
1174
+ detached: process.platform !== 'win32',
1134
1175
  stdio: ['pipe', 'pipe', 'pipe']
1135
1176
  });
1136
1177
  const timeoutMs = parseOptionalPositiveInteger(options.timeoutMs);
@@ -1140,6 +1181,8 @@ function runExternalAgent(agentSpec, params, emit = () => {}, options = {}) {
1140
1181
  let stderrRemainder = '';
1141
1182
  let outputBytes = 0;
1142
1183
  let settled = false;
1184
+ const onAbort = () => fail(signal.reason);
1185
+ signal.addEventListener('abort', onAbort, { once: true });
1143
1186
 
1144
1187
  const timeout = timeoutMs
1145
1188
  ? setTimeout(() => {
@@ -1164,10 +1207,8 @@ function runExternalAgent(agentSpec, params, emit = () => {}, options = {}) {
1164
1207
  if (timeout) {
1165
1208
  clearTimeout(timeout);
1166
1209
  }
1167
- if (child.exitCode === null && !child.killed) {
1168
- child.kill('SIGTERM');
1169
- }
1170
- reject(error);
1210
+ signal.removeEventListener('abort', onAbort);
1211
+ stopCodexAppServer(child, { processGroup: process.platform !== 'win32', reason: error }).then(() => reject(error), reject);
1171
1212
  }
1172
1213
 
1173
1214
  function succeed(result) {
@@ -1178,7 +1219,8 @@ function runExternalAgent(agentSpec, params, emit = () => {}, options = {}) {
1178
1219
  if (timeout) {
1179
1220
  clearTimeout(timeout);
1180
1221
  }
1181
- resolve(result);
1222
+ signal.removeEventListener('abort', onAbort);
1223
+ stopCodexAppServer(child, { processGroup: process.platform !== 'win32' }).then(() => resolve(result), reject);
1182
1224
  }
1183
1225
 
1184
1226
  child.stdout.setEncoding('utf8');
@@ -1200,6 +1242,7 @@ function runExternalAgent(agentSpec, params, emit = () => {}, options = {}) {
1200
1242
  emitTaskEvent(emit, event.type || 'agent.progress', event.title || event.type || 'Agent progress', event.detail || {}, event.status || 'running');
1201
1243
  }
1202
1244
  });
1245
+ child.stdin.on('error', fail);
1203
1246
  child.on('error', fail);
1204
1247
  child.on('close', code => {
1205
1248
  if (settled) {
@@ -1226,7 +1269,7 @@ function runExternalAgent(agentSpec, params, emit = () => {}, options = {}) {
1226
1269
  });
1227
1270
 
1228
1271
  child.stdin.end(JSON.stringify(params));
1229
- });
1272
+ }));
1230
1273
  }
1231
1274
 
1232
1275
  function parsePositiveInteger(value, fallback) {
@@ -1299,27 +1342,19 @@ function errorResponse(id, code, message, details = {}) {
1299
1342
  };
1300
1343
  }
1301
1344
 
1302
- function getActiveNativeWorkState() {
1345
+ function getActiveNativeWorkState(env = process.env) {
1303
1346
  return {
1304
1347
  projectLocks: activeProjectLocks.size,
1348
+ processStopBarriers: processStopBarrier.getBlockedProjectCount(env.CODEX_OVERLEAF_MIRROR_ROOT, { env }),
1305
1349
  runControllers: activeRunControllers.size,
1306
1350
  activeTurnControls: Array.from(activeRunEntries.values()).filter(entry => entry.control).length
1307
1351
  };
1308
1352
  }
1309
1353
 
1310
- function abortAllActiveOperations(reason = 'Native messaging transport disconnected.') {
1311
- const error = new Error(String(reason || 'Native messaging transport disconnected.'));
1312
- error.code = 'native_transport_closed';
1313
- for (const controller of activeRunControllers.values()) {
1314
- if (controller && !controller.signal?.aborted) {
1315
- controller.abort(error);
1316
- }
1317
- }
1318
- }
1319
-
1320
1354
  module.exports = {
1321
1355
  NATIVE_REQUEST_QUOTAS,
1322
- abortAllActiveOperations,
1356
+ abortAllActiveOperations: runLifecycle.abortAllActiveOperations,
1357
+ quarantineActiveOperations: runLifecycle.quarantineActiveOperations,
1323
1358
  buildDefaultTaskResult,
1324
1359
  getActiveNativeWorkState,
1325
1360
  handleRequest,