codex-overleaf-link 2.4.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/README.md +275 -305
  2. package/README.zh-CN.md +290 -320
  3. package/extension/bootstrap/background.js +99 -19
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +11 -16
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/update.js +25 -2
  8. package/extension/bootstrap/updateStatus.js +1 -0
  9. package/extension/runtime-manifest.json +3 -1
  10. package/extension/src/background.js +234 -56
  11. package/extension/src/backgroundBootstrapMigration.js +181 -0
  12. package/extension/src/backgroundSharedSessions.js +440 -0
  13. package/extension/src/backgroundUpdateCoordinator.js +541 -381
  14. package/extension/src/backgroundUpdateSafety.js +173 -0
  15. package/extension/src/content/activeTurnControl.js +16 -2
  16. package/extension/src/content/applyResultFormatters.js +46 -73
  17. package/extension/src/content/assetTransferBroker.js +244 -12
  18. package/extension/src/content/changeHistoryView.js +175 -0
  19. package/extension/src/content/composerAttachments.js +117 -67
  20. package/extension/src/content/composerPanel.js +41 -8
  21. package/extension/src/content/contentRuntime.js +1084 -1099
  22. package/extension/src/content/diagnosticsController.js +122 -97
  23. package/extension/src/content/diagnosticsExport.js +236 -0
  24. package/extension/src/content/diagnosticsPanel.js +78 -19
  25. package/extension/src/content/diffReviewPanel.js +8 -5
  26. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  27. package/extension/src/content/generated/content.bundle.js +14680 -3606
  28. package/extension/src/content/generated/content.bundle.meta.json +30 -3
  29. package/extension/src/content/globalPreferencesController.js +1 -1
  30. package/extension/src/content/legacyGlobalRegistry.js +25 -1
  31. package/extension/src/content/localSkillsPanel.js +141 -16
  32. package/extension/src/content/markdownDomRenderer.js +9 -4
  33. package/extension/src/content/markdownText.js +5 -6
  34. package/extension/src/content/modelPicker.js +8 -5
  35. package/extension/src/content/nativeCompatibilityController.js +45 -20
  36. package/extension/src/content/otWarmMirror.js +4 -5
  37. package/extension/src/content/otWarmMirrorController.js +17 -20
  38. package/extension/src/content/pageBridgeClient.js +33 -5
  39. package/extension/src/content/panelMaintenance.js +87 -132
  40. package/extension/src/content/panelRenderer.js +10 -0
  41. package/extension/src/content/pendingInputView.js +109 -76
  42. package/extension/src/content/projectProviderSelection.js +13 -4
  43. package/extension/src/content/projectSessionCleanup.js +109 -62
  44. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  45. package/extension/src/content/providerSettingsCoordinator.js +63 -14
  46. package/extension/src/content/providerSettingsDialog.js +724 -113
  47. package/extension/src/content/recentProjects.js +21 -6
  48. package/extension/src/content/runActivitySummary.js +665 -0
  49. package/extension/src/content/runCompletionMeta.js +344 -0
  50. package/extension/src/content/runController.js +40 -15
  51. package/extension/src/content/runFailureNotice.js +34 -12
  52. package/extension/src/content/runGuidanceController.js +76 -30
  53. package/extension/src/content/runGuidanceView.js +59 -10
  54. package/extension/src/content/runPresence.js +306 -0
  55. package/extension/src/content/runQueueScheduler.js +150 -43
  56. package/extension/src/content/runRecoveryActions.js +146 -0
  57. package/extension/src/content/runResultActions.js +11 -8
  58. package/extension/src/content/runReviewController.js +574 -0
  59. package/extension/src/content/runScrollLayout.js +21 -2
  60. package/extension/src/content/runTimelineView.js +315 -290
  61. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  62. package/extension/src/content/selectionContextView.js +300 -0
  63. package/extension/src/content/sensitivePreflight.js +140 -0
  64. package/extension/src/content/sessionForkController.js +80 -17
  65. package/extension/src/content/sessionManager.js +1 -1
  66. package/extension/src/content/sessionPersistence.js +6 -1
  67. package/extension/src/content/settingsPanel.js +84 -55
  68. package/extension/src/content/settingsWorkbench.js +379 -0
  69. package/extension/src/content/sharedSessionViewSync.js +109 -0
  70. package/extension/src/content/subagentActivityView.js +195 -0
  71. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  72. package/extension/src/content/updateIdle.js +4 -1
  73. package/extension/src/content/updateNotice.js +179 -56
  74. package/extension/src/content/updateSafetyReceiver.js +58 -0
  75. package/extension/src/content/writebackController.js +308 -2
  76. package/extension/src/content/writebackOrchestrator.js +213 -199
  77. package/extension/src/content/writebackPlan.js +231 -0
  78. package/extension/src/content/writebackRecovery.js +122 -0
  79. package/extension/src/content/writingStyleSettings.js +600 -0
  80. package/extension/src/content/writtenChangesView.js +152 -0
  81. package/extension/src/page/binaryAssetUploader.js +223 -23
  82. package/extension/src/page/fileMover.js +256 -0
  83. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  84. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  85. package/extension/src/page/referenceProjects.js +149 -0
  86. package/extension/src/page/reviewContext.js +113 -0
  87. package/extension/src/page/saveState.js +20 -6
  88. package/extension/src/page/snapshotRouter.js +124 -48
  89. package/extension/src/page/textCoordinates.js +102 -0
  90. package/extension/src/page/textFileCreator.js +442 -71
  91. package/extension/src/page/trackedChangeCapture.js +53 -57
  92. package/extension/src/page/trackedChangeOwnership.js +216 -0
  93. package/extension/src/page/trackedChangeReplay.js +184 -0
  94. package/extension/src/page/trackedChangesLifecycle.js +343 -339
  95. package/extension/src/page/treeOperations.js +33 -12
  96. package/extension/src/page/updateSafetyProbe.js +41 -0
  97. package/extension/src/page/writebackReceiptJournal.js +114 -0
  98. package/extension/src/page/writebackRouter.js +242 -201
  99. package/extension/src/pageBridge.js +73 -102
  100. package/extension/src/popup.js +261 -65
  101. package/extension/src/shared/agentTranscript.js +41 -4
  102. package/extension/src/shared/auditRecords.js +73 -0
  103. package/extension/src/shared/changeHistoryModel.js +94 -0
  104. package/extension/src/shared/compatibility.js +22 -6
  105. package/extension/src/shared/compileAdapter.js +101 -37
  106. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  107. package/extension/src/shared/directoryCleanupProof.js +43 -0
  108. package/extension/src/shared/failureReasons.js +329 -3
  109. package/extension/src/shared/globalPreferences.js +31 -14
  110. package/extension/src/shared/governanceRules.js +4 -37
  111. package/extension/src/shared/i18n.js +389 -25
  112. package/extension/src/shared/managedUpdateProjection.js +8 -5
  113. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  114. package/extension/src/shared/pageRpcContract.js +9 -2
  115. package/extension/src/shared/projectFiles.js +21 -44
  116. package/extension/src/shared/providerProfiles.js +114 -3
  117. package/extension/src/shared/runActivityModel.js +361 -0
  118. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  119. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  120. package/extension/src/shared/runInputQueue.js +48 -5
  121. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  122. package/extension/src/shared/selectionContext.js +37 -0
  123. package/extension/src/shared/sessionState.js +166 -30
  124. package/extension/src/shared/settlementFacts.js +10 -2
  125. package/extension/src/shared/sharedSessionBridge.js +311 -0
  126. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  127. package/extension/src/shared/staleGuard.js +118 -13
  128. package/extension/src/shared/storageDb.js +77 -78
  129. package/extension/src/shared/storageMigration.js +74 -111
  130. package/extension/src/shared/storageRunActions.js +211 -22
  131. package/extension/src/shared/storageValuePruning.js +40 -0
  132. package/extension/src/shared/subagentActivityModel.js +174 -0
  133. package/extension/src/shared/undoOperations.js +216 -6
  134. package/extension/src/shared/updateConsent.js +42 -11
  135. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  136. package/extension/src/shared/writebackIntent.js +78 -0
  137. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  138. package/extension/src/shared/writebackSettlement.js +67 -48
  139. package/extension/src/shared/writingStyle.js +33 -0
  140. package/extension/styles/panel.css +1987 -385
  141. package/extension/styles/popup.css +110 -0
  142. package/native-host/src/anthropicBridgeRequest.js +6 -3
  143. package/native-host/src/anthropicBridgeResponse.js +6 -2
  144. package/native-host/src/anthropicMessagesBridge.js +24 -16
  145. package/native-host/src/bootstrapMigration.js +296 -0
  146. package/native-host/src/bootstrapMigrationContract.js +101 -0
  147. package/native-host/src/bootstrapUpdate.js +233 -0
  148. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  149. package/native-host/src/chatBridgeResponse.js +10 -0
  150. package/native-host/src/chatCompletionsBridge.js +21 -15
  151. package/native-host/src/codexConfigPaths.js +106 -0
  152. package/native-host/src/codexHome.js +66 -73
  153. package/native-host/src/codexHomeScope.js +124 -0
  154. package/native-host/src/codexInheritedAuth.js +90 -0
  155. package/native-host/src/codexLocalSkills.js +301 -0
  156. package/native-host/src/codexModelRecovery.js +107 -0
  157. package/native-host/src/codexPromptAssembly.js +33 -12
  158. package/native-host/src/codexProviderLaunch.js +3 -3
  159. package/native-host/src/codexProviderTest.js +24 -26
  160. package/native-host/src/codexRuntimeIdentity.js +1 -0
  161. package/native-host/src/codexSessionRunner.js +211 -318
  162. package/native-host/src/codexSessionTiming.js +21 -8
  163. package/native-host/src/codexSkillIsolation.js +170 -0
  164. package/native-host/src/codexThreadFork.js +19 -3
  165. package/native-host/src/index.js +19 -7
  166. package/native-host/src/managedInstall.js +115 -39
  167. package/native-host/src/managedInstallRecovery.js +140 -0
  168. package/native-host/src/managedRuntimeEvidence.js +57 -0
  169. package/native-host/src/mirrorWorkspace.js +92 -22
  170. package/native-host/src/nativeDoctor.js +15 -0
  171. package/native-host/src/nativeEnvironment.js +70 -0
  172. package/native-host/src/nativeHelperLifecycle.js +21 -0
  173. package/native-host/src/nativeHostRegistration.js +33 -0
  174. package/native-host/src/nativeQuotas.js +15 -8
  175. package/native-host/src/nativeRunLifecycle.js +71 -0
  176. package/native-host/src/overleafWorkspaceRules.js +16 -0
  177. package/native-host/src/processStopBarrier.js +115 -0
  178. package/native-host/src/projectPathPlatform.js +30 -0
  179. package/native-host/src/providerHttp.js +80 -0
  180. package/native-host/src/providerModelCatalog.js +4 -3
  181. package/native-host/src/providerProfile.js +17 -3
  182. package/native-host/src/providerReasoning.js +10 -11
  183. package/native-host/src/providerRuntime.js +1 -0
  184. package/native-host/src/providerStore.js +76 -14
  185. package/native-host/src/releaseTransport.js +14 -5
  186. package/native-host/src/responsesPassthroughBridge.js +266 -52
  187. package/native-host/src/runtimeInstaller.js +21 -1
  188. package/native-host/src/selectionScope.js +86 -0
  189. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  190. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  191. package/native-host/src/subagentAdmission.js +26 -0
  192. package/native-host/src/subagentBroker.js +176 -159
  193. package/native-host/src/subagentTelemetry.js +325 -0
  194. package/native-host/src/subagentWorkspace.js +103 -0
  195. package/native-host/src/subagentWorkspacePath.js +34 -0
  196. package/native-host/src/taskRunnerRuntime.js +130 -57
  197. package/native-host/src/tomlStatements.js +140 -0
  198. package/native-host/src/turnAttachments.js +113 -0
  199. package/native-host/src/updateManager.js +406 -38
  200. package/native-host/src/updateNetworkControl.js +75 -0
  201. package/native-host/src/updateTrust.js +21 -2
  202. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  203. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  306. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  307. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  308. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  309. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  310. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  311. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  312. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  313. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  314. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  315. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  316. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  317. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  318. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  319. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  320. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  321. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  322. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  323. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  324. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  325. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  326. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  327. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  328. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  329. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  330. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  331. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  332. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  333. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  334. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  335. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  336. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  337. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  338. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  339. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  340. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  341. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  342. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  343. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  344. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  345. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  346. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  347. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  348. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  349. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  350. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  351. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  352. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  353. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  354. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  355. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  356. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  357. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  358. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  359. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  360. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  361. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  362. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  363. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  364. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  365. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  366. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  367. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  368. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  369. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  370. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  371. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  372. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  373. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  374. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  375. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  376. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  377. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  378. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  379. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  380. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  381. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  382. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  383. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  384. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  385. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  386. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  387. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  388. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  389. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  390. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  391. package/native-host/src/writingStyleEvidence.js +102 -0
  392. package/native-host/src/writingStylePdfWorker.js +35 -0
  393. package/native-host/src/writingStyleProfile.js +165 -0
  394. package/native-host/src/writingStyleRuntime.js +475 -0
  395. package/native-host/src/writingStyleStore.js +73 -0
  396. package/package.json +1 -1
  397. package/scripts/install-managed.mjs +1 -0
