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.
- package/README.md +155 -392
- package/README.zh-CN.md +154 -393
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- package/scripts/install-managed.mjs +1 -0
|
@@ -20,6 +20,7 @@ const path = require('node:path');
|
|
|
20
20
|
|
|
21
21
|
const { SUBAGENT_QUEUE_DIR } = require('./mirrorWorkspace');
|
|
22
22
|
const { safeWorkspaceRelativePath } = require('./subagentWorkspacePath');
|
|
23
|
+
const { estimateWorkerWaves } = require('./subagentAdmission');
|
|
23
24
|
const { compactEvent, publicText } = require('./subagentTelemetry');
|
|
24
25
|
const { createSubagentWorkspace } = require('./subagentWorkspace');
|
|
25
26
|
|
|
@@ -46,7 +47,10 @@ function createSubagentBroker(options = {}) {
|
|
|
46
47
|
if (typeof runWorkerTask !== 'function') {
|
|
47
48
|
throw new Error('subagent broker requires a runWorkerTask function');
|
|
48
49
|
}
|
|
49
|
-
const emit =
|
|
50
|
+
const emit = (...args) => {
|
|
51
|
+
try { if (typeof options.emit === 'function') options.emit(...args); }
|
|
52
|
+
catch (_error) { /* Observability cannot prevent worker cleanup. */ }
|
|
53
|
+
};
|
|
50
54
|
const onMirrorDirty = typeof options.onMirrorDirty === 'function' ? options.onMirrorDirty : () => {};
|
|
51
55
|
const parentSignal = options.signal || null;
|
|
52
56
|
const env = options.env || process.env;
|
|
@@ -84,6 +88,8 @@ function createSubagentBroker(options = {}) {
|
|
|
84
88
|
let anyWorkerStarted = false;
|
|
85
89
|
let cancelled = false;
|
|
86
90
|
let closed = false;
|
|
91
|
+
let brokerError = null;
|
|
92
|
+
let stopping = null;
|
|
87
93
|
|
|
88
94
|
function start() {
|
|
89
95
|
// Stale queue dirs from a previous run carry old results/logs — clear
|
|
@@ -129,11 +135,22 @@ function createSubagentBroker(options = {}) {
|
|
|
129
135
|
intakeJobs();
|
|
130
136
|
fillSlots();
|
|
131
137
|
processAdoptions();
|
|
132
|
-
} catch (
|
|
133
|
-
|
|
138
|
+
} catch (error) {
|
|
139
|
+
recordFailure(error);
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
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
|
+
|
|
137
154
|
function intakeJobs() {
|
|
138
155
|
if (!accepting || !fs.existsSync(jobsDir)) {
|
|
139
156
|
return;
|
|
@@ -177,11 +194,12 @@ function createSubagentBroker(options = {}) {
|
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
function idFromFileName(name) {
|
|
180
|
-
|
|
197
|
+
const id = name.replace(/\.json$/, '');
|
|
198
|
+
return JOB_ID_PATTERN.test(id) ? id : `rejected-${crypto.randomBytes(11).toString('hex')}`;
|
|
181
199
|
}
|
|
182
200
|
|
|
183
201
|
function jobIdOrFallback(job, name) {
|
|
184
|
-
return typeof job?.id === 'string' && job.id ? job.id
|
|
202
|
+
return typeof job?.id === 'string' && JOB_ID_PATTERN.test(job.id) ? job.id : idFromFileName(name);
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
function validateJob(job) {
|
|
@@ -214,13 +232,11 @@ function createSubagentBroker(options = {}) {
|
|
|
214
232
|
}
|
|
215
233
|
owned.push(safe);
|
|
216
234
|
}
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// so the broker enforces its own wall-clock envelope across ALL waves.
|
|
223
|
-
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);
|
|
224
240
|
if (Date.now() + projectedWaves * limits.perWorkerTimeoutMs > startedAt + limits.brokerBudgetMs) {
|
|
225
241
|
return { reason: 'insufficient_time' };
|
|
226
242
|
}
|
|
@@ -245,6 +261,10 @@ function createSubagentBroker(options = {}) {
|
|
|
245
261
|
}
|
|
246
262
|
|
|
247
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
|
+
}
|
|
248
268
|
atomicWrite(path.join(resultsDir, `${result.id}.json`), JSON.stringify(result, null, 2));
|
|
249
269
|
}
|
|
250
270
|
|
|
@@ -265,6 +285,11 @@ function createSubagentBroker(options = {}) {
|
|
|
265
285
|
}
|
|
266
286
|
const [jobId] = queued.splice(index, 1);
|
|
267
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
|
+
}
|
|
268
293
|
try {
|
|
269
294
|
startWorker(entry);
|
|
270
295
|
} catch (error) {
|
|
@@ -338,6 +363,7 @@ function createSubagentBroker(options = {}) {
|
|
|
338
363
|
} catch (_error) { /* Observability must not fail or stop the worker. */ }
|
|
339
364
|
}
|
|
340
365
|
}).then(workerResult => {
|
|
366
|
+
if (controller.signal.aborted) throw controller.signal.reason || new Error('Subagent aborted');
|
|
341
367
|
finishWorker(entry, {
|
|
342
368
|
status: 'completed',
|
|
343
369
|
summary: String(workerResult?.assistantMessage || '').slice(0, SUMMARY_LIMIT_CHARS),
|
|
@@ -345,21 +371,22 @@ function createSubagentBroker(options = {}) {
|
|
|
345
371
|
}, workerResult);
|
|
346
372
|
}).catch(error => {
|
|
347
373
|
// Any broker-initiated abort that is not the wall-clock deadline
|
|
348
|
-
// (parent cancel, drain)
|
|
349
|
-
|
|
350
|
-
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';
|
|
351
376
|
appendLog(job.id, `${status}: ${error?.stack || error?.message || String(error)}`);
|
|
352
377
|
finishWorker(entry, { status, reason: error?.message || String(error) });
|
|
353
|
-
}).finally(() => {
|
|
378
|
+
}).catch(error => recordFailure(error, job.id)).finally(() => {
|
|
354
379
|
clearTimeout(deadlineTimer);
|
|
355
380
|
running.delete(job.id);
|
|
356
381
|
if (running.size === 0) {
|
|
357
382
|
endWave();
|
|
358
383
|
}
|
|
384
|
+
writeBrokerFile(closed ? 'closed' : 'ready');
|
|
359
385
|
fillSlots();
|
|
360
|
-
});
|
|
386
|
+
}).catch(error => recordFailure(error, job.id));
|
|
361
387
|
|
|
362
388
|
running.set(job.id, { controller, deadlineTimer, promise });
|
|
389
|
+
try { writeBrokerFile('ready'); } catch (error) { recordFailure(error, job.id); }
|
|
363
390
|
}
|
|
364
391
|
|
|
365
392
|
function finishWorker(entry, resultFields, workerResult) {
|
|
@@ -451,7 +478,6 @@ function createSubagentBroker(options = {}) {
|
|
|
451
478
|
// Workers have exited before this capability is published. A worker
|
|
452
479
|
// cannot pre-authorize its own incomplete output while still running.
|
|
453
480
|
adoptionToken = crypto.randomBytes(24).toString('hex');
|
|
454
|
-
writeBrokerFile('ready');
|
|
455
481
|
}
|
|
456
482
|
}
|
|
457
483
|
|
|
@@ -498,69 +524,56 @@ function createSubagentBroker(options = {}) {
|
|
|
498
524
|
for (const { controller } of running.values()) {
|
|
499
525
|
controller.abort(new Error('run cancelled'));
|
|
500
526
|
}
|
|
501
|
-
for (const jobId of queued.splice(0)) {
|
|
502
|
-
const entry = jobs.get(jobId);
|
|
503
|
-
entry.status = 'cancelled';
|
|
504
|
-
writeResult({ id: jobId, status: 'cancelled', reason: 'run cancelled' });
|
|
505
|
-
emit('codex.subagent.failed', publicText(entry.job.title || jobId, 160), {
|
|
506
|
-
jobId, status: 'cancelled', reason: 'run cancelled'
|
|
507
|
-
}, 'warning');
|
|
508
|
-
}
|
|
509
527
|
if (anyWorkerStarted) {
|
|
510
|
-
|
|
511
|
-
// already skips writeback, and the dirty mark forces the next run's
|
|
512
|
-
// mirror sync to rebuild the workspace from Overleaf (spec P1-3 fix).
|
|
513
|
-
onMirrorDirty();
|
|
528
|
+
try { onMirrorDirty(); } catch (error) { recordFailure(error); }
|
|
514
529
|
}
|
|
515
|
-
|
|
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(() => {});
|
|
516
533
|
}
|
|
517
534
|
|
|
518
|
-
|
|
519
|
-
processAdoptions();
|
|
520
|
-
accepting = false;
|
|
521
|
-
// Queued-but-unstarted jobs would otherwise vanish with no result file,
|
|
522
|
-
// hanging a lead still polling for its job count. Settle them first
|
|
523
|
-
// (mirrors onParentAbort's queue handling).
|
|
535
|
+
function cancelQueued(reason) {
|
|
524
536
|
for (const jobId of queued.splice(0)) {
|
|
525
537
|
const entry = jobs.get(jobId);
|
|
526
|
-
if (entry)
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
writeResult({ id: jobId, status: 'cancelled', reason: 'The run ended before this subagent started.' });
|
|
538
|
+
if (entry) entry.status = 'cancelled';
|
|
539
|
+
try { writeResult({ id: jobId, status: 'cancelled', reason }); }
|
|
540
|
+
catch (error) { recordFailure(error, jobId); }
|
|
530
541
|
emit('codex.subagent.failed', publicText(entry?.job?.title || jobId, 160), {
|
|
531
|
-
jobId, status: 'cancelled', reason
|
|
542
|
+
jobId, status: 'cancelled', reason
|
|
532
543
|
}, 'warning');
|
|
533
544
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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();
|
|
548
568
|
}
|
|
549
569
|
}
|
|
550
|
-
for (const { controller } of running.values()) {
|
|
551
|
-
controller.abort(new Error('broker drained'));
|
|
552
|
-
}
|
|
553
|
-
// Bounded final settle: a worker that ignores its abort must not hang
|
|
554
|
-
// stop() forever — that strands codex.run and leaks the project lock (the
|
|
555
|
-
// very zombie-lock case handleCodexCancel exists to recover from).
|
|
556
|
-
await settleRunningWithin(limits.drainGraceMs);
|
|
557
570
|
if (jobs.size) {
|
|
558
571
|
emit('codex.subagent.drained', 'subagents drained', {
|
|
559
572
|
jobs: jobs.size,
|
|
560
573
|
violations: violations.length
|
|
561
|
-
}, 'completed');
|
|
574
|
+
}, brokerError ? 'warning' : 'completed');
|
|
562
575
|
}
|
|
563
|
-
|
|
576
|
+
if (brokerError) throw brokerError;
|
|
564
577
|
}
|
|
565
578
|
|
|
566
579
|
async function settleRunningWithin(timeoutMs) {
|
|
@@ -591,7 +604,7 @@ function createSubagentBroker(options = {}) {
|
|
|
591
604
|
parentSignal?.removeEventListener?.('abort', onParentAbort);
|
|
592
605
|
try {
|
|
593
606
|
writeBrokerFile('closed');
|
|
594
|
-
} catch (
|
|
607
|
+
} catch (error) { recordFailure(error); }
|
|
595
608
|
}
|
|
596
609
|
|
|
597
610
|
return {
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
// Observation only: never resumes a thread, starts a turn or settles a run.
|
|
4
4
|
const CHILD_ID = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,159}$/;
|
|
5
5
|
const MAX_CHILDREN = 16;
|
|
6
|
+
const MAX_HISTORY_READ_FAILURES = 3;
|
|
6
7
|
const TERMINAL = new Set(['completed', 'failed', 'cancelled']);
|
|
7
8
|
const EVIDENCE = { history: 1, agent: 2, turn: 3 };
|
|
8
9
|
|
|
9
10
|
function publicText(value, limit = 4096) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.replace(/((?:api[_-]?key|access[_-]?token|secret|password|authorization)\s*[:=]\s*["']?)[^\s"',;]+/gi, '$1[REDACTED_SECRET]')
|
|
14
|
-
.slice(0, limit);
|
|
11
|
+
// Preserve the delegated task and model/tool text. The event allowlist
|
|
12
|
+
// excludes credential fields; metadataText separately hides local paths.
|
|
13
|
+
return String(value ?? '').slice(0, limit);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
function fields(value, names, limit = 4096) {
|
|
@@ -185,7 +184,7 @@ function createNativeSubagentObserver({ emit, request, getRoot, ownsParent, isSt
|
|
|
185
184
|
return false;
|
|
186
185
|
}
|
|
187
186
|
function snapshot(child, terminal = false) {
|
|
188
|
-
if (!active() || child.readDisabled) return;
|
|
187
|
+
if (!active() || child.readDisabled || (child.readFailures || 0) >= MAX_HISTORY_READ_FAILURES) return;
|
|
189
188
|
if (child.reading) { child.readAgain ||= terminal; return; }
|
|
190
189
|
if (!terminal && Date.now() - child.lastRead < 2000) return;
|
|
191
190
|
child.reading = true; child.lastRead = Date.now();
|
|
@@ -204,6 +203,11 @@ function createNativeSubagentObserver({ emit, request, getRoot, ownsParent, isSt
|
|
|
204
203
|
if (!turn?.id || turn.id === getRoot().turnId
|
|
205
204
|
|| child.turnId && child.turnId !== turn.id) return;
|
|
206
205
|
child.historyTurnId = turn.id;
|
|
206
|
+
child.readFailures = 0;
|
|
207
|
+
if (child.historyUnavailable) {
|
|
208
|
+
child.historyUnavailable = false;
|
|
209
|
+
publish(child, null, { historyUnavailable: false });
|
|
210
|
+
}
|
|
207
211
|
const items = Array.isArray(turn.items) ? turn.items : [];
|
|
208
212
|
// Recover real child input before compactEvent drops user content.
|
|
209
213
|
// A fork's parent context and encrypted delegation are not task text.
|
|
@@ -231,11 +235,14 @@ function createNativeSubagentObserver({ emit, request, getRoot, ownsParent, isSt
|
|
|
231
235
|
? (interruptionObserved ? 'cancelled' : undefined) : turn.status;
|
|
232
236
|
if (setStatus(child, next, EVIDENCE.history)) publish(child, null);
|
|
233
237
|
}
|
|
234
|
-
}).catch(
|
|
238
|
+
}).catch(error => {
|
|
235
239
|
if (!active() || child.generation !== generation) return;
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
child.readDisabled =
|
|
240
|
+
// Unsupported history stays disabled; other errors get a bounded retry
|
|
241
|
+
// only on later activity. Observation never interrupts or resumes work.
|
|
242
|
+
child.readDisabled = error?.code === -32601 || error?.code === '-32601'
|
|
243
|
+
|| /(?:thread[ /]read.*(?:unsupported|not supported)|method (?:not found|not supported))/i.test(String(error?.message || ''));
|
|
244
|
+
child.readFailures = (child.readFailures || 0) + 1;
|
|
245
|
+
child.historyUnavailable = true;
|
|
239
246
|
publish(child, null, { historyUnavailable: true });
|
|
240
247
|
}).finally(() => {
|
|
241
248
|
child.reading = false;
|
|
@@ -258,6 +265,8 @@ function createNativeSubagentObserver({ emit, request, getRoot, ownsParent, isSt
|
|
|
258
265
|
if (known.retiredTurns.size > 100) known.retiredTurns.delete(known.retiredTurns.values().next().value);
|
|
259
266
|
known.turnId = turnId;
|
|
260
267
|
known.generation += 1;
|
|
268
|
+
known.readFailures = 0;
|
|
269
|
+
known.lastRead = -Infinity;
|
|
261
270
|
known.statusEvidence = 0;
|
|
262
271
|
known.interruptionObserved = false;
|
|
263
272
|
}
|
|
@@ -7,14 +7,17 @@ const { SUBAGENT_QUEUE_DIR } = require('./mirrorWorkspace');
|
|
|
7
7
|
if (typeof value !== 'string' || !value.trim()) {
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
|
-
const normalized = value.trim().replace(/\\/g, '/')
|
|
10
|
+
const normalized = value.trim().replace(/\\/g, '/');
|
|
11
11
|
if (!normalized || path.isAbsolute(normalized)) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
let segments = normalized.split('/');
|
|
15
15
|
if (segments.some(segment => segment === '..' || segment === '')) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
+
// All consumers use the same identity, including control guards and locks.
|
|
19
|
+
segments = segments.filter(segment => segment !== '.');
|
|
20
|
+
if (!segments.length) return null;
|
|
18
21
|
if (segments[0] === SUBAGENT_QUEUE_DIR) {
|
|
19
22
|
// The queue control plane (jobs/results/logs/broker.json) is never
|
|
20
23
|
// ownable — but the work/ scratch zone IS: single-file fan-out slices
|