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,85 +2,87 @@
2
2
 
3
3
  const { truncateText } = require('./debugLog');
4
4
  const { formatOverleafWorkspaceRules } = require('./overleafWorkspaceRules');
5
+ const { buildReadProgressRules } = require('./readProgressGuard');
5
6
 
6
7
  const PROJECT_CUSTOM_INSTRUCTIONS_MAX_CHARS = 12000;
7
8
  const NATIVE_MESSAGE_PROMPT_MAX_CHARS = 1024 * 1024;
8
9
 
9
- /**
10
- * Build the full Codex turn prompt from run parameters.
11
- * @param {Object} options
12
- * @param {string} options.task - User task text.
13
- * @param {string} options.customInstructions - Project custom instructions (may be empty).
14
- * @param {Array} options.skills - Array of { id, name, content } loaded skill objects.
15
- * @param {Object|null} options.skillInvocation - { skillId, forced } if slash-selected.
16
- * @param {Array} options.attachments - Array of { name, relativePath } staged attachment refs.
17
- * @param {string} options.activePath - Currently focused file path.
18
- * @param {Array<string>} options.contextFiles - Selected context file paths.
19
- * @param {Object} options.compileContext - { enabled, logSummary } compile context.
20
- * @returns {{ systemPrompt: string, userPrompt: string }}
21
- */
10
+ /** Stable parent-thread guidance; the active mode and task belong to the turn. */
11
+ function buildCodexDeveloperInstructions(options = {}) {
12
+ const params = options.params && typeof options.params === 'object' ? options.params : options;
13
+ const locale = (options.locale ?? params.locale) === 'zh' ? 'Chinese (zh)' : 'English (en)';
14
+ const skillInvocation = options.skillInvocation || params.skillInvocation;
15
+ return [
16
+ 'You are Codex working inside the Codex Overleaf Link panel, helping an academic author with their LaTeX project in Overleaf. The user reads your answer in a narrow (~380px) side panel.',
17
+ [
18
+ 'Reply language and style:',
19
+ `- Reply in the language of the user's latest request. If unclear, use the UI language: ${locale}. Keep LaTeX source, file paths, and identifiers unchanged.`,
20
+ '- Lead with the answer or outcome. Use short paragraphs and lists; avoid wide tables and long code dumps. Do not restate the request.',
21
+ '- Complete the requested analysis or edit within this turn. Do not end with only a plan or a promise to act later.',
22
+ '- For edits, summarize what changed per file with citations. The panel already shows written files, diffs, and Undo; do not paste whole diffs.'
23
+ ].join('\n'),
24
+ ...(isSkillInstallerInvocation(skillInvocation) ? [] : [formatOverleafWorkspaceRules()]),
25
+ ['Project location citation rules:', formatProjectLocationCitationRules()].join('\n'),
26
+ ['Answer formatting:', formatMathOutputRules()].join('\n'),
27
+ [
28
+ 'Academic integrity:',
29
+ '- Never invent citations, BibTeX entries, data, results, or quotations.',
30
+ '- When a claim needs a source you cannot verify in the project, say so and suggest what kind of source is needed instead of adding one.'
31
+ ].join('\n'),
32
+ [
33
+ 'Editing discipline for writable turns:',
34
+ '- Change only what the request needs. Keep unrelated text, whitespace, and formatting untouched; small diffs keep Overleaf tracked changes and Undo reviewable.',
35
+ '- Keep \\label, \\ref, and \\cite keys and file names stable unless asked to change them.',
36
+ '- Keep the document compilable, with balanced braces and environments.'
37
+ ].join('\n'),
38
+ [
39
+ 'Modes:',
40
+ '- Each turn states its active mode. These modes govern project work; explicit skill-install turns follow their separate installation scope.',
41
+ '- Ask is read-only: inspect and explain. When suggesting edits, show short LaTeX snippets with citations and mention that Auto mode can apply them.',
42
+ '- Auto may edit the local workspace. The browser bridge handles authorization, deletion approval, and syncing to Overleaf.'
43
+ ].join('\n'),
44
+ buildReadProgressRules()
45
+ ].join('\n\n');
46
+ }
47
+
48
+ /** Build dynamic per-turn context and the current user request. */
22
49
  function buildCodexTurnPrompt(options = {}) {
23
50
  const context = normalizePromptOptions(options);
24
-
25
- const systemPrompt = [
26
- 'Same Codex Overleaf session context:',
27
- `Session id: ${context.session.id || 'none'}`,
28
- '',
29
- 'Recent turns in this UI session:',
30
- formatSessionHistory(context.session.history),
31
- '',
32
- 'Current Overleaf workspace:',
33
- `- Project: ${context.projectKey || context.projectId || 'unknown'}`,
34
- '- The local workspace was synced from Overleaf immediately before this turn.',
35
- '- If the recent session history conflicts with the files in the workspace, trust the files.',
36
- ...(isSkillInstallerInvocation(context.skillInvocation) ? [] : ['', formatOverleafWorkspaceRules()]),
37
- '',
38
- 'Project location citation rules:',
39
- formatProjectLocationCitationRules(),
40
- formatMathOutputRules(),
41
- '',
42
- 'Focused project file inventory:',
43
- formatFocusedProjectFileInventory(context.focusFiles),
44
- '',
45
- 'Focus files:',
46
- formatFocusFiles(context.focusFiles),
47
- '',
48
- 'Compilation context (@compile-log):',
49
- formatCompileLogContext(context),
50
- '',
51
- 'Project custom instructions:',
52
- formatCustomInstructionsContext(context.customInstructions),
53
- '',
54
- 'Project local skills:',
55
- formatProjectLocalSkillsContext(context),
56
- '',
57
- 'Codex skill loading:',
58
- formatCodexSkillLoadingContext(context),
59
- '',
60
- 'Selected Codex skill:',
61
- formatSkillInvocationContext(context),
62
- '',
63
- 'Attachments for this turn:',
64
- formatTurnAttachmentsContext(context.attachments),
65
- '',
66
- 'Mode for this turn:',
67
- `- ${context.mode}`,
68
- '- ask: inspect and explain only; do not edit files.',
69
- '- auto: edit the local workspace directly when the request calls for changes. The browser bridge handles authorization, deletion approval, and syncing back to Overleaf.',
70
- '',
71
- 'Write expectation for this turn:',
72
- formatWriteExpectation({
73
- mode: context.mode,
74
- task: context.userTask,
75
- skillInvocation: context.skillInvocation
76
- })
77
- ].join('\n');
78
-
79
- const userPrompt = [
80
- 'Current user request:',
81
- context.userTask || '(empty request)'
82
- ].join('\n');
83
-
51
+ const sections = [
52
+ `Mode for this turn: ${context.mode}`,
53
+ ['Write expectation for this turn:', formatWriteExpectation(context)].join('\n')
54
+ ];
55
+ if (!isSkillInstallerInvocation(context.skillInvocation)) {
56
+ sections.push([
57
+ 'Current Overleaf workspace:',
58
+ ...(context.projectKey || context.projectId ? [`- Project: ${context.projectKey || context.projectId}`] : []),
59
+ ...formatWorkspaceSnapshotContext(context.unreadFiles)
60
+ ].join('\n'));
61
+ }
62
+ if (!context.threadId && Array.isArray(context.session.history) && context.session.history.length) {
63
+ sections.push(['Recent turns in this UI session:', formatSessionHistory(context.session.history)].join('\n'));
64
+ }
65
+ if (context.focusFiles.length) {
66
+ sections.push(['Focus files:', formatFocusFiles(context.focusFiles)].join('\n'));
67
+ }
68
+ if (String(context.compileLog || '').trim()) {
69
+ sections.push(['Compilation context (@compile-log):', formatCompileLogContext(context)].join('\n'));
70
+ }
71
+ if (String(context.customInstructions || '').trim()) {
72
+ sections.push(['Project custom instructions:', formatCustomInstructionsContext(context.customInstructions)].join('\n'));
73
+ }
74
+ if (context.selectedSkillIds.length && (context.projectLocalSkills.skills?.length || context.projectLocalSkills.missing?.length)) {
75
+ sections.push(['Project local skills:', formatProjectLocalSkillsContext(context)].join('\n'));
76
+ }
77
+ if (normalizeSkillInvocation(context.skillInvocation) || normalizeSkillInvocation(context.codexSkillInvocationContext?.invocation)
78
+ || context.codexSkillInvocationContext?.ignored?.length || context.codexSkillInvocationContext?.missing?.length) {
79
+ sections.push(['Selected Codex skill:', formatSkillInvocationContext(context)].join('\n'));
80
+ }
81
+ if (context.attachments.length) {
82
+ sections.push(['Attachments for this turn:', formatTurnAttachmentsContext(context.attachments)].join('\n'));
83
+ }
84
+ const systemPrompt = sections.join('\n\n');
85
+ const userPrompt = ['Current user request:', context.userTask || '(empty request)'].join('\n');
84
86
  return { systemPrompt, userPrompt };
85
87
  }
