@aiwg/cli 2026.10.9 → 2026.10.10
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/agentic/code/providers/model-capabilities.v1.json +197 -3
- package/bin/aiwg.mjs +8 -0
- package/dist/src/cli/handlers/mc-admission.js +39 -0
- package/dist/src/cli/handlers/mc.js +2 -31
- package/dist/src/cli/handlers/models-help.js +52 -0
- package/dist/src/cli/handlers/models.js +11 -81
- package/dist/src/cli/handlers/use-addon-profile.js +72 -0
- package/dist/src/cli/handlers/use.js +191 -230
- package/dist/src/cli/ui.js +1 -0
- package/dist/src/config/aiwg-config.js +38 -184
- package/dist/src/config/cli-help.js +54 -0
- package/dist/src/config/cli.js +5 -69
- package/dist/src/config/config-normalization.js +10 -0
- package/dist/src/config/worker-activation-gate.js +26 -0
- package/dist/src/config/worker-adoption-cli.js +102 -0
- package/dist/src/config/worker-adoption.js +368 -0
- package/dist/src/config/worker-config-digest.js +14 -0
- package/dist/src/config/worker-group-templates.js +150 -0
- package/dist/src/config/worker-guidance-warning.js +28 -0
- package/dist/src/config/worker-owner-lock.js +152 -0
- package/dist/src/config/worker-parallelism.js +154 -0
- package/dist/src/config/worker-write.js +75 -0
- package/dist/src/decision/telemetry/exporter.js +2 -1
- package/dist/src/decision/telemetry/index.js +1 -0
- package/dist/src/decision/telemetry/mapping.js +43 -0
- package/dist/src/decision/telemetry/otlp-http.js +5 -2
- package/dist/src/decision/telemetry/redaction.js +6 -1
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/telemetry/worker.js +69 -0
- package/dist/src/lint/rule-activation.js +15 -7
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/server.mjs +2 -11
- package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
- package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
- package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
- package/dist/src/mission-protocol/codecs.js +8 -38
- package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
- package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
- package/dist/src/models/conductor-data-policy.d.mts +8 -0
- package/dist/src/models/conductor-data-policy.mjs +190 -0
- package/dist/src/models/config-loader.js +19 -3
- package/dist/src/models/config-writer.js +27 -0
- package/dist/src/models/model-capabilities.v1.json +197 -3
- package/dist/src/models/provider-policy.js +7 -155
- package/dist/src/models/resolver.js +2 -293
- package/dist/src/models/worker-artifact-binding.mjs +12 -0
- package/dist/src/models/worker-direct-readiness-policy.js +155 -0
- package/dist/src/models/worker-direct-readiness.js +9 -218
- package/dist/src/models/worker-health-readiness.js +105 -26
- package/dist/src/models/worker-installation.mjs +12 -14
- package/dist/src/models/worker-member-references.js +64 -0
- package/dist/src/models/worker-plan.js +317 -0
- package/dist/src/models/worker-selection-policy.js +217 -0
- package/dist/src/models/wrapper-deployment.js +1 -1
- package/dist/src/serve/shared-host-admission-checks.js +116 -0
- package/dist/src/serve/shared-host-scheduler.js +25 -107
- package/dist/src/serve/shared-host-starvation.js +73 -0
- package/dist/src/serve/worker-artifacts.d.mts +4 -0
- package/dist/src/serve/worker-artifacts.mjs +56 -0
- package/dist/src/serve/worker-command-request.js +68 -0
- package/dist/src/serve/worker-invocation-paths.js +144 -0
- package/dist/src/serve/worker-invocation-preparation.js +169 -0
- package/dist/src/serve/worker-invocation.js +24 -291
- package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
- package/dist/src/serve/worker-stdin-frame.mjs +6 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
- package/package.json +9 -1
- package/schemas/mission-v1.schema.json +20 -19
- package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
- package/tools/agents/providers/claude-post-deploy.mjs +35 -0
- package/tools/agents/providers/claude.mjs +3 -30
- package/tools/agents/providers/codex-post-deploy.mjs +13 -0
- package/tools/agents/providers/codex.mjs +2 -8
- package/tools/agents/providers/conductor-data-guard.mjs +61 -0
- package/tools/agents/providers/rule-delivery.mjs +3 -16
- package/tools/agents/providers/rule-presentation.mjs +38 -0
- package/tools/plugin/package-plugins.mjs +1 -1
- package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
- package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
- package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
- package/tools/ralph-external/lib/session-attempt.mjs +85 -0
- package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
- package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
- package/tools/ralph-external/lib/worker-final.mjs +143 -0
- package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
- package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
- package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
- package/tools/ralph-external/session-launcher.mjs +73 -217
- package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
|
@@ -46,7 +46,12 @@ import { sampleWorkerIdentity, sameWorkerIdentity, createWorkerIdentityCapture,
|
|
|
46
46
|
qualifyWorkerCpuTickRate, sampleWorkerCpuTime, isSafeWorkerGroupTarget } from './process-monitor.mjs';
|
|
47
47
|
import { createWorkerHealthObserver, sampleOwnedWorktreeContent, classifyWorkerHealthFailure } from './lib/worker-health.mjs';
|
|
48
48
|
import { ScopedFailureBreaker, attributeFailureScope } from './lib/consecutive-failure-circuit-breaker.mjs';
|
|
49
|
+
import { assertManagedWorkerLaunch, validateManagedAttempt } from './lib/managed-worker-attempt.mjs';
|
|
50
|
+
export { assertManagedWorkerLaunch } from './lib/managed-worker-attempt.mjs';
|
|
49
51
|
import { buildSessionEnvironment } from './lib/session-environment.mjs';
|
|
52
|
+
import { withWorkerPath, boundedPrivateRead } from './lib/worker-inputs.mjs';
|
|
53
|
+
import { collectWorkerFinal } from './lib/worker-final.mjs';
|
|
54
|
+
import { resolveRetryReference } from './lib/worker-launch-cli.mjs';
|
|
50
55
|
import { launcherRecoveryEvidence, observeLauncherRecoveryOutcome, recordLauncherRecoveryHint } from './lib/launcher-recovery-evidence.mjs';
|
|
51
56
|
export { observeLauncherRecoveryOutcome } from './lib/launcher-recovery-evidence.mjs';
|
|
52
57
|
import { workerNeedsNativeNamespaces, probeWorkerNativeSandbox, nativeSandboxFailure, nativeSandboxProbeMessage, NATIVE_SANDBOX_UNAVAILABLE, checkWorkerDispatch, detectWorkerIsolation, launchIsolatedWorker, getWorkerIsolationStatus, stopWorkerUnit, readUnitControlGroup } from './lib/worker-isolation.mjs';
|
|
@@ -94,75 +99,9 @@ const bytesDigest = bytes => `sha256:${createHash('sha256').update(bytes).digest
|
|
|
94
99
|
export const beneath = (root, filename) => { const r = relative(root, filename); return r === '' || !r.startsWith('..') && !isAbsolute(r); };
|
|
95
100
|
const signalNumberToName = Object.fromEntries(Object.entries(osConstants.signals ?? {}).map(([name, number]) => [number, name]));
|
|
96
101
|
|
|
97
|
-
/** Resolve the deepest existing ancestor's realpath, then reattach any
|
|
98
|
-
* not-yet-created remainder. A lexical prefix match is not sufficient: a
|
|
99
|
-
* symlinked ancestor of either path can make `beneath()` wrong in either
|
|
100
|
-
* direction, so both sides are resolved before the lexical comparison. */
|
|
101
|
-
async function resolveRealish(target) {
|
|
102
|
-
const parts = target.split('/').filter(Boolean);
|
|
103
|
-
for (let i = parts.length; i >= 0; i--) {
|
|
104
|
-
const candidate = i === 0 ? '/' : `/${parts.slice(0, i).join('/')}`;
|
|
105
|
-
try {
|
|
106
|
-
const real = await realpath(candidate);
|
|
107
|
-
const remainder = parts.slice(i).join('/');
|
|
108
|
-
return remainder ? join(real, remainder) : real;
|
|
109
|
-
} catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
110
|
-
}
|
|
111
|
-
return target;
|
|
112
|
-
}
|
|
113
102
|
export async function realBeneath(root, filename) {
|
|
114
|
-
const
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Linux directory handles pin each ancestor. O_NOFOLLOW on a pathname alone
|
|
119
|
-
// cannot protect an ancestor that the worker replaces after inspection.
|
|
120
|
-
async function withWorkerPath(filename, operation) {
|
|
121
|
-
if (!isAbsolute(filename) || filename.includes('\0') || filename.split('/').includes('..')) throw new Error('Invalid worker path');
|
|
122
|
-
const components = filename.split('/').filter(Boolean);
|
|
123
|
-
const leaf = components.pop();
|
|
124
|
-
let directory = await open('/', constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
125
|
-
try {
|
|
126
|
-
for (const component of components) {
|
|
127
|
-
const next = await open(`/proc/self/fd/${directory.fd}/${component}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
128
|
-
await directory.close(); directory = next;
|
|
129
|
-
}
|
|
130
|
-
return await operation(`/proc/self/fd/${directory.fd}/${leaf ?? '.'}`);
|
|
131
|
-
} finally { await directory.close(); }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// #2915 review 4 (finding 4): `privateOutput: true` REPAIRS a loose mode
|
|
135
|
-
// (chmod 0600) rather than rejecting it — correct for the worker's own
|
|
136
|
-
// final-output file, which the launcher itself created and is free to
|
|
137
|
-
// re-tighten. `rejectInsecureMode: true` additionally REFUSES a mode other
|
|
138
|
-
// than exactly 0600 instead of repairing it, for evidence this launcher
|
|
139
|
-
// did NOT create moments ago (retry predecessor metadata) — a 0666 file
|
|
140
|
-
// there is itself a signal worth refusing on, not silently normalizing
|
|
141
|
-
// away.
|
|
142
|
-
async function boundedPrivateRead(filename, limit, inspect, privateOutput = false, rejectInsecureMode = false) {
|
|
143
|
-
if (!await inspect(filename, 'file')) throw new Error('Required worker input is missing');
|
|
144
|
-
const handle = await withWorkerPath(filename, target => open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK));
|
|
145
|
-
try {
|
|
146
|
-
const stat = await handle.stat();
|
|
147
|
-
if (!stat.isFile() || stat.nlink !== 1 || stat.size > limit) throw new Error('Worker input must be a bounded unaliased regular file');
|
|
148
|
-
if (privateOutput) {
|
|
149
|
-
if (stat.uid !== process.getuid()) throw new Error('Worker final must be conductor-owned');
|
|
150
|
-
if (rejectInsecureMode) {
|
|
151
|
-
if ((stat.mode & 0o777) !== 0o600) throw new Error('Worker input mode must already be exactly 0600; insecure modes are refused, not repaired');
|
|
152
|
-
} else {
|
|
153
|
-
await handle.chmod(0o600);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const buffer = Buffer.alloc(limit + 1);
|
|
157
|
-
let length = 0;
|
|
158
|
-
while (length < buffer.length) {
|
|
159
|
-
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
|
|
160
|
-
if (!bytesRead) break;
|
|
161
|
-
length += bytesRead;
|
|
162
|
-
}
|
|
163
|
-
if (length > limit) throw new Error('Worker input exceeds bound');
|
|
164
|
-
return buffer.subarray(0, length);
|
|
165
|
-
} finally { await handle.close(); }
|
|
103
|
+
const paths = await importImpl(import.meta.url, 'serve/worker-invocation-paths.js');
|
|
104
|
+
return paths.realBeneath(root, filename);
|
|
166
105
|
}
|
|
167
106
|
|
|
168
107
|
export async function publishWorkerRecord(runDir, name, value, inspect) {
|
|
@@ -330,55 +269,8 @@ function checkDispatchGuard(dispatchGuard) {
|
|
|
330
269
|
}
|
|
331
270
|
}
|
|
332
271
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
* future declaration kind and are refused here. */
|
|
336
|
-
export async function materializeWorkerArtifacts(invocation, runDir) {
|
|
337
|
-
const artifacts = invocation.artifacts ?? [];
|
|
338
|
-
if (!Array.isArray(artifacts) || artifacts.length > 16) throw new Error('Worker artifact count exceeds bound');
|
|
339
|
-
const names = new Set();
|
|
340
|
-
let total = 0;
|
|
341
|
-
for (const artifact of artifacts) {
|
|
342
|
-
if (!artifact || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(artifact.name)
|
|
343
|
-
|| /[^A-Za-z0-9_-]/.test(artifact.name) || names.has(artifact.name) || typeof artifact.content !== 'string'
|
|
344
|
-
|| artifact.location !== undefined && artifact.location !== 'run-directory') throw new Error('Invalid worker artifact declaration');
|
|
345
|
-
names.add(artifact.name);
|
|
346
|
-
const size = Buffer.byteLength(artifact.content);
|
|
347
|
-
total += size;
|
|
348
|
-
if (size > 1048576 || total > 4194304) throw new Error('Worker artifact bytes exceed bound');
|
|
349
|
-
}
|
|
350
|
-
const paths = new Map(artifacts.map(artifact => [artifact.name, join(runDir, `artifact-${artifact.name}`)]));
|
|
351
|
-
const argv = invocation.argv.map(token => {
|
|
352
|
-
if (!token.includes('${artifact:')) return token;
|
|
353
|
-
const match = /^\$\{artifact:([A-Za-z0-9][A-Za-z0-9_-]{0,63})\}$/.exec(token);
|
|
354
|
-
if (!match || !paths.has(match[1])) throw new Error('Undeclared worker artifact placeholder');
|
|
355
|
-
return paths.get(match[1]);
|
|
356
|
-
});
|
|
357
|
-
const { inspectWorkerPath } = await importImpl(import.meta.url, 'serve/worker-invocation.js');
|
|
358
|
-
await inspectWorkerPath(runDir, 'directory');
|
|
359
|
-
const stat = await lstat(runDir);
|
|
360
|
-
if (stat.uid !== process.getuid() || (stat.mode & 0o077)) throw new Error('Worker artifact directory must be private and owned');
|
|
361
|
-
const created = [];
|
|
362
|
-
const cleanup = async () => {
|
|
363
|
-
const results = await Promise.allSettled(created.map(filename =>
|
|
364
|
-
withWorkerPath(filename, target => unlink(target)).catch(error => {
|
|
365
|
-
if (error.code !== 'ENOENT') throw error;
|
|
366
|
-
})));
|
|
367
|
-
const failed = results.find(result => result.status === 'rejected');
|
|
368
|
-
if (failed) throw failed.reason;
|
|
369
|
-
};
|
|
370
|
-
try {
|
|
371
|
-
for (const artifact of artifacts) {
|
|
372
|
-
const filename = paths.get(artifact.name);
|
|
373
|
-
const handle = await withWorkerPath(filename, target => open(target,
|
|
374
|
-
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600));
|
|
375
|
-
created.push(filename);
|
|
376
|
-
try { await handle.writeFile(artifact.content); await handle.sync(); }
|
|
377
|
-
finally { await handle.close(); }
|
|
378
|
-
}
|
|
379
|
-
return { argv, cleanup };
|
|
380
|
-
} catch (error) { await cleanup(); throw error; }
|
|
381
|
-
}
|
|
272
|
+
import { materializeWorkerArtifacts, WorkerArtifactLifecycle } from './lib/worker-artifacts.mjs';
|
|
273
|
+
export { materializeWorkerArtifacts } from './lib/worker-artifacts.mjs';
|
|
382
274
|
|
|
383
275
|
const _workerRecoverySubscriptions = new WeakMap();
|
|
384
276
|
const _workerRecoveryReporters = new WeakMap();
|
|
@@ -413,45 +305,6 @@ function subscribeWorkerRecovery(owner, breaker) {
|
|
|
413
305
|
_workerRecoverySubscriptions.set(owner, () => reporter.owners.delete(owner));
|
|
414
306
|
}
|
|
415
307
|
|
|
416
|
-
/** Reuse the MC authority and its registered one-shot identity at every spawn boundary. */
|
|
417
|
-
export async function assertManagedWorkerLaunch(options, invocation, inputs, projectRoot = process.cwd()) {
|
|
418
|
-
if (!options.managedAdmission || invocation.mode !== 'managed'
|
|
419
|
-
|| inputs.snapshot.config.parallelism?.worker_mode !== 'managed') throw new Error('Managed worker admission is required');
|
|
420
|
-
const { createMcAdmissionScheduler } = await importImpl(import.meta.url, 'cli/handlers/mc.js');
|
|
421
|
-
const { loadLauncherRegistry } = await importImpl(import.meta.url, 'cli/handlers/ralph-launcher.js');
|
|
422
|
-
const { workerModelKey } = await importImpl(import.meta.url, 'models/provider-policy.js');
|
|
423
|
-
const scheduler = await createMcAdmissionScheduler(projectRoot, inputs.snapshot.config);
|
|
424
|
-
const { requestId, generation } = options.managedAdmission;
|
|
425
|
-
const snapshot = scheduler.snapshot();
|
|
426
|
-
const record = snapshot.records[requestId];
|
|
427
|
-
const deadlineAtMs = Math.min(...[record?.accounting?.runKey, record?.accounting?.taskKey]
|
|
428
|
-
.map(key => snapshot.allowances?.[key]?.deadlineAt).filter(Boolean).map(Date.parse));
|
|
429
|
-
const registered = loadLauncherRegistry(projectRoot).loops[process.env.RALPH_LOOP_ID];
|
|
430
|
-
const worker = record?.managed;
|
|
431
|
-
const reservation = record?.accounting?.reservation;
|
|
432
|
-
if (!record || record.state !== 'admitted' || record.leaseGeneration !== generation
|
|
433
|
-
|| !record.launchClaimedAt || !record.metadata?.invocationId || Date.parse(record.leaseExpiresAt ?? '') <= Date.now()
|
|
434
|
-
|| deadlineAtMs <= Date.now()
|
|
435
|
-
|| worker?.kind !== 'external' || worker.group !== invocation.selected.group
|
|
436
|
-
|| worker.member !== invocation.selected.member || worker.cli !== invocation.selected.cli
|
|
437
|
-
|| worker.model !== workerModelKey(invocation.selected.inferenceProvider, invocation.selected.model)
|
|
438
|
-
|| reservation?.tokens != null && !(invocation.limits.tokens <= reservation.tokens)
|
|
439
|
-
|| reservation?.costUsd != null && !(invocation.limits.costUsd !== null && invocation.limits.costUsd <= reservation.costUsd)
|
|
440
|
-
|| registered?.managedAdmission?.requestId !== requestId || registered.managedAdmission.generation !== generation
|
|
441
|
-
|| registered.managedWorker?.invocationPath !== options.invocationPath
|
|
442
|
-
|| registered.managedWorker?.runDir !== options.runDir) {
|
|
443
|
-
throw new Error('Managed worker launch differs from the current claimed admission');
|
|
444
|
-
}
|
|
445
|
-
if (record.execution) {
|
|
446
|
-
const identity = sampleWorkerIdentity(process.pid);
|
|
447
|
-
if (record.execution.pid !== process.pid || !identity?.bootId || !identity.startTicks
|
|
448
|
-
|| record.execution.startedAt !== `${identity.bootId}:${identity.startTicks}:${identity.uid}`) {
|
|
449
|
-
throw new Error('Managed worker supervisor identity differs');
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
return { ...record, deadlineAtMs };
|
|
453
|
-
}
|
|
454
|
-
|
|
455
308
|
async function workerDispatchPath(filename) {
|
|
456
309
|
let candidate = filename;
|
|
457
310
|
while (dirname(candidate) !== candidate) {
|
|
@@ -491,8 +344,25 @@ function workerLaunchRefusal(outcome) {
|
|
|
491
344
|
// Neither worker output nor a caller-supplied result can populate this context.
|
|
492
345
|
const directStartupCorrectionContext = new WeakMap();
|
|
493
346
|
|
|
347
|
+
// Each attempt directory is created exclusively; its parent is the conductor's job.
|
|
348
|
+
async function createAttemptRunDirectory(runDir) {
|
|
349
|
+
try { await mkdir(runDir, { mode: 0o700 }); } catch (error) {
|
|
350
|
+
if (error?.code === 'ENOENT') throw new Error(`Worker run directory parent does not exist: ${dirname(runDir)}; create it (mode 0700) before launch`);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
494
355
|
async function launchOneWorker(owner, options) {
|
|
495
|
-
const { inspectWorkerPath: inspect, revalidateWorkerInvocationPlan:
|
|
356
|
+
const { inspectWorkerPath: inspect, revalidateWorkerInvocationPlan: validatePlan } = await importImpl(import.meta.url, 'serve/worker-invocation.js');
|
|
357
|
+
// Forward the saved-plan path: invocation identity is verified against it (#2905).
|
|
358
|
+
const validate = async (plan, snapshot, drivers, now, planPath = options.invocationPath) => {
|
|
359
|
+
const validateAt = (p, s, d, n) => validatePlan(p, s, d, n, planPath);
|
|
360
|
+
if (plan.mode !== 'managed' || !options.managedAdmission) return validateAt(plan, snapshot, drivers, now);
|
|
361
|
+
const checked = await validateManagedAttempt(options, plan, { snapshot, drivers }, now, validateAt,
|
|
362
|
+
attempt => publishWorkerRecord(dirname(options.invocationPath), `${basename(options.invocationPath)}.attempt.json`, attempt, inspect));
|
|
363
|
+
options.attempt = checked.attempt;
|
|
364
|
+
return checked.invocation;
|
|
365
|
+
};
|
|
496
366
|
const { qualificationDigest } = await importImpl(import.meta.url, 'models/qualification-digest.mjs');
|
|
497
367
|
const planBytes = await boundedPrivateRead(options.invocationPath, 1048576, inspect);
|
|
498
368
|
const saved = JSON.parse(planBytes.toString('utf8'));
|
|
@@ -502,9 +372,11 @@ async function launchOneWorker(owner, options) {
|
|
|
502
372
|
let inputs = await loadInputs();
|
|
503
373
|
const clock = () => typeof options.now === 'function' ? options.now() : options.now ?? new Date().toISOString();
|
|
504
374
|
const now = clock();
|
|
505
|
-
const
|
|
375
|
+
const { assertConductorWorkerPlan } = await importImpl(import.meta.url, 'serve/worker-invocation-paths.js');
|
|
376
|
+
await assertConductorWorkerPlan(saved, options.invocationPath, inputs.drivers);
|
|
377
|
+
const invocation = await validate(saved, inputs.snapshot, inputs.drivers, now, options.invocationPath);
|
|
506
378
|
if (invocation.mode === 'managed' || options.managedAdmission) {
|
|
507
|
-
const admitted = await assertManagedWorkerLaunch(options, invocation, inputs);
|
|
379
|
+
const admitted = await assertManagedWorkerLaunch(options, invocation, inputs, options.projectRoot ?? process.cwd());
|
|
508
380
|
options = { ...options, scopedBreaker: null, managedInvocationId: admitted.metadata.invocationId,
|
|
509
381
|
managedDeadlineAtMs: admitted.deadlineAtMs };
|
|
510
382
|
}
|
|
@@ -536,12 +408,12 @@ async function launchOneWorker(owner, options) {
|
|
|
536
408
|
// check too.
|
|
537
409
|
let verifiedPreviousAttemptId = null;
|
|
538
410
|
if (options?.retryOf !== undefined) {
|
|
539
|
-
const exitRecord = await verifyRetryPredecessor(options.retryOf,
|
|
411
|
+
const exitRecord = await verifyRetryPredecessor(await resolveRetryReference(options.retryOf, { inspect }),
|
|
540
412
|
{ newInvocationPath: options?.invocationPath, inspect, workerWritableRoots: writableRoots });
|
|
541
413
|
verifiedPreviousAttemptId = exitRecord.invocationId;
|
|
542
414
|
}
|
|
543
|
-
const paths =
|
|
544
|
-
if (new Set(Object.values(paths)).size !==
|
|
415
|
+
const paths = (await importImpl(import.meta.url, 'serve/worker-invocation-paths.js')).workerOutputPaths(invocation);
|
|
416
|
+
if (new Set(Object.values(paths)).size !== Object.keys(paths).length || Object.values(paths).some(filename =>
|
|
545
417
|
filename === options.invocationPath || filename === invocation.briefPath
|
|
546
418
|
|| dirname(filename) === runDir && basename(filename).startsWith('artifact-')
|
|
547
419
|
|| ['invocation.json', 'process.json', 'exit.json', 'health.json', 'final-evidence.json', 'worker-start.json', 'worker-identity.json', 'worker-exit.json',
|
|
@@ -571,7 +443,7 @@ async function launchOneWorker(owner, options) {
|
|
|
571
443
|
const isolation = invocation.isolation === 'native-sandbox' ? { mode: 'native-sandbox' }
|
|
572
444
|
: options.isolation ?? detectWorkerIsolation();
|
|
573
445
|
if (isolation.mode === 'systemd-unit' && workerNeedsNativeNamespaces(adapter, permissions)) {
|
|
574
|
-
const probe = await (options.probeNativeSandbox ?? probeWorkerNativeSandbox)({ cwd: invocation.cwd,
|
|
446
|
+
const probe = await (options.probeNativeSandbox ?? probeWorkerNativeSandbox)({ adapter, cwd: invocation.cwd,
|
|
575
447
|
envNames: invocation.envNames, env: Object.fromEntries(invocation.envNames.filter(name => process.env[name] !== undefined)
|
|
576
448
|
.map(name => [name, process.env[name]])) });
|
|
577
449
|
if (!probe.ok) throw new WorkerPolicyError(NATIVE_SANDBOX_UNAVAILABLE, nativeSandboxProbeMessage(probe));
|
|
@@ -608,7 +480,7 @@ async function launchOneWorker(owner, options) {
|
|
|
608
480
|
// themselves; pre-spawn refusals above never reach this point. Global
|
|
609
481
|
// dispatch AND per-CLI enablement are checked inside `admitDirectWorker`
|
|
610
482
|
// before any breaker construction (#2915 final review, finding 1).
|
|
611
|
-
const invocationId = randomUUID();
|
|
483
|
+
const invocationId = invocation.invocationId ?? randomUUID();
|
|
612
484
|
// #2915 review 4 (finding 8): computed ONCE, before admission, so the
|
|
613
485
|
// SAME digest both gates the allowance reservation here and is published
|
|
614
486
|
// into invocation.json below — never recomputed from a possibly-stale
|
|
@@ -718,13 +590,15 @@ async function launchOneWorker(owner, options) {
|
|
|
718
590
|
throw error;
|
|
719
591
|
}
|
|
720
592
|
}
|
|
593
|
+
// Created before run-directory preparation so its failure path can report it.
|
|
594
|
+
const artifactLifecycle = new WorkerArtifactLifecycle(invocation, invocationId);
|
|
721
595
|
// #2915 review 4 (finding 2): this run-directory preparation is
|
|
722
596
|
// pre-spawn work happening AFTER a successful admission above — any
|
|
723
597
|
// failure here (mkdir, ownership, the `.launch-owner` write) must settle
|
|
724
598
|
// the reservation exactly like every other pre-spawn exit, never leak it
|
|
725
599
|
// by throwing before the big try/catch below is reached.
|
|
726
600
|
try {
|
|
727
|
-
if (!runExists) await
|
|
601
|
+
if (!runExists) await createAttemptRunDirectory(runDir); // exclusive attempt: no recursive creation or reuse
|
|
728
602
|
const runStat = await lstat(runDir);
|
|
729
603
|
if (runStat.uid !== process.getuid()) throw new Error('Worker run directory must be conductor-owned');
|
|
730
604
|
await withWorkerPath(runDir, async target => { const handle = await open(target, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
@@ -736,6 +610,7 @@ async function launchOneWorker(owner, options) {
|
|
|
736
610
|
// release without leaving a marker even past the canary deadline.
|
|
737
611
|
await settleLedger({ kind: 'uncertain', confirmedStop: true });
|
|
738
612
|
if (ledgerDiagnostics.length) error.ledgerDiagnostics = ledgerDiagnostics;
|
|
613
|
+
artifactLifecycle.failure(error);
|
|
739
614
|
throw error;
|
|
740
615
|
}
|
|
741
616
|
const supervisor = sampleWorkerIdentity(process.pid);
|
|
@@ -754,6 +629,7 @@ async function launchOneWorker(owner, options) {
|
|
|
754
629
|
// own outcome.
|
|
755
630
|
let latestProcessRecord = null;
|
|
756
631
|
const publish = (name, value) => {
|
|
632
|
+
artifactLifecycle.observe(name, value);
|
|
757
633
|
if (name === 'process.json') {
|
|
758
634
|
value = { ...value, recovery: { ...admission.breaker.recoveryStatus(), observedAt: new Date().toISOString() } };
|
|
759
635
|
if (options.startupCorrectionRecord) value.startupCorrection = options.startupCorrectionRecord;
|
|
@@ -790,6 +666,7 @@ async function launchOneWorker(owner, options) {
|
|
|
790
666
|
// task — never from an unverified caller claim.
|
|
791
667
|
previousAttemptId: verifiedPreviousAttemptId,
|
|
792
668
|
requestId: invocationId, taskDigest, role: saved.request.role,
|
|
669
|
+
...(options.managedAdmission ? { attempt: options.attempt } : {}),
|
|
793
670
|
selected: invocation.selected, planDigest: qualificationDigest(saved), invocationDigest: qualificationDigest(invocation),
|
|
794
671
|
policyDigest: invocation.policyDigest, catalogDigest: invocation.catalogDigest, profileDigest: invocation.profileDigest,
|
|
795
672
|
expiresAt: invocation.expiresAt, argv: displayArgv, commandDisplay: displayArgv.map(token => JSON.stringify(token)).join(' '),
|
|
@@ -813,7 +690,7 @@ async function launchOneWorker(owner, options) {
|
|
|
813
690
|
}
|
|
814
691
|
// Recheck policy, prompt, installation and exact bindings after file preparation.
|
|
815
692
|
inputs = await loadInputs();
|
|
816
|
-
await validate(saved, inputs.snapshot, inputs.drivers, clock());
|
|
693
|
+
await validate(saved, inputs.snapshot, inputs.drivers, clock(), options.invocationPath);
|
|
817
694
|
gate = (options.checkDispatch ?? checkWorkerDispatch)({ config: inputs.snapshot.config,
|
|
818
695
|
paths: await Promise.all([invocation.cwd, runDir].map(workerDispatchPath)) });
|
|
819
696
|
if (!gate.ok) throw workerDispatchRefusal(gate);
|
|
@@ -875,6 +752,7 @@ async function launchOneWorker(owner, options) {
|
|
|
875
752
|
// real child's own queued events (exit/close) race ahead of
|
|
876
753
|
// `captureOneWorker`'s own listener attachment in several tests (and
|
|
877
754
|
// would in production).
|
|
755
|
+
artifactLifecycle.spawning();
|
|
878
756
|
const launchResult = (options.launchIsolated ?? launchIsolatedWorker)({ name: invocationId,
|
|
879
757
|
command: artifacts.argv[0], args: artifacts.argv.slice(1), cwd: invocation.cwd,
|
|
880
758
|
memoryMaxGb: options.memoryMaxGb ?? 4, gate, config: inputs.snapshot.config, isolation, workerGroup: invocation.selected.group,
|
|
@@ -988,6 +866,7 @@ async function launchOneWorker(owner, options) {
|
|
|
988
866
|
const graceMs = (invocation.health?.terminateGraceSeconds ?? 5) * 1000;
|
|
989
867
|
const { confirmedStop } = await resolveStopEvidence({ isolationMode: outcome.mode,
|
|
990
868
|
unitName: outcome.unit ?? null, child: outcome.child, graceMs });
|
|
869
|
+
artifactLifecycle.confirmedStop = confirmedStop;
|
|
991
870
|
await settleLedger({ kind: 'uncertain', confirmedStop });
|
|
992
871
|
// #2915 review 6 (finding 3d): when this bounded stop escalation DOES
|
|
993
872
|
// confirm termination, also call the explicit reconciliation -- a
|
|
@@ -1005,7 +884,7 @@ async function launchOneWorker(owner, options) {
|
|
|
1005
884
|
} finally {
|
|
1006
885
|
admission.breaker.removeListener('transition', onScopeTransition);
|
|
1007
886
|
await publishing;
|
|
1008
|
-
try { await artifacts
|
|
887
|
+
try { await artifactLifecycle.finish(artifacts); } finally {
|
|
1009
888
|
// Never chmod a worker-replaced pathname; only the inodes we opened.
|
|
1010
889
|
for (const handle of handles) { await handle.chmod(0o600).catch(() => {}); await handle.close().catch(() => {}); }
|
|
1011
890
|
await inspect(runDir, 'directory');
|
|
@@ -1075,7 +954,7 @@ async function captureOneWorker(owner, ctx) {
|
|
|
1075
954
|
// For systemd-unit mode the unit's MainPID is not known synchronously; it
|
|
1076
955
|
// is still resolved once inside observeIdentity.
|
|
1077
956
|
let nativeSupervisor = hasTransport && outcome.mode !== 'systemd-unit' ? sampleNativeSupervisor(child.pid) : null;
|
|
1078
|
-
const streams = [handles[0], handles[1], handles[
|
|
957
|
+
const streams = [handles[0], handles[1], handles[Object.keys(paths).length], handles[Object.keys(paths).length + 1]].map(handle => createWriteStream('', { fd: handle.fd, autoClose: false }));
|
|
1079
958
|
let identityObservation = 'pending';
|
|
1080
959
|
let reportedWorker = null;
|
|
1081
960
|
let output = Buffer.alloc(0);
|
|
@@ -1309,26 +1188,10 @@ async function captureOneWorker(owner, ctx) {
|
|
|
1309
1188
|
let healthFinalValid = false;
|
|
1310
1189
|
let healthAttemptFinalValid = false;
|
|
1311
1190
|
try {
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
if (finalStat.size === 0 && driver.extractFinal) {
|
|
1317
|
-
const final = driver.extractFinal(output.toString('utf8'));
|
|
1318
|
-
if (final === null || final === undefined || final.success === false) throw new Error('Missing or unsuccessful final extraction');
|
|
1319
|
-
const bytes = JSON.stringify({ invocationId, source: 'adapter-extraction', candidate: true, verified: false, result: final });
|
|
1320
|
-
if (Buffer.byteLength(bytes) > 262144) throw new Error('Final exceeds bound');
|
|
1321
|
-
await handles[2].writeFile(bytes); extracted = true;
|
|
1322
|
-
}
|
|
1323
|
-
const finalBytes = await boundedPrivateRead(paths.final, 262144, ctx.inspect, true);
|
|
1324
|
-
if (!finalBytes.length) throw new Error('Missing final output');
|
|
1325
|
-
if (!extracted && driver.extractFinal) {
|
|
1326
|
-
const parsed = driver.extractFinal(finalBytes.toString('utf8'));
|
|
1327
|
-
if (parsed === null || parsed === undefined || parsed.success === false) throw new Error('Invalid final output');
|
|
1328
|
-
}
|
|
1329
|
-
await publish('final-evidence.json', { invocationId, path: paths.final, digest: bytesDigest(finalBytes),
|
|
1330
|
-
source: extracted ? 'adapter-extraction' : 'worker-output', candidate: true, verified: false,
|
|
1331
|
-
terminalSuccess: result.exitCode === 0 && !result.timedOut && !result.errorCode });
|
|
1191
|
+
const { finalBytes, extracted, parseFinal } = await collectWorkerFinal({
|
|
1192
|
+
path: paths.final, workdir: invocation.cwd, driver, output, invocationId, result,
|
|
1193
|
+
inspect: ctx.inspect, handle: handles[2], publish, outputFraming: ctx.profile.health?.outputFraming,
|
|
1194
|
+
});
|
|
1332
1195
|
// Consume #2890's strict final contract when supplied by the installed
|
|
1333
1196
|
// driver. Legacy extraction remains a candidate, never health authority.
|
|
1334
1197
|
if (driver.parseWorkerFinal) {
|
|
@@ -1336,7 +1199,7 @@ async function captureOneWorker(owner, ctx) {
|
|
|
1336
1199
|
// Strict validation consumes original bytes. A legacy extracted
|
|
1337
1200
|
// object may already have lost duplicate keys and cannot authorize
|
|
1338
1201
|
// completion through reserialization.
|
|
1339
|
-
const claims = await
|
|
1202
|
+
const claims = await parseFinal(extracted ? output : finalBytes, {
|
|
1340
1203
|
format: extracted && ctx.profile.health?.outputFraming === 'jsonl' ? 'jsonl' : 'json', observation: { transport: 'shell',
|
|
1341
1204
|
status: result.exitCode === 0 && !result.errorCode && !result.timedOut ? 'completed' : 'failed',
|
|
1342
1205
|
termination: result.termination, exitCode: result.exitCode } });
|
|
@@ -1601,7 +1464,8 @@ async function captureOneWorker(owner, ctx) {
|
|
|
1601
1464
|
//
|
|
1602
1465
|
// `launchOneWorkerBaseline` and `captureOneWorkerBaseline` reproduce
|
|
1603
1466
|
// `launchOneWorker`/`captureOneWorker`, apart from the lifecycle fixes above,
|
|
1604
|
-
//
|
|
1467
|
+
// with shared workerClaims/digest validation (#3675) applied to both paths,
|
|
1468
|
+
// otherwise as they existed at base commit `a4085b86a` (`git show
|
|
1605
1469
|
// a4085b86a:tools/ralph-external/session-launcher.mjs`) — before any
|
|
1606
1470
|
// #2915 direct-recovery code existed in this file. `launchOneWorker`
|
|
1607
1471
|
// above calls `launchOneWorkerBaseline` for every attempt
|
|
@@ -1633,6 +1497,7 @@ async function captureOneWorker(owner, ctx) {
|
|
|
1633
1497
|
// `a4085b86a` called them — with NO `onDispatchCommit` — which that
|
|
1634
1498
|
// module's own dual-mode contract (`_runDispatchCommit`) defines as
|
|
1635
1499
|
// "behaves exactly as before the hook existed";
|
|
1500
|
+
// - shared final validation (`lib/worker-final.mjs`, #3675);
|
|
1636
1501
|
// - unchanged imports (`process-monitor.mjs`, `lib/worker-health.mjs`,
|
|
1637
1502
|
// `serve/worker-invocation.js`, `models/qualification-digest.mjs`).
|
|
1638
1503
|
// Neither function imports, reads, or references the breaker, the
|
|
@@ -1647,12 +1512,13 @@ async function launchOneWorkerBaseline(owner, options, ctx) {
|
|
|
1647
1512
|
loadInputs, validate, inspect, clock, qualificationDigest } = ctx;
|
|
1648
1513
|
let inputs = ctx.inputs;
|
|
1649
1514
|
let localGate = gate;
|
|
1650
|
-
if (!runExists) await
|
|
1515
|
+
if (!runExists) await createAttemptRunDirectory(runDir); // exclusive attempt: no recursive creation or reuse
|
|
1651
1516
|
const runStat = await lstat(runDir);
|
|
1652
1517
|
if (runStat.uid !== process.getuid()) throw new Error('Worker run directory must be conductor-owned');
|
|
1653
1518
|
await withWorkerPath(runDir, async target => { const handle = await open(target, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
1654
1519
|
try { await handle.chmod(0o700); } finally { await handle.close(); } });
|
|
1655
|
-
const invocationId = options.managedAdmission ? options.managedInvocationId : randomUUID();
|
|
1520
|
+
const invocationId = invocation.invocationId ?? (options.managedAdmission ? options.managedInvocationId : randomUUID());
|
|
1521
|
+
if (options.managedAdmission && invocationId !== options.managedInvocationId) throw new Error('Worker artifact invocation id differs from managed admission');
|
|
1656
1522
|
const reservation = await withWorkerPath(join(runDir, '.launch-owner'), target => open(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600));
|
|
1657
1523
|
try { await reservation.writeFile(invocationId); await reservation.sync(); } finally { await reservation.close(); }
|
|
1658
1524
|
const supervisor = sampleWorkerIdentity(process.pid);
|
|
@@ -1664,7 +1530,9 @@ async function launchOneWorkerBaseline(owner, options, ctx) {
|
|
|
1664
1530
|
const writeRecord = options.publishRecord ?? publishWorkerRecord;
|
|
1665
1531
|
const publishFailures = [];
|
|
1666
1532
|
let publishing = Promise.resolve();
|
|
1533
|
+
const artifactLifecycle = new WorkerArtifactLifecycle(invocation, invocationId);
|
|
1667
1534
|
const publish = (name, value) => {
|
|
1535
|
+
artifactLifecycle.observe(name, value);
|
|
1668
1536
|
const write = publishing.catch(() => {}).then(() => writeRecord(runDir, name, value, inspect));
|
|
1669
1537
|
publishing = write.catch(error => { publishFailures.push({ name, error: error?.message ?? String(error) }); });
|
|
1670
1538
|
return write;
|
|
@@ -1678,6 +1546,7 @@ async function launchOneWorkerBaseline(owner, options, ctx) {
|
|
|
1678
1546
|
const displayArgv = artifacts.argv.map((token, i) => i === 0 ? token : `<argument:${bytesDigest(token)}>`);
|
|
1679
1547
|
await publish('invocation.json', { version: 1, origin: 'launcher', invocationId, attemptId: invocationId,
|
|
1680
1548
|
previousAttemptId: verifiedPreviousAttemptId ?? null, requestId: options.managedAdmission?.requestId ?? invocationId, taskDigest: qualificationDigest(saved.request), role: saved.request.role,
|
|
1549
|
+
...(options.managedAdmission ? { attempt: options.attempt } : {}),
|
|
1681
1550
|
selected: invocation.selected, planDigest: qualificationDigest(saved), invocationDigest: qualificationDigest(invocation),
|
|
1682
1551
|
policyDigest: invocation.policyDigest, catalogDigest: invocation.catalogDigest, profileDigest: invocation.profileDigest,
|
|
1683
1552
|
expiresAt: invocation.expiresAt, argv: displayArgv, commandDisplay: displayArgv.map(token => JSON.stringify(token)).join(' '),
|
|
@@ -1701,7 +1570,7 @@ async function launchOneWorkerBaseline(owner, options, ctx) {
|
|
|
1701
1570
|
}
|
|
1702
1571
|
// Recheck policy, prompt, installation and exact bindings after file preparation.
|
|
1703
1572
|
inputs = await loadInputs();
|
|
1704
|
-
await validate(saved, inputs.snapshot, inputs.drivers, clock());
|
|
1573
|
+
await validate(saved, inputs.snapshot, inputs.drivers, clock(), options.invocationPath);
|
|
1705
1574
|
localGate = (options.checkDispatch ?? checkWorkerDispatch)({ config: inputs.snapshot.config,
|
|
1706
1575
|
paths: await Promise.all([invocation.cwd, runDir].map(workerDispatchPath)) });
|
|
1707
1576
|
if (!localGate.ok) throw workerDispatchRefusal(localGate);
|
|
@@ -1718,13 +1587,14 @@ async function launchOneWorkerBaseline(owner, options, ctx) {
|
|
|
1718
1587
|
// gated path's own test doubles, not a behavior this branch adds):
|
|
1719
1588
|
// `await`ing a value that was never a Promise resolves to that exact
|
|
1720
1589
|
// value with no observable difference.
|
|
1590
|
+
artifactLifecycle.spawning();
|
|
1721
1591
|
const launchResult = (options.launchIsolated ?? launchIsolatedWorker)({ name: invocationId,
|
|
1722
1592
|
command: artifacts.argv[0], args: artifacts.argv.slice(1), cwd: invocation.cwd,
|
|
1723
1593
|
memoryMaxGb: options.memoryMaxGb ?? 4, gate: localGate, config: inputs.snapshot.config, isolation,
|
|
1724
1594
|
workerGroup: invocation.selected.group,
|
|
1725
1595
|
allowDegraded: !!options.allowDegraded, attached: true, ownedGroup: true,
|
|
1726
1596
|
...(options.managedAdmission ? { onDispatchCommit: async () => {
|
|
1727
|
-
const admitted = await assertManagedWorkerLaunch(options, invocation, inputs);
|
|
1597
|
+
const admitted = await assertManagedWorkerLaunch(options, invocation, inputs, options.projectRoot ?? process.cwd());
|
|
1728
1598
|
options.managedDeadlineAtMs = Math.min(options.managedDeadlineAtMs, admitted.deadlineAtMs);
|
|
1729
1599
|
} } : {}),
|
|
1730
1600
|
...(options.managedAdmission ? { onSpawnAttempt: () => { managedSpawnAttempted = true; } } : {}),
|
|
@@ -1739,9 +1609,10 @@ async function launchOneWorkerBaseline(owner, options, ctx) {
|
|
|
1739
1609
|
...(options.managedAdmission ? { managedAdmission: options.managedAdmission } : {}),
|
|
1740
1610
|
endedAt: new Date().toISOString(), isolation: isolation.mode, termination: managedSpawnAttempted ? 'uncertain' : 'confirmed',
|
|
1741
1611
|
descendants: managedSpawnAttempted ? 'unknown' : 'not-started', errorCode: error.code ?? 'LAUNCH_REFUSED' });
|
|
1612
|
+
artifactLifecycle.failure(error);
|
|
1742
1613
|
throw error;
|
|
1743
1614
|
} finally {
|
|
1744
|
-
try { await artifacts
|
|
1615
|
+
try { await artifactLifecycle.finish(artifacts); } finally {
|
|
1745
1616
|
// Never chmod a worker-replaced pathname; only the inodes we opened.
|
|
1746
1617
|
for (const handle of handles) { await handle.chmod(0o600).catch(() => {}); await handle.close().catch(() => {}); }
|
|
1747
1618
|
await inspect(runDir, 'directory');
|
|
@@ -1761,7 +1632,7 @@ async function captureOneWorkerBaseline(owner, ctx) {
|
|
|
1761
1632
|
let identity = immediate?.pid === child.pid && immediate?.pid === immediate?.pgid
|
|
1762
1633
|
&& immediate?.uid === process.getuid?.() && immediate?.parentPid === process.pid ? immediate : null;
|
|
1763
1634
|
let nativeSupervisor = hasTransport && outcome.mode !== 'systemd-unit' ? sampleNativeSupervisor(child.pid) : null;
|
|
1764
|
-
const streams = [handles[0], handles[1], handles[
|
|
1635
|
+
const streams = [handles[0], handles[1], handles[Object.keys(paths).length], handles[Object.keys(paths).length + 1]].map(handle => createWriteStream('', { fd: handle.fd, autoClose: false }));
|
|
1765
1636
|
let identityObservation = 'pending';
|
|
1766
1637
|
let reportedWorker = null;
|
|
1767
1638
|
let output = Buffer.alloc(0);
|
|
@@ -1944,29 +1815,13 @@ async function captureOneWorkerBaseline(owner, ctx) {
|
|
|
1944
1815
|
publishFailures: ctx.publishFailures?.length ? ctx.publishFailures.slice() : null };
|
|
1945
1816
|
let healthFinalValid = false;
|
|
1946
1817
|
try {
|
|
1947
|
-
const
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
if (finalStat.size === 0 && driver.extractFinal) {
|
|
1952
|
-
const final = driver.extractFinal(output.toString('utf8'));
|
|
1953
|
-
if (final === null || final === undefined || final.success === false) throw new Error('Missing or unsuccessful final extraction');
|
|
1954
|
-
const bytes = JSON.stringify({ invocationId, source: 'adapter-extraction', candidate: true, verified: false, result: final });
|
|
1955
|
-
if (Buffer.byteLength(bytes) > 262144) throw new Error('Final exceeds bound');
|
|
1956
|
-
await handles[2].writeFile(bytes); extracted = true;
|
|
1957
|
-
}
|
|
1958
|
-
const finalBytes = await boundedPrivateRead(paths.final, 262144, ctx.inspect, true);
|
|
1959
|
-
if (!finalBytes.length) throw new Error('Missing final output');
|
|
1960
|
-
if (!extracted && driver.extractFinal) {
|
|
1961
|
-
const parsed = driver.extractFinal(finalBytes.toString('utf8'));
|
|
1962
|
-
if (parsed === null || parsed === undefined || parsed.success === false) throw new Error('Invalid final output');
|
|
1963
|
-
}
|
|
1964
|
-
await publish('final-evidence.json', { invocationId, path: paths.final, digest: bytesDigest(finalBytes),
|
|
1965
|
-
source: extracted ? 'adapter-extraction' : 'worker-output', candidate: true, verified: false,
|
|
1966
|
-
terminalSuccess: result.exitCode === 0 && !result.timedOut && !result.errorCode });
|
|
1818
|
+
const { finalBytes, extracted, parseFinal } = await collectWorkerFinal({
|
|
1819
|
+
path: paths.final, workdir: invocation.cwd, driver, output, invocationId, result,
|
|
1820
|
+
inspect: ctx.inspect, handle: handles[2], publish, outputFraming: ctx.profile.health?.outputFraming,
|
|
1821
|
+
});
|
|
1967
1822
|
if (driver.parseWorkerFinal) {
|
|
1968
1823
|
try {
|
|
1969
|
-
await
|
|
1824
|
+
await parseFinal(extracted ? output : finalBytes, {
|
|
1970
1825
|
format: extracted && ctx.profile.health?.outputFraming === 'jsonl' ? 'jsonl' : 'json', observation: { transport: 'shell',
|
|
1971
1826
|
status: result.exitCode === 0 && !result.errorCode && !result.timedOut ? 'completed' : 'failed',
|
|
1972
1827
|
termination: result.termination, exitCode: result.exitCode } });
|
|
@@ -4272,6 +4127,7 @@ export class SessionLauncher extends EventEmitter {
|
|
|
4272
4127
|
prompt: options.prompt,
|
|
4273
4128
|
// AIWG's tracking UUID is not an OMP native session to resume.
|
|
4274
4129
|
sessionId: this.providerAdapter.getName() === 'omp' ? options.resumeSession : options.sessionId,
|
|
4130
|
+
resumeSession: options.resumeSession,
|
|
4275
4131
|
model: options.model,
|
|
4276
4132
|
budget: options.budget,
|
|
4277
4133
|
maxTurns: options.maxTurns,
|