@@ -19,6 +19,10 @@ const fs = require('node:fs');
19
19
  const path = require('node:path');
20
20
 
21
21
  const { SUBAGENT_QUEUE_DIR } = require('./mirrorWorkspace');
22
+ const { safeWorkspaceRelativePath } = require('./subagentWorkspacePath');
23
+ const { estimateWorkerWaves } = require('./subagentAdmission');
24
+ const { compactEvent, publicText } = require('./subagentTelemetry');
25
+ const { createSubagentWorkspace } = require('./subagentWorkspace');
22
26
 
23
27
  const PROTOCOL_VERSION = 1;
24
28
  const JOB_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
@@ -28,8 +32,8 @@ const SUMMARY_LIMIT_CHARS = 2048;
28
32
  const DEFAULT_LIMITS = Object.freeze({
29
33
  maxWorkers: 3,
30
34
  maxJobsPerRun: 8,
31
- perWorkerTimeoutMs: 300000,
32
- brokerBudgetMs: 900000,
35
+ perWorkerTimeoutMs: 600000,
36
+ brokerBudgetMs: 1800000,
33
37
  pollIntervalMs: 500,
34
38
  drainGraceMs: 30000
35
39
  });
@@ -43,15 +47,31 @@ function createSubagentBroker(options = {}) {
43
47
  if (typeof runWorkerTask !== 'function') {
44
48
  throw new Error('subagent broker requires a runWorkerTask function');
45
49
  }
46
- const emit = typeof options.emit === 'function' ? options.emit : () => {};
50
+ const emit = (...args) => {
51
+ try { if (typeof options.emit === 'function') options.emit(...args); }
52
+ catch (_error) { /* Observability cannot prevent worker cleanup. */ }
53
+ };
47
54
  const onMirrorDirty = typeof options.onMirrorDirty === 'function' ? options.onMirrorDirty : () => {};
48
55
  const parentSignal = options.signal || null;
49
- const limits = { ...DEFAULT_LIMITS, ...(options.limits || {}) };
56
+ const env = options.env || process.env;
57
+ const boundedMs = (value, fallback, max) => Number.isFinite(Number(value)) && Number(value) > 0
58
+ ? Math.min(max, Math.max(60000, Number(value))) : fallback;
59
+ const limits = { ...DEFAULT_LIMITS,
60
+ perWorkerTimeoutMs: boundedMs(env.CODEX_OVERLEAF_SUBAGENT_TIMEOUT_MS, DEFAULT_LIMITS.perWorkerTimeoutMs, 900000),
61
+ brokerBudgetMs: boundedMs(env.CODEX_OVERLEAF_SUBAGENT_BUDGET_MS, DEFAULT_LIMITS.brokerBudgetMs, 3600000),
62
+ ...(options.limits || {}) };
50
63
 
51
64
  const queueRoot = path.join(workspacePath, SUBAGENT_QUEUE_DIR);
52
65
  const jobsDir = path.join(queueRoot, 'jobs');
53
66
  const resultsDir = path.join(queueRoot, 'results');
54
67
  const logsDir = path.join(queueRoot, 'logs');
68
+ const { atomicWrite, appendLog, adoptionHash, hashWorkspace, hashPaths, buildWorkerPrompt } =
69
+ createSubagentWorkspace({ workspacePath, logsDir });
70
+ const adoptionsDir = path.join(queueRoot, 'adoptions');
71
+ const adoptionResultsDir = path.join(queueRoot, 'adoption-results');
72
+ const quarantined = new Map();
73
+ const seenAdoptions = new Set();
74
+ let adoptionToken = '';
55
75
 
56
76
  const jobs = new Map(); // id -> { job, status, startedAt, ownedBefore }
57
77
  const seenFiles = new Set();
@@ -68,6 +88,8 @@ function createSubagentBroker(options = {}) {
68
88
  let anyWorkerStarted = false;
69
89
  let cancelled = false;
70
90
  let closed = false;
91
+ let brokerError = null;
92
+ let stopping = null;
71
93
 
72
94
  function start() {
73
95
  // Stale queue dirs from a previous run carry old results/logs — clear
@@ -76,6 +98,8 @@ function createSubagentBroker(options = {}) {
76
98
  fs.mkdirSync(jobsDir, { recursive: true });
77
99
  fs.mkdirSync(resultsDir, { recursive: true });
78
100
  fs.mkdirSync(logsDir, { recursive: true });
101
+ fs.mkdirSync(adoptionsDir, { recursive: true });
102
+ fs.mkdirSync(adoptionResultsDir, { recursive: true });
79
103
  fs.mkdirSync(path.join(queueRoot, 'work'), { recursive: true });
80
104
  startedAt = Date.now();
81
105
  accepting = true;
@@ -94,16 +118,14 @@ function createSubagentBroker(options = {}) {
94
118
  maxJobsPerRun: limits.maxJobsPerRun,
95
119
  perWorkerTimeoutMs: limits.perWorkerTimeoutMs,
96
120
  brokerBudgetMs: limits.brokerBudgetMs,
121
+ activeWorkers: running.size,
122
+ adoptionToken,
123
+ adoptionDirectory: 'adoptions',
97
124
  acceptedUntil: new Date(startedAt + limits.brokerBudgetMs - limits.perWorkerTimeoutMs).toISOString()
98
125
  };
99
126
  atomicWrite(path.join(queueRoot, 'broker.json'), JSON.stringify(payload, null, 2));
100
127
  }
101
128
 
102
- function atomicWrite(target, text) {
103
- const tmp = path.join(path.dirname(target), `.tmp-${crypto.randomUUID()}`);
104
- fs.writeFileSync(tmp, text);
105
- fs.renameSync(tmp, target);
106
- }
107
129
 
108
130
  function pollOnce() {
109
131
  if (closed || cancelled) {
@@ -112,11 +134,23 @@ function createSubagentBroker(options = {}) {
112
134
  try {
113
135
  intakeJobs();
114
136
  fillSlots();
115
- } catch (_error) {
116
- // The queue is adversarial model output; the broker never throws on it.
137
+ processAdoptions();
138
+ } catch (error) {
139
+ recordFailure(error);
117
140
  }
118
141
  }
119
142
 
143
+ function recordFailure(error, jobId) {
144
+ accepting = false;
145
+ if (brokerError) return;
146
+ brokerError = Object.assign(new Error('Subagent broker results or cleanup could not be persisted.'), {
147
+ code: 'subagent_io_failed', cause: error
148
+ });
149
+ emit('codex.subagent.failed', 'Subagent result storage failed', {
150
+ jobId, status: 'failed', reason: brokerError.message, code: brokerError.code
151
+ }, 'warning');
152
+ }
153
+
120
154
  function intakeJobs() {
121
155
  if (!accepting || !fs.existsSync(jobsDir)) {
122
156
  return;
@@ -154,15 +188,18 @@ function createSubagentBroker(options = {}) {
154
188
  acceptedCount += 1;
155
189
  jobs.set(job.id, { job, status: 'queued' });
156
190
  queued.push(job.id);
157
- emit('codex.subagent.queued', job.title || job.id, { jobId: job.id, files: job.files }, 'running');
191
+ emit('codex.subagent.queued', publicText(job.title || job.id, 160), {
192
+ jobId: job.id, files: job.files, task: publicText(job.task, 4096)
193
+ }, 'running');
158
194
  }
159
195
 
160
196
  function idFromFileName(name) {
161
- return name.replace(/\.json$/, '').slice(0, 32) || 'unknown';
197
+ const id = name.replace(/\.json$/, '');
198
+ return JOB_ID_PATTERN.test(id) ? id : `rejected-${crypto.randomBytes(11).toString('hex')}`;
162
199
  }
163
200
 
164
201
  function jobIdOrFallback(job, name) {
165
- return typeof job?.id === 'string' && job.id ? job.id.slice(0, 32) : idFromFileName(name);
202
+ return typeof job?.id === 'string' && JOB_ID_PATTERN.test(job.id) ? job.id : idFromFileName(name);
166
203
  }
167
204
 
168
205
  function validateJob(job) {
@@ -195,13 +232,11 @@ function createSubagentBroker(options = {}) {
195
232
  }
196
233
  owned.push(safe);
197
234
  }
198
- // Overlapping ownership no longer rejects: same-file jobs are admitted
199
- // and SERIALIZED by the scheduler (fillSlots never runs two jobs whose
200
- // files intersect). Prompt-scoped same-file delegation is then safe —
201
- // the physical lost-update race only exists under concurrency (v1.6.1).
202
- // Wave-aware admission: the parent turn has no default absolute deadline,
203
- // so the broker enforces its own wall-clock envelope across ALL waves.
204
- const projectedWaves = Math.ceil((queued.length + running.size + 1) / limits.maxWorkers);
235
+ // Reserve serial waves for overlapping ownership, matching fillSlots.
236
+ // The budget covers worker admission; parent/OS cleanup has separate limits.
237
+ const projectedWaves = estimateWorkerWaves(
238
+ [...running.keys()].map(id => jobs.get(id).job.files),
239
+ [...queued.map(id => jobs.get(id).job.files), owned], limits.maxWorkers);
205
240
  if (Date.now() + projectedWaves * limits.perWorkerTimeoutMs > startedAt + limits.brokerBudgetMs) {
206
241
  return { reason: 'insufficient_time' };
207
242
  }
@@ -212,30 +247,6 @@ function createSubagentBroker(options = {}) {
212
247
  return null;
213
248
  }
214
249
 
215
- function safeWorkspaceRelativePath(value) {
216
- if (typeof value !== 'string' || !value.trim()) {
217
- return null;
218
- }
219
- const normalized = value.trim().replace(/\\/g, '/').replace(/^\.\//, '');
220
- if (!normalized || path.isAbsolute(normalized)) {
221
- return null;
222
- }
223
- const segments = normalized.split('/');
224
- if (segments.some(segment => segment === '..' || segment === '')) {
225
- return null;
226
- }
227
- if (segments[0] === SUBAGENT_QUEUE_DIR) {
228
- // The queue control plane (jobs/results/logs/broker.json) is never
229
- // ownable — but the work/ scratch zone IS: single-file fan-out slices
230
- // live there (v1.6.1 scatter-gather), excluded from writeback by the
231
- // mirror-scan rule yet fully owned/hashed like any other job file.
232
- if (segments[1] === 'work' && segments.length >= 3) {
233
- return segments.join('/');
234
- }
235
- return null;
236
- }
237
- return segments.join('/');
238
- }
239
250
 
240
251
  function writeRejection(input) {
241
252
  const result = {
@@ -250,6 +261,10 @@ function createSubagentBroker(options = {}) {
250
261
  }
251
262
 
252
263
  function writeResult(result) {
264
+ // Rejection receipts cross the same filesystem boundary as accepted jobs.
265
+ if (typeof result.id !== 'string' || !JOB_ID_PATTERN.test(result.id)) {
266
+ throw new Error('Invalid subagent result ID');
267
+ }
253
268
  atomicWrite(path.join(resultsDir, `${result.id}.json`), JSON.stringify(result, null, 2));
254
269
  }
255
270
 
@@ -270,6 +285,11 @@ function createSubagentBroker(options = {}) {
270
285
  }
271
286
  const [jobId] = queued.splice(index, 1);
272
287
  const entry = jobs.get(jobId);
288
+ if (Date.now() + limits.perWorkerTimeoutMs > startedAt + limits.brokerBudgetMs) {
289
+ entry.status = 'rejected';
290
+ writeRejection({ id: jobId, reason: 'insufficient_time' });
291
+ continue;
292
+ }
273
293
  try {
274
294
  startWorker(entry);
275
295
  } catch (error) {
@@ -283,6 +303,9 @@ function createSubagentBroker(options = {}) {
283
303
  try {
284
304
  writeResult({ id: jobId, status: 'failed', reason: error?.message || 'subagent failed to start' });
285
305
  } catch (_writeError) { /* result dir may be gone */ }
306
+ emit('codex.subagent.failed', publicText(entry?.job?.title || jobId, 160), {
307
+ jobId, status: 'failed', reason: publicText(error?.message || 'subagent failed to start', 1200)
308
+ }, 'warning');
286
309
  }
287
310
  }
288
311
  }
@@ -313,8 +336,9 @@ function createSubagentBroker(options = {}) {
313
336
  controller.abort(new Error('subagent timeout'));
314
337
  }, limits.perWorkerTimeoutMs);
315
338
 
316
- emit('codex.subagent.started', job.title || job.id, {
339
+ emit('codex.subagent.started', publicText(job.title || job.id, 160), {
317
340
  jobId: job.id,
341
+ task: publicText(job.task, 4096),
318
342
  files: job.files,
319
343
  activeWorkers: running.size + 1,
320
344
  maxWorkers: limits.maxWorkers
@@ -323,8 +347,23 @@ function createSubagentBroker(options = {}) {
323
347
  const promise = runWorkerTask({
324
348
  jobId: job.id,
325
349
  prompt: buildWorkerPrompt(job),
326
- signal: controller.signal
350
+ signal: controller.signal,
351
+ onEvent: event => {
352
+ if (closed || entry.status !== 'running') return;
353
+ try {
354
+ const bounded = compactEvent(event);
355
+ if (!bounded) return;
356
+ const params = bounded.detail?.params || {};
357
+ entry.threadId = bounded.detail?.threadId || params.threadId || params.thread?.id || entry.threadId || '';
358
+ entry.sequence = (entry.sequence || 0) + 1;
359
+ emit('codex.subagent.event', publicText(job.title || job.id, 160), {
360
+ source: 'broker', jobId: job.id, threadId: entry.threadId,
361
+ sequence: entry.sequence, event: bounded
362
+ }, 'running');
363
+ } catch (_error) { /* Observability must not fail or stop the worker. */ }
364
+ }
327
365
  }).then(workerResult => {
366
+ if (controller.signal.aborted) throw controller.signal.reason || new Error('Subagent aborted');
328
367
  finishWorker(entry, {
329
368
  status: 'completed',
330
369
  summary: String(workerResult?.assistantMessage || '').slice(0, SUMMARY_LIMIT_CHARS),
@@ -332,21 +371,22 @@ function createSubagentBroker(options = {}) {
332
371
  }, workerResult);
333
372
  }).catch(error => {
334
373
  // Any broker-initiated abort that is not the wall-clock deadline
335
- // (parent cancel, drain) reads as 'cancelled'; only genuine worker
336
- // errors read as 'failed'.
337
- const status = timedOut ? 'timeout' : controller.signal.aborted ? 'cancelled' : 'failed';
374
+ // (parent cancel, drain), or an interrupted worker turn, is cancelled.
375
+ const status = timedOut ? 'timeout' : controller.signal.aborted || error?.code === 'codex_cancelled' ? 'cancelled' : 'failed';
338
376
  appendLog(job.id, `${status}: ${error?.stack || error?.message || String(error)}`);
339
377
  finishWorker(entry, { status, reason: error?.message || String(error) });
340
- }).finally(() => {
378
+ }).catch(error => recordFailure(error, job.id)).finally(() => {
341
379
  clearTimeout(deadlineTimer);
342
380
  running.delete(job.id);
343
381
  if (running.size === 0) {
344
382
  endWave();
345
383
  }
384
+ writeBrokerFile(closed ? 'closed' : 'ready');
346
385
  fillSlots();
347
- });
386
+ }).catch(error => recordFailure(error, job.id));
348
387
 
349
388
  running.set(job.id, { controller, deadlineTimer, promise });
389
+ try { writeBrokerFile('ready'); } catch (error) { recordFailure(error, job.id); }
350
390
  }
351
391
 
352
392
  function finishWorker(entry, resultFields, workerResult) {
@@ -362,12 +402,13 @@ function createSubagentBroker(options = {}) {
362
402
  // demotion reads getViolationPaths() and drops these from writeback
363
403
  // (spec S8 safety extension, v1.6.2).
364
404
  for (const file of changedFiles) {
365
- violationPaths.add(file);
405
+ quarantined.set(file, { jobId: job.id, status: resultFields.status, adoptedHash: '' });
366
406
  }
367
407
  }
368
408
  const result = {
369
409
  id: job.id,
370
410
  ...resultFields,
411
+ threadId: workerResult?.threadId || entry.threadId || '',
371
412
  changedFiles,
372
413
  durationMs: Date.now() - entry.startedAt
373
414
  };
@@ -377,24 +418,23 @@ function createSubagentBroker(options = {}) {
377
418
  atomicWrite(path.join(resultsDir, `${job.id}.last-message.md`), String(workerResult.assistantMessage));
378
419
  }
379
420
  const eventType = resultFields.status === 'completed' ? 'codex.subagent.completed' : 'codex.subagent.failed';
380
- emit(eventType, job.title || job.id, {
421
+ emit(eventType, publicText(job.title || job.id, 160), {
381
422
  jobId: job.id,
423
+ threadId: result.threadId,
382
424
  status: resultFields.status,
383
- reason: resultFields.reason,
425
+ reason: publicText(resultFields.reason, 1200),
426
+ summary: publicText(workerResult?.assistantMessage || resultFields.summary, 24000),
384
427
  changedFiles,
385
428
  durationMs: result.durationMs
386
429
  }, resultFields.status === 'completed' ? 'completed' : 'warning');
387
430
  }
388
431
 
389
- function appendLog(jobId, text) {
390
- try {
391
- fs.appendFileSync(path.join(logsDir, `${jobId}.log`), `${new Date().toISOString()} ${text}\n`);
392
- } catch (_error) { /* logs are best-effort */ }
393
- }
394
432
 
395
433
  // ---- wave tracking (spec S5): owned attribution is per worker; unowned
396
434
  // changes can only be attributed to the wave's set of suspects.
397
435
  function beginWave() {
436
+ adoptionToken = '';
437
+ writeBrokerFile('ready');
398
438
  waveIndex += 1;
399
439
  waveSnapshot = {
400
440
  hashes: hashWorkspace(),
@@ -434,68 +474,48 @@ function createSubagentBroker(options = {}) {
434
474
  emit('codex.subagent.violation', file, violation, 'warning');
435
475
  }
436
476
  waveSnapshot = null;
477
+ if (!closed && !cancelled && accepting && running.size === 0) {
478
+ // Workers have exited before this capability is published. A worker
479
+ // cannot pre-authorize its own incomplete output while still running.
480
+ adoptionToken = crypto.randomBytes(24).toString('hex');
481
+ }
437
482
  }
438
483
 
439
- function hashWorkspace() {
440
- const hashes = new Map();
441
- walk(workspacePath, '');
442
- return hashes;
443
484
 
444
- function walk(dir, prefix) {
445
- let entries;
485
+ function processAdoptions() {
486
+ if (closed || cancelled || running.size || !adoptionToken || !fs.existsSync(adoptionsDir)) return;
487
+ for (const name of fs.readdirSync(adoptionsDir).filter(name => /^[a-z0-9-]{1,32}\.json$/.test(name)).slice(0, 64)) {
488
+ if (seenAdoptions.has(name)) continue;
489
+ seenAdoptions.add(name);
490
+ let result = { ok: false, reason: 'invalid_adoption' };
446
491
  try {
447
- entries = fs.readdirSync(dir, { withFileTypes: true });
448
- } catch (_error) {
449
- return;
450
- }
451
- for (const entry of entries) {
452
- // The whole queue zone (incl. the work/ slice scratch) stays out of
453
- // wave hashing: scratch can never reach Overleaf, same-wave slices
454
- // are all in the owned union anyway, and a lead staggering slice
455
- // creation mid-wave must not read as a violation (v1.6.1).
456
- if (entry.name === '.DS_Store' || entry.name === SUBAGENT_QUEUE_DIR || entry.name === '.codex-overleaf-attachments') {
457
- continue;
458
- }
459
- const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
460
- const absolute = path.join(dir, entry.name);
461
- if (entry.isDirectory()) {
462
- walk(absolute, relative);
463
- } else if (entry.isFile()) {
464
- hashes.set(relative, hashFile(absolute));
492
+ const requestPath = path.join(adoptionsDir, name), stat = fs.lstatSync(requestPath);
493
+ if (!stat.isFile() || stat.size > MAX_JOB_FILE_BYTES) throw new Error('invalid_adoption_file');
494
+ const request = JSON.parse(fs.readFileSync(requestPath, 'utf8'));
495
+ if (request.token !== adoptionToken || request.reviewed !== true || !Array.isArray(request.files)
496
+ || !request.files.length || request.files.length > 100) throw new Error('adoption_not_authorized');
497
+ const approved = [];
498
+ for (const item of request.files) {
499
+ const safe = safeWorkspaceRelativePath(item?.path), held = quarantined.get(safe);
500
+ const entry = held && jobs.get(held.jobId);
501
+ if (!safe || !held || held.jobId !== item.jobId || !entry?.job?.files.includes(safe)
502
+ || violationPaths.has(safe) || !/^[a-f0-9]{64}$/i.test(item.sha256 || '')
503
+ || adoptionHash(safe) !== item.sha256.toLowerCase()) throw new Error('adoption_content_or_owner_mismatch');
504
+ approved.push({ path: safe, sha256: item.sha256.toLowerCase(), held });
465
505
  }
466
- }
467
- }
468
- }
469
-
470
- function hashPaths(files) {
471
- const hashes = new Map();
472
- for (const file of files) {
473
- hashes.set(file, hashFile(path.join(workspacePath, file)));
506
+ for (const item of approved) item.held.adoptedHash = item.sha256;
507
+ result = { ok: true, files: approved.map(item => ({ path: item.path, sha256: item.sha256 })) };
508
+ } catch (error) { result = { ok: false, reason: String(error?.message || error).slice(0, 200) }; }
509
+ atomicWrite(path.join(adoptionResultsDir, name), JSON.stringify(result, null, 2));
474
510
  }
475
- return hashes;
476
511
  }
477
512
 
478
- function hashFile(absolute) {
479
- try {
480
- return crypto.createHash('sha1').update(fs.readFileSync(absolute)).digest('hex');
481
- } catch (_error) {
482
- return 'missing';
513
+ function blockedPaths() {
514
+ const blocked = new Set(violationPaths);
515
+ for (const [file, held] of quarantined) {
516
+ if (!held.adoptedHash || adoptionHash(file) !== held.adoptedHash) blocked.add(file);
483
517
  }
484
- }
485
-
486
- function buildWorkerPrompt(job) {
487
- const readOnly = job.readOnlyContext.length
488
- ? `- You may read these for context but must not modify them: ${job.readOnlyContext.join(', ')}.\n`
489
- : '';
490
- return [
491
- 'You are a subagent working on one slice of a larger task.',
492
- 'HARD CONSTRAINTS:',
493
- `- You may modify ONLY these files: ${job.files.join(', ')}.`,
494
- readOnly + '- Do not modify, create, or delete any other file. Inside .codex-overleaf-subagents/ you may touch ONLY the slice files listed above (if any).',
495
- '- Work fully autonomously; nobody can answer questions.',
496
- '',
497
- job.task
498
- ].join('\n');
518
+ return blocked;
499
519
  }
500
520
 
501
521
  function onParentAbort() {
@@ -504,62 +524,56 @@ function createSubagentBroker(options = {}) {
504
524
  for (const { controller } of running.values()) {
505
525
  controller.abort(new Error('run cancelled'));
506
526
  }
507
- for (const jobId of queued.splice(0)) {
508
- const entry = jobs.get(jobId);
509
- entry.status = 'cancelled';
510
- writeResult({ id: jobId, status: 'cancelled', reason: 'run cancelled' });
511
- }
512
527
  if (anyWorkerStarted) {
513
- // Partial worker edits must be deterministically discarded: cancellation
514
- // already skips writeback, and the dirty mark forces the next run's
515
- // mirror sync to rebuild the workspace from Overleaf (spec P1-3 fix).
516
- onMirrorDirty();
528
+ try { onMirrorDirty(); } catch (error) { recordFailure(error); }
517
529
  }
518
- close();
530
+ // Event listeners cannot expose an async rejection. The same sticky stop
531
+ // promise is still awaited by the parent runner and reports any I/O error.
532
+ stop({ drain: false }).catch(() => {});
519
533
  }
520
534
 
521
- async function stop({ drain = true } = {}) {
522
- accepting = false;
523
- // Queued-but-unstarted jobs would otherwise vanish with no result file,
524
- // hanging a lead still polling for its job count. Settle them first
525
- // (mirrors onParentAbort's queue handling).
535
+ function cancelQueued(reason) {
526
536
  for (const jobId of queued.splice(0)) {
527
537
  const entry = jobs.get(jobId);
528
- if (entry) {
529
- entry.status = 'cancelled';
530
- }
531
- writeResult({ id: jobId, status: 'cancelled', reason: 'The run ended before this subagent started.' });
532
- }
533
- if (drain && running.size) {
534
- let graceTimer = null;
535
- const grace = new Promise(resolve => {
536
- graceTimer = setTimeout(resolve, limits.drainGraceMs);
537
- });
538
- await Promise.race([
539
- Promise.allSettled([...running.values()].map(worker => worker.promise)),
540
- grace
541
- ]);
542
- // The grace timer gates an awaited race; if the workers settled first it
543
- // must be cleared or it keeps the event loop alive for drainGraceMs
544
- // (the "keep timers gated, then clear" discipline from c3cd357).
545
- if (graceTimer) {
546
- clearTimeout(graceTimer);
547
- }
538
+ if (entry) entry.status = 'cancelled';
539
+ try { writeResult({ id: jobId, status: 'cancelled', reason }); }
540
+ catch (error) { recordFailure(error, jobId); }
541
+ emit('codex.subagent.failed', publicText(entry?.job?.title || jobId, 160), {
542
+ jobId, status: 'cancelled', reason
543
+ }, 'warning');
548
544
  }
549
- for (const { controller } of running.values()) {
550
- controller.abort(new Error('broker drained'));
545
+ }
546
+
547
+ function stop({ drain = true } = {}) {
548
+ accepting = false;
549
+ if (!stopping) stopping = Promise.resolve().then(() => stopWorkers(drain));
550
+ return stopping.then(() => { if (brokerError) throw brokerError; });
551
+ }
552
+
553
+ async function stopWorkers(drain) {
554
+ accepting = false;
555
+ try {
556
+ try { processAdoptions(); } catch (error) { recordFailure(error); }
557
+ cancelQueued(cancelled ? 'run cancelled' : 'The run ended before this subagent started.');
558
+ if (drain && !cancelled && !brokerError) await settleRunningWithin(limits.drainGraceMs);
559
+ } finally {
560
+ for (const { controller } of running.values()) controller.abort(new Error('broker drained'));
561
+ // Keep bounded waiting for workers that ignore abort; their own deadline
562
+ // remains active until settlement. OS exit proof belongs to the runner.
563
+ try { await settleRunningWithin(limits.drainGraceMs); } finally {
564
+ for (const jobId of running.keys()) for (const file of jobs.get(jobId).job.files) {
565
+ quarantined.set(file, { jobId, status: 'cancelled', adoptedHash: '' });
566
+ }
567
+ close();
568
+ }
551
569
  }
552
- // Bounded final settle: a worker that ignores its abort must not hang
553
- // stop() forever — that strands codex.run and leaks the project lock (the
554
- // very zombie-lock case handleCodexCancel exists to recover from).
555
- await settleRunningWithin(limits.drainGraceMs);
556
570
  if (jobs.size) {
557
571
  emit('codex.subagent.drained', 'subagents drained', {
558
572
  jobs: jobs.size,
559
573
  violations: violations.length
560
- }, 'completed');
574
+ }, brokerError ? 'warning' : 'completed');
561
575
  }
562
- close();
576
+ if (brokerError) throw brokerError;
563
577
  }
564
578
 
565
579
  async function settleRunningWithin(timeoutMs) {
@@ -590,7 +604,7 @@ function createSubagentBroker(options = {}) {
590
604
  parentSignal?.removeEventListener?.('abort', onParentAbort);
591
605
  try {
592
606
  writeBrokerFile('closed');
593
- } catch (_error) { /* workspace may already be gone */ }
607
+ } catch (error) { recordFailure(error); }
594
608
  }
595
609
 
596
610
  return {
@@ -599,7 +613,8 @@ function createSubagentBroker(options = {}) {
599
613
  pollOnce,
600
614
  hasActiveWorkers: () => running.size > 0,
601
615
  hasAcceptedJobs: () => jobs.size > 0,
602
- getViolationPaths: () => new Set(violationPaths),
616
+ getViolationPaths: blockedPaths,
617
+ getBlockedReason: file => violationPaths.has(file) ? 'subagent_unauthorized_edit' : 'subagent_unfinished_output',
603
618
  getAuditSummary: () => ({
604
619
  jobs: [...jobs.values()].map(entry => ({
605
620
  id: entry.job?.id,
@@ -609,7 +624,9 @@ function createSubagentBroker(options = {}) {
609
624
  reason: entry.result?.reason,
610
625
  durationMs: entry.result?.durationMs
611
626
  })),
612
- violations: violations.slice()
627
+ violations: violations.slice(),
628
+ quarantined: [...quarantined].map(([file, item]) => ({ path: file, jobId: item.jobId, status: item.status,
629
+ adopted: Boolean(item.adoptedHash && adoptionHash(file) === item.adoptedHash) }))
613
630
  })
614
631
  };
615
632
  }