86
88
 
@@ -88,10 +90,12 @@ function buildCodexTurnPrompt(options = {}) {
88
90
  * Validate prompt size against native messaging limits.
89
91
  * @param {string} systemPrompt
90
92
  * @param {string} userPrompt
93
+ * @param {string} developerInstructions
91
94
  * @returns {{ ok: boolean, totalChars: number, exceedsLimit: boolean }}
92
95
  */
93
- function validatePromptSize(systemPrompt, userPrompt) {
94
- const totalChars = String(systemPrompt || '').length + String(userPrompt || '').length;
96
+ function validatePromptSize(systemPrompt, userPrompt, developerInstructions = '') {
97
+ const totalChars = String(systemPrompt || '').length + String(userPrompt || '').length
98
+ + String(developerInstructions || '').length;
95
99
  const exceedsLimit = totalChars > NATIVE_MESSAGE_PROMPT_MAX_CHARS;
96
100
  return {
97
101
  ok: !exceedsLimit,
@@ -128,9 +132,11 @@ function normalizePromptOptions(options = {}) {
128
132
  return {
129
133
  params,
130
134
  session,
135
+ threadId: String(options.threadId ?? params.threadId ?? '').trim(),
131
136
  projectKey: mirror.projectKey || options.projectKey || params.projectId || params.project?.id || params.project?.projectId,
132
137
  projectId: options.projectId || params.projectId || params.project?.id || params.project?.projectId,
133
138
  workspacePath: mirror.workspacePath || options.workspacePath || '',
139
+ unreadFiles: Array.isArray(mirror.skippedFiles) ? mirror.skippedFiles : [],
134
140
  userTask: String(options.task !== undefined ? options.task : params.task || '').trim(),
135
141
  mode: options.mode || params.mode || 'auto',
136
142
  customInstructions: options.customInstructions !== undefined ? options.customInstructions : params.customInstructions,
@@ -154,6 +160,18 @@ function normalizePromptOptions(options = {}) {
154
160
  };
155
161
  }
156
162
 
163
+ function formatWorkspaceSnapshotContext(unreadFiles) {
164
+ if (!unreadFiles.length) return [
165
+ '- The local workspace was synced from Overleaf before this turn; trust the files over conflicting session history.'
166
+ ];
167
+ return [
168
+ '- The readable project files were synced from Overleaf before this turn.',
169
+ '- The following assets were not read because of the binary size limits. A retained local copy may be stale; a missing local copy does not mean the asset was deleted from Overleaf.',
170
+ '- Do not rely on these assets as current evidence or edit them. Their local changes cannot be written back until their current Overleaf content is read. Other readable files remain available for this task.',
171
+ ...unreadFiles.map(file => `- Unavailable current content: ${JSON.stringify(file.path)} (${file.reason}).`)
172
+ ];
173
+ }
174
+
157
175
  function normalizeLoadedSkills(skills = []) {
158
176
  return (Array.isArray(skills) ? skills : [])
159
177
  .map(skill => ({
@@ -183,7 +201,7 @@ function formatTurnAttachmentsContext(attachments = []) {
183
201
  ].join('\n');
184
202
  }
185
203
 
186
- function formatWriteExpectation({ mode = 'auto', task = '', skillInvocation = null } = {}) {
204
+ function formatWriteExpectation({ mode = 'auto', skillInvocation = null } = {}) {
187
205
  if (isSkillInstallerInvocation(skillInvocation)) {
188
206
  return [
189
207
  '- This is a skill-install turn.',
@@ -195,29 +213,7 @@ function formatWriteExpectation({ mode = 'auto', task = '', skillInvocation = nu
195
213
  if (mode === 'ask') {
196
214
  return '- This is read-only. Inspect and explain; do not edit files.';
197
215
  }
198
- if (requestImpliesFileChanges(task)) {
199
- const lines = [
200
- '- The request asks for file changes. You must edit the local workspace when you find concrete fixes.',
201
- '- Do not stop at a suggestion list or say you will not modify files unless no safe concrete edit exists.',
202
- '- If you intentionally leave files unchanged, explain the specific blocker in the final answer.'
203
- ];
204
- if (requestTargetsAbstract(task)) {
205
- lines.push('- The request targets the abstract/summary. Locate the LaTeX abstract environment or summary paragraph and edit that local project file directly.');
206
- }
207
- return lines.join('\n');
208
- }
209
- return [
210
- '- This mode can write. If the request asks for corrections, revisions, fixes, polishing, updates, or implementation, edit the local workspace directly.',
211
- '- If the request is purely an inspection question, report findings without inventing unnecessary edits.'
212
- ].join('\n');
213
- }
214
-
215
- function requestImpliesFileChanges(task = '') {
216
- return /修正|修复|修改|改[一-龥]*|完善|补全|补充|润色|重写|改写|整理|调整|应用|写入|fix|correct|repair|revise|edit|update|rewrite|polish|improve|apply/i.test(String(task || ''));
217
- }
218
-
219
- function requestTargetsAbstract(task = '') {
220
- return /摘要|abstract|summary/i.test(String(task || ''));
216
+ return '- Decide from the request whether it asks for changes. If it asks you to fix, revise, polish, rewrite, apply or otherwise change the project, edit the workspace files directly instead of only suggesting; if it asks a question, answer it without editing. If you intentionally leave files unchanged for a change request, explain the blocker.';
221
217
  }
222
218
 
223
219
  function normalizeFocusFiles(value) {
@@ -258,8 +254,8 @@ function normalizeProjectPath(value) {
258
254
 
259
255
  function formatProjectLocationCitationRules() {
260
256
  return [
261
- '- In any user-visible output, cite project locations using only Overleaf project-relative paths from the project file inventory below.',
262
- '- Use path:LINE[:COLUMN] (for example, path/to/file.tex:12:3).',
257
+ '- In any user-visible output, cite project locations using only Overleaf project-relative paths of files in this workspace.',
258
+ '- Use path:LINE[:COLUMN] for a position and path:START-END for a span (for example, path/to/file.tex:12:3 or main.tex:12-20).',
263
259
  '- Do not wrap project location citations in backticks or inline code; write main.tex:28 as normal text so the browser can turn it into a jump button.',
264
260
  '- Do not cite local absolute paths, temporary workspace paths, file:// URLs, or markdown links to local files.',
265
261
  '- If a line number is uncertain, cite only the project-relative file path.'
@@ -276,14 +272,6 @@ function formatMathOutputRules() {
276
272
  ].join('\n');
277
273
  }
278
274
 
279
- function formatFocusedProjectFileInventory(files) {
280
- const focusFiles = normalizeFocusFiles(files);
281
- if (!focusFiles.length) {
282
- return '- none selected.';
283
- }
284
- return focusFiles.map(filePath => `- ${filePath}`).join('\n');
285
- }
286
-
287
275
  function formatFocusFiles(files) {
288
276
  if (!files.length) {
289
277
  return '- none; use the whole project when needed.';
@@ -383,29 +371,25 @@ function formatProjectLocalSkillsContext(context = {}) {
383
371
  return sections.length ? sections.join('\n\n') : '- none loaded.';
384
372
  }
385
373
 
386
- function formatCodexSkillLoadingContext(context = {}) {
387
- return [
388
- `- Codex local skills: ${context.loadCodexLocalSkills === false ? 'disabled' : 'enabled'}`,
389
- `- Codex Overleaf skills: ${context.loadCodexOverleafSkills === false ? 'disabled' : 'enabled'}`
390
- ].join('\n');
391
- }
392
-
393
374
  function formatSkillInvocationContext(context = {}) {
394
375
  const invocation = normalizeSkillInvocation(context.skillInvocation);
395
376
  const codexSkillInvocationContext = context.codexSkillInvocationContext || {};
396
377
  const contextInvocation = normalizeSkillInvocation(codexSkillInvocationContext.invocation);
397
378
  const effectiveInvocation = contextInvocation || invocation;
379
+ const skillLabel = effectiveInvocation?.scope === 'codex-local' ? 'local Codex' : 'Codex Overleaf';
398
380
  if (Array.isArray(codexSkillInvocationContext.ignored) && codexSkillInvocationContext.ignored.length) {
399
381
  return [
400
- 'Ignored selected Codex Overleaf skill:',
382
+ `Ignored selected ${skillLabel} skill:`,
401
383
  ...codexSkillInvocationContext.ignored.map(item => `- ${item.id}`),
402
- '- Reason: Codex Overleaf skills are disabled for this turn.',
384
+ effectiveInvocation?.scope === 'codex-local'
385
+ ? '- Reason: The selected local Codex skill is disabled for this turn.'
386
+ : '- Reason: Codex Overleaf skills are disabled for this turn.',
403
387
  '- The stale slash selection was ignored; no selected skill instructions were forced into this prompt.'
404
388
  ].join('\n');
405
389
  }
406
390
  if (Array.isArray(codexSkillInvocationContext.missing) && codexSkillInvocationContext.missing.length) {
407
391
  return [
408
- 'Missing selected Codex Overleaf skill:',
392
+ `Missing selected ${skillLabel} skill:`,
409
393
  ...codexSkillInvocationContext.missing.map(id => `- ${id}`),
410
394
  '- The stale slash selection was ignored; no selected skill instructions were forced into this prompt.'
411
395
  ].join('\n');
@@ -427,17 +411,19 @@ function formatSkillInvocationContext(context = {}) {
427
411
  if (loadedSkill) {
428
412
  return [
429
413
  `- ${effectiveInvocation.id} (${effectiveInvocation.title})`,
430
- '- REQUIRED for this turn: read and use this selected Codex Overleaf skill before answering.',
414
+ `- REQUIRED for this turn: read and use this selected ${skillLabel} skill before answering.`,
431
415
  '- Follow the embedded SKILL.md instructions when they apply to the current user request.',
416
+ ...(effectiveInvocation.scope === 'codex-local' && loadedSkill.path
417
+ ? [`- Skill source: ${loadedSkill.path}`, '- Resolve relative skill resources against this SKILL.md directory.'] : []),
432
418
  '',
433
- 'Selected Codex Overleaf SKILL.md:',
419
+ `Selected ${skillLabel} SKILL.md:`,
434
420
  `## ${loadedSkill.id}: ${loadedSkill.title || effectiveInvocation.title || loadedSkill.id}`,
435
421
  fencedBlock(loadedSkill.content)
436
422
  ].join('\n');
437
423
  }
438
424
  return [
439
425
  `- ${effectiveInvocation.id} (${effectiveInvocation.title})`,
440
- '- Use this selected Codex Overleaf skill for the current turn.',
426
+ `- Use this selected ${skillLabel} skill for the current turn.`,
441
427
  '- Follow the skill instructions and workflow when they apply to the user request.'
442
428
  ].join('\n');
443
429
  }
@@ -450,17 +436,18 @@ function normalizeSkillInvocation(value) {
450
436
  const title = String(value?.title || value?.name || (id === 'skill-installer' ? 'Skill Installer' : id))
451
437
  .trim()
452
438
  .slice(0, 80) || 'Skill Installer';
453
- if (id === 'skill-installer') {
439
+ if (id === 'skill-installer' && (!value?.scope || value.scope === 'builtin')) {
454
440
  return { id, title };
455
441
  }
456
- if (value?.scope !== 'codex-overleaf' && !Object.prototype.hasOwnProperty.call(value || {}, 'skillId')) {
442
+ if (!['codex-overleaf', 'codex-local'].includes(value?.scope) && !Object.prototype.hasOwnProperty.call(value || {}, 'skillId')) {
457
443
  return null;
458
444
  }
459
- return { id, title, scope: 'codex-overleaf' };
445
+ return { id, title, scope: value?.scope === 'codex-local' ? 'codex-local' : 'codex-overleaf' };
460
446
  }
461
447
 
462
448
  function isSkillInstallerInvocation(value) {
463
- return normalizeSkillInvocation(value)?.id === 'skill-installer';
449
+ const invocation = normalizeSkillInvocation(value);
450
+ return invocation?.id === 'skill-installer' && !invocation.scope;
464
451
  }
465
452
 
466
453
  function isSafeSkillId(id) {
@@ -495,4 +482,4 @@ function formatSessionHistory(history) {
495
482
  }).join('\n');
496
483
  }
497
484
 
498
- module.exports = { buildCodexTurnPrompt, validatePromptSize };
485
+ module.exports = { buildCodexDeveloperInstructions, buildCodexTurnPrompt, validatePromptSize };
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { spawn, spawnSync } = require('node:child_process');
3
+ const { runOwnedNativeHelper } = require('./nativeHelperLifecycle');
4
+ const { stopCodexAppServer } = require('./codexSessionTiming');
5
+ const { spawn } = require('node:child_process');
4
6
  const crypto = require('node:crypto');
5
7
  const fs = require('node:fs');
6
8
  const os = require('node:os');
@@ -12,16 +14,22 @@ const { providerError } = require('./providerProfile');
12
14
  const MAX_OUTPUT_BYTES = 256 * 1024;
13
15
 
14
16
  async function runProviderConnectionTest({ launch, env = process.env, signal } = {}) {
15
- const prepared = await prepareProviderLaunch(launch, { signal });
16
- try {
17
- return await runProviderConnectionTestProcess({ launch: prepared.launch, env, signal });
18
- } finally {
19
- await prepared.close();
20
- }
17
+ throwIfTestCancelled(signal);
18
+ return runOwnedNativeHelper(async ownedSignal => {
19
+ throwIfTestCancelled(ownedSignal);
20
+ const prepared = await prepareProviderLaunch(launch, { signal: ownedSignal });
21
+ try {
22
+ throwIfTestCancelled(ownedSignal);
23
+ return await runProviderConnectionTestProcess({ launch: prepared.launch, env, signal: ownedSignal });
24
+ } finally {
25
+ await prepared.close();
26
+ }
27
+ }, signal);
21
28
  }
22
29
 
23
30
  function runProviderConnectionTestProcess({ launch, env = process.env, signal } = {}) {
24
31
  return new Promise((resolve, reject) => {
32
+ throwIfTestCancelled(signal);
25
33
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-overleaf-provider-test-'));
26
34
  const codexHome = path.join(root, 'codex-home');
27
35
  const workspace = path.join(root, 'workspace');
@@ -52,6 +60,7 @@ function runProviderConnectionTestProcess({ launch, env = process.env, signal }
52
60
  const startedAt = Date.now();
53
61
  let child;
54
62
  try {
63
+ throwIfTestCancelled(signal);
55
64
  child = spawn(command, args, {
56
65
  cwd: workspace,
57
66
  env: childEnv,
@@ -61,7 +70,8 @@ function runProviderConnectionTestProcess({ launch, env = process.env, signal }
61
70
  });
62
71
  } catch (error) {
63
72
  cleanup(root);
64
- reject(providerError('provider_test_spawn_failed', 'Codex could not start the provider connection test.', { cause: error }));
73
+ reject(error?.code === 'provider_test_cancelled' ? error
74
+ : providerError('provider_test_spawn_failed', 'Codex could not start the provider connection test.', { cause: error }));
65
75
  return;
66
76
  }
67
77
  let stdout = '';
@@ -121,10 +131,10 @@ function runProviderConnectionTestProcess({ launch, env = process.env, signal }
121
131
  settled = true;
122
132
  clearTimeout(timer);
123
133
  signal?.removeEventListener('abort', onAbort);
124
- if (error) terminateProcessTree(child);
125
- cleanup(root);
126
- if (error) reject(error);
127
- else resolve(result);
134
+ stopCodexAppServer(child, { processGroup: process.platform !== 'win32', reason: error }).then(() => {
135
+ cleanup(root);
136
+ if (error) reject(error); else resolve(result);
137
+ }, stopError => { cleanup(root); reject(stopError); });
128
138
  }
129
139
  });
130
140
  }
@@ -168,20 +178,8 @@ function hasLeakedToolControlTokens(value) {
168
178
  return /<\|tool_call_(?:begin|argument_begin|end)\|>/i.test(String(value || ''));
169
179
  }
170
180
 
171
- function terminateProcessTree(child) {
172
- if (!child?.pid) return;
173
- try {
174
- if (process.platform === 'win32') {
175
- spawnSync('taskkill', ['/pid', String(child.pid), '/t', '/f'], { stdio: 'ignore', windowsHide: true });
176
- } else {
177
- process.kill(-child.pid, 'SIGTERM');
178
- setTimeout(() => {
179
- try { process.kill(-child.pid, 'SIGKILL'); } catch (_error) {}
180
- }, 500).unref?.();
181
- }
182
- } catch (_error) {
183
- try { child.kill('SIGKILL'); } catch (_killError) {}
184
- }
181
+ function throwIfTestCancelled(signal) {
182
+ if (signal?.aborted) throw providerError('provider_test_cancelled', 'Provider connection test was cancelled.');
185
183
  }
186
184
 
187
185
  function cleanup(root) {