@ours.network/fleet 1.2.0-nightly.54 → 1.2.0-nightly.55
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 +20 -12
- package/dist/agent-ours/service.d.ts +4 -1
- package/dist/agent-ours/service.js +31 -7
- package/dist/application/log-service.js +8 -8
- package/dist/application/role-creation-service.js +2 -1
- package/dist/application/role-removal-service.js +7 -1
- package/dist/application/role-repository.js +17 -0
- package/dist/application/task-layouts.js +12 -2
- package/dist/application/task-room-service.js +19 -17
- package/dist/application/types.d.ts +2 -0
- package/dist/build-info.json +4 -4
- package/dist/cli.js +31 -3
- package/dist/config.d.ts +2 -0
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/codex-session.js +2 -2
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +15 -2
- package/dist/rooms-tasks/cli.js +21 -0
- package/dist/rooms-tasks/close.d.ts +2 -0
- package/dist/rooms-tasks/close.js +66 -1
- package/dist/rooms-tasks/deletion.js +10 -0
- package/dist/rooms-tasks/erasure.js +4 -2
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +1 -0
- package/dist/rooms-tasks/layout-service.js +31 -13
- package/dist/rooms-tasks/layout.d.ts +7 -0
- package/dist/rooms-tasks/layout.js +35 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/provision.js +26 -1
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +1 -0
- package/dist/rooms-tasks/task-state.js +16 -1
- package/dist/rooms-tasks/types.d.ts +3 -0
- package/dist/rooms-tasks/workspace.d.ts +2 -0
- package/dist/rooms-tasks/workspace.js +5 -0
- package/dist/runner.d.ts +6 -0
- package/dist/runner.js +160 -22
- package/dist/session/acp.d.ts +1 -0
- package/dist/session/acp.js +7 -0
- package/dist/session/arbiter.d.ts +5 -0
- package/dist/session/arbiter.js +16 -2
- package/dist/session/codex-app-server.d.ts +1 -0
- package/dist/session/codex-app-server.js +5 -0
- package/dist/session/types.d.ts +2 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +32 -3
- package/dist/startup-readiness.d.ts +1 -1
- package/dist/startup-readiness.js +3 -3
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +24 -12
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +258 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +14 -14
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-lifecycle.d.ts +17 -7
- package/dist/temp-lifecycle.js +85 -53
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/docs/fleet-migration.md +54 -0
- package/docs/task-workspaces.md +26 -0
- package/package.json +4 -1
package/dist/runner.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { readProvenance } from './creation.js';
|
|
2
|
+
import { firstManagedSessionMayStart, managedRecoveryPath, readManagedRecovery, writeManagedRecovery } from './managed-recovery.js';
|
|
1
3
|
import { StartupTelemetry } from './startup-telemetry.js';
|
|
2
4
|
import { managedStartupPrompt, managedTaskPrompt } from './startup-prompt.js';
|
|
3
5
|
import { createLayoutControl } from './rooms-tasks/layout-control.js';
|
|
4
6
|
import { TemporaryChatIdle } from './temp-idle.js';
|
|
7
|
+
import { taskSupervisorMayRun, taskMemberRetirementProof } from './task-supervision.js';
|
|
5
8
|
import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
|
|
6
9
|
import { prepareManagedHarness } from './agent-ours/harness.js';
|
|
7
10
|
import { prepareManagedCliLaunch } from './managed-cli.js';
|
|
@@ -33,7 +36,7 @@ import { ScheduledLoopManager, } from './loops/manager.js';
|
|
|
33
36
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-proxy.js';
|
|
34
37
|
import { effectivePermissionMode } from './permissions.js';
|
|
35
38
|
import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
|
|
36
|
-
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
|
|
39
|
+
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, readTempSupervisor, } from './temp-lifecycle.js';
|
|
37
40
|
import { FleetCommandAuditStore } from './fleet-command-audit.js';
|
|
38
41
|
export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
|
|
39
42
|
export class SupervisorRecycleRequiredError extends Error {
|
|
@@ -511,14 +514,43 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
511
514
|
deps.log(`[${name}] worklog rotation deferred: concurrent modification detected`);
|
|
512
515
|
else if (rotation.rotated)
|
|
513
516
|
deps.log(`[${name}] worklog rotated: ${rotation.beforeBytes} -> ${rotation.afterBytes} bytes`);
|
|
517
|
+
const durableTask = Boolean(readTempSupervisor(dir)?.taskOwner);
|
|
518
|
+
const retainedManaged = durableTask || Boolean(deps.suspendOnStop && adapter.supportsResume);
|
|
514
519
|
const sidFile = join(dir, '.session-id');
|
|
520
|
+
const backendSidFile = join(dir, role.session === 'codex-app-server' ? '.session-id' : '.acp-session-id');
|
|
521
|
+
let recovery = retainedManaged ? readManagedRecovery(dir) : undefined;
|
|
522
|
+
// Native Codex shares .session-id with the runner's pre-launch UUID, so file
|
|
523
|
+
// existence alone is not evidence that its first backend thread was created.
|
|
524
|
+
const firstSessionRetry = retainedManaged && existsSync(join(dir, '.booted'))
|
|
525
|
+
&& (!existsSync(backendSidFile) || (role.session === 'codex-app-server' && recovery?.session === 'pending'))
|
|
526
|
+
&& firstManagedSessionMayStart(dir);
|
|
527
|
+
if (retainedManaged && existsSync(join(dir, '.booted'))
|
|
528
|
+
&& (!existsSync(sidFile) || !existsSync(backendSidFile)) && !firstSessionRetry)
|
|
529
|
+
throw Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
|
|
515
530
|
if (!existsSync(sidFile))
|
|
516
531
|
writeFileSync(sidFile, randomUUID() + '\n');
|
|
517
532
|
const sessionId = readFileSync(sidFile, 'utf8').trim();
|
|
518
533
|
const bootedFile = join(dir, '.booted');
|
|
519
534
|
const exitFile = join(dir, '.exit-status');
|
|
520
535
|
const booted = existsSync(bootedFile);
|
|
521
|
-
|
|
536
|
+
if (durableTask && !adapter.supportsResume)
|
|
537
|
+
throw Error('TASK_HARNESS_RESUME_UNSUPPORTED');
|
|
538
|
+
const mode = booted && adapter.supportsResume && !firstSessionRetry ? 'resume' : 'fresh';
|
|
539
|
+
const requireResume = retainedManaged && mode === 'resume';
|
|
540
|
+
if (retainedManaged && !recovery) {
|
|
541
|
+
// Older admitted sessions have no cursor. Continue once, without replaying
|
|
542
|
+
// their assignment; fresh sessions record intent before the first attempt.
|
|
543
|
+
recovery = { version: 1, session: booted ? 'established' : 'pending', readiness: booted,
|
|
544
|
+
initial: booted ? 'dispatching' : 'pending', active: booted };
|
|
545
|
+
writeManagedRecovery(dir, recovery);
|
|
546
|
+
}
|
|
547
|
+
const recoveryBefore = recovery ? { ...recovery } : undefined;
|
|
548
|
+
const saveRecovery = (update) => {
|
|
549
|
+
if (!recovery)
|
|
550
|
+
return;
|
|
551
|
+
recovery = { ...recovery, ...update };
|
|
552
|
+
writeManagedRecovery(dir, recovery);
|
|
553
|
+
};
|
|
522
554
|
// Stamp EVERY attempt, not just the first. `.booted` used to be written only
|
|
523
555
|
// on the fresh path, so after a restart — including one the supervisor never
|
|
524
556
|
// saw, like an OOM-kill — its mtime still read the original boot and any
|
|
@@ -672,11 +704,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
672
704
|
managedOurs: managedHarness.ours,
|
|
673
705
|
role, prep,
|
|
674
706
|
launch: { ...launch, argv: wrappedArgv, env: managedHarness.env },
|
|
675
|
-
cwd: runCwd, stateDir: dir, mode, permissions: perms,
|
|
707
|
+
cwd: runCwd, stateDir: dir, mode, requireResume, permissions: perms,
|
|
676
708
|
permissionMode: effectivePermissionMode(role), log: deps.log,
|
|
677
709
|
}));
|
|
678
710
|
managedService.setFileDelivery?.(agentSession.sendFileToChat?.bind(agentSession));
|
|
679
711
|
startupTelemetry.mark('harness_ready');
|
|
712
|
+
saveRecovery({ session: 'established' });
|
|
680
713
|
pid = agentSession.pid;
|
|
681
714
|
arbiter = new RoleTurnArbiter(agentSession);
|
|
682
715
|
sessionHandle = arbiter;
|
|
@@ -697,7 +730,25 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
697
730
|
startMonitorAfterStartup();
|
|
698
731
|
}
|
|
699
732
|
});
|
|
700
|
-
|
|
733
|
+
const activePrompts = new Set();
|
|
734
|
+
const unsubscribeContinuity = agentSession.subscribeConversation?.(event => {
|
|
735
|
+
if (!recovery?.readiness)
|
|
736
|
+
return;
|
|
737
|
+
if (event.kind === 'prompt.started') {
|
|
738
|
+
if (event.promptId)
|
|
739
|
+
activePrompts.add(event.promptId);
|
|
740
|
+
saveRecovery({ active: true });
|
|
741
|
+
}
|
|
742
|
+
else if (event.kind === 'turn.completed' && 'outcome' in event.payload) {
|
|
743
|
+
if (event.promptId)
|
|
744
|
+
activePrompts.delete(event.promptId);
|
|
745
|
+
// A shutdown/failed turn needs continuation; a successful terminal turn
|
|
746
|
+
// proves it may stay idle. Never store prompt bodies in this cursor.
|
|
747
|
+
if (event.payload.outcome === 'completed')
|
|
748
|
+
saveRecovery({ active: activePrompts.size > 0 });
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
unsubscribeRecovery = () => { unsubscribeFailure(); unsubscribeStartup?.(); unsubscribeTelemetry?.(); unsubscribeContinuity?.(); };
|
|
701
752
|
if (role.owner_channel) {
|
|
702
753
|
try {
|
|
703
754
|
ownerBinder = await deps.acquireOwnerBinder(dir, name, role.owner_channel.identity);
|
|
@@ -818,8 +869,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
818
869
|
// Wait for the first turn's TERMINAL result. An agent that accepts the
|
|
819
870
|
// startup prompt and then refuses it has not started; logging the role as
|
|
820
871
|
// up would hide a role that never completed its readiness turn.
|
|
821
|
-
|
|
822
|
-
|
|
872
|
+
const retainedResume = retainedManaged && mode === 'resume' && recoveryBefore?.readiness === true;
|
|
873
|
+
if (!retainedResume)
|
|
874
|
+
startupTelemetry.mark('startup_submitted');
|
|
875
|
+
// The backend already verified the saved conversation. Resuming a managed
|
|
876
|
+
// member admits new wakes/console input without replaying startup or work.
|
|
877
|
+
const starting = retainedResume
|
|
878
|
+
? Promise.resolve({ accepted: false, succeeded: true, outcome: 'completed', detail: 'retained backend resumed; no readiness prompt submitted' })
|
|
879
|
+
: arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
|
|
823
880
|
// Keep the early cursor baseline; do not deliver wakes into the protected
|
|
824
881
|
// first turn. Steering can pre-empt startup just like cancellation.
|
|
825
882
|
// Owner traffic and agent replies must be observed during a long first
|
|
@@ -884,20 +941,36 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
884
941
|
+ 'keeping temporary supervisor alive');
|
|
885
942
|
startupRecoveryAllowed = interruptedForWake;
|
|
886
943
|
sessionStartupComplete = started.succeeded || (interruptedForWake && successfulTurnObserved);
|
|
887
|
-
if (started.succeeded)
|
|
944
|
+
if (started.succeeded && !retainedResume)
|
|
888
945
|
startupTelemetry.mark('readiness_turn_completed');
|
|
946
|
+
if (started.succeeded)
|
|
947
|
+
saveRecovery({ readiness: true });
|
|
889
948
|
// Readiness is now complete. Configured work (or retained continuity) is
|
|
890
949
|
// admitted as a distinct turn; its terminal result must not block console
|
|
891
950
|
// readiness, mail delivery, or supervisor liveness. Empty fresh agents wait
|
|
892
951
|
// for authenticated console input or the monitor's unread/arrival wake.
|
|
893
|
-
|
|
894
|
-
|
|
952
|
+
const configuredWork = Boolean(role.mission?.trim() || role.roomMemberStartup?.task?.trim() || role.briefing_file?.trim());
|
|
953
|
+
const initialPending = retainedManaged && recoveryBefore?.initial === 'pending';
|
|
954
|
+
const interruptedWork = retainedManaged && (recoveryBefore?.active || recoveryBefore?.initial === 'dispatching');
|
|
955
|
+
const workPrompt = retainedManaged
|
|
956
|
+
? initialPending && configuredWork ? managedTaskPrompt(dir, 'fresh', false)
|
|
957
|
+
: interruptedWork ? 'Fleet supervisor restarted. Continue pending work in the restored conversation; do not redo completed steps. '
|
|
958
|
+
+ (recoveryBefore?.initial === 'dispatching' ? `If you have not yet received your assignment, read ${join(dir, 'briefing.md')} once before acting. ` : '')
|
|
959
|
+
+ 'Read the continuation log only as needed to recover your next action. If no work remains, end this turn and wait for a concrete task or supervisor wake.' : undefined
|
|
960
|
+
: configuredWork || booted ? managedTaskPrompt(dir, mode, booted) : undefined;
|
|
961
|
+
if (started.succeeded && !deps.shouldStop?.() && workPrompt) {
|
|
962
|
+
saveRecovery({ initial: 'dispatching', active: true });
|
|
963
|
+
const queued = await arbiter.queuePrompt(workPrompt, {
|
|
895
964
|
origin: { kind: 'startup' },
|
|
896
965
|
});
|
|
897
966
|
void queued.completion.then(result => {
|
|
967
|
+
if (result.succeeded)
|
|
968
|
+
saveRecovery({ initial: 'completed', active: activePrompts.size > 0 });
|
|
898
969
|
deps.log(`[${name}] configured task turn ${result.outcome}`);
|
|
899
970
|
}, () => deps.log(`[${name}] configured task turn failed`));
|
|
900
971
|
}
|
|
972
|
+
else if (started.succeeded && initialPending && !configuredWork)
|
|
973
|
+
saveRecovery({ initial: 'completed' });
|
|
901
974
|
reloadLoopConfig = async () => {
|
|
902
975
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
903
976
|
const definitions = nextRole.loops ?? [];
|
|
@@ -986,7 +1059,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
986
1059
|
});
|
|
987
1060
|
let chatIdle;
|
|
988
1061
|
try {
|
|
989
|
-
if (temp && !role.roomMemberStartup)
|
|
1062
|
+
if (temp && !role.roomMemberStartup && !readTempSupervisor(dir)?.taskOwner)
|
|
990
1063
|
chatIdle = new TemporaryChatIdle(dir, deps.now());
|
|
991
1064
|
}
|
|
992
1065
|
catch {
|
|
@@ -1122,11 +1195,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1122
1195
|
const rotate = (why) => {
|
|
1123
1196
|
writeFileSync(sidFile, randomUUID() + '\n');
|
|
1124
1197
|
rmSync(bootedFile, { force: true });
|
|
1198
|
+
rmSync(managedRecoveryPath(dir), { force: true });
|
|
1125
1199
|
rotated = true;
|
|
1126
1200
|
deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
|
|
1127
1201
|
};
|
|
1128
|
-
if (!temp && deps.shouldStop?.())
|
|
1129
|
-
deps.log(`[${name}] supervisor
|
|
1202
|
+
if (readTempSupervisor(dir)?.taskOwner || ((!temp || deps.suspendOnStop) && deps.shouldStop?.()))
|
|
1203
|
+
deps.log(`[${name}] supervisor/context retained -> next start RESUMES context`);
|
|
1130
1204
|
else if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
|
|
1131
1205
|
|| exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
|
|
1132
1206
|
// This is a deliberate adapter reclamation, not evidence that resume state
|
|
@@ -1176,11 +1250,13 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1176
1250
|
*/
|
|
1177
1251
|
export async function runSupervised(name, opts = {}, partialDeps = {}, attempt = runOnce) {
|
|
1178
1252
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
1179
|
-
const
|
|
1253
|
+
const taskMember = opts.temp === true;
|
|
1254
|
+
const dir = agentDir(name, taskMember);
|
|
1180
1255
|
mkdirSync(dir, { recursive: true });
|
|
1181
1256
|
let stopping = false;
|
|
1182
1257
|
const requestStop = () => { stopping = true; };
|
|
1183
|
-
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
|
|
1258
|
+
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
|
|
1259
|
+
|| (taskMember && requestedTempStopReason(dir) === 'operator-stop');
|
|
1184
1260
|
deps.shouldStop = shouldStop;
|
|
1185
1261
|
const stamp = () => new Date(deps.now()).toISOString();
|
|
1186
1262
|
// Record how the PREVIOUS supervisor process ended before doing anything
|
|
@@ -1208,10 +1284,12 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1208
1284
|
process.on('SIGINT', requestStop);
|
|
1209
1285
|
try {
|
|
1210
1286
|
while (!shouldStop()) {
|
|
1287
|
+
if (taskMember && !taskSupervisorMayRun(name))
|
|
1288
|
+
break;
|
|
1211
1289
|
let ledger = readRestartLedger(dir);
|
|
1212
1290
|
try {
|
|
1213
1291
|
const configPath = resolveConfigPath(dir, opts.configPath);
|
|
1214
|
-
const role = findRole(loadConfig(configPath), name);
|
|
1292
|
+
const role = taskMember ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
1215
1293
|
reconcileModelRecovery(dir, role, stamp());
|
|
1216
1294
|
if (modelRecoveryHeld(dir)) {
|
|
1217
1295
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
@@ -1227,18 +1305,26 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1227
1305
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
1228
1306
|
continue;
|
|
1229
1307
|
}
|
|
1308
|
+
if (attempt === runOnce && taskMember) {
|
|
1309
|
+
// Boot-time daemon unavailability must not consume the harness crash budget.
|
|
1310
|
+
const { waitForRoleDaemon } = await import('./startup-readiness.js');
|
|
1311
|
+
await waitForRoleDaemon(name, undefined, {}, true);
|
|
1312
|
+
}
|
|
1313
|
+
if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
|
|
1314
|
+
break;
|
|
1230
1315
|
let result;
|
|
1231
1316
|
try {
|
|
1232
1317
|
// Service-manager boot and automatic retry bypass the operator-facing
|
|
1233
1318
|
// up/restart commands. Reconcile again immediately before every real
|
|
1234
1319
|
// permanent harness attempt; the operation is idempotent and releases its
|
|
1235
1320
|
// provisioning lease before the agent or owner channel binds.
|
|
1236
|
-
if (attempt === runOnce) {
|
|
1321
|
+
if (attempt === runOnce && !taskMember) {
|
|
1237
1322
|
const configPath = resolveConfigPath(dir, opts.configPath);
|
|
1238
1323
|
const role = findRole(loadConfig(configPath), name);
|
|
1239
1324
|
await reconcilePermanentRoleIdentities(role, undefined, deps.log);
|
|
1240
1325
|
}
|
|
1241
|
-
result = await attempt(name, { configPath: opts.configPath,
|
|
1326
|
+
result = await attempt(name, { configPath: opts.configPath, ...(taskMember ? { temp: true } : {}),
|
|
1327
|
+
allowResumeRotation: taskMember ? false : !ledger.resumeDiscarded }, deps);
|
|
1242
1328
|
}
|
|
1243
1329
|
catch (e) {
|
|
1244
1330
|
if (e instanceof SupervisorRecycleRequiredError) {
|
|
@@ -1260,7 +1346,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1260
1346
|
mode: 'fresh',
|
|
1261
1347
|
};
|
|
1262
1348
|
}
|
|
1263
|
-
if (stopping)
|
|
1349
|
+
if (stopping || (taskMember && (shouldStop() || result.retirementReason)))
|
|
1264
1350
|
break;
|
|
1265
1351
|
// Re-read: the attempt itself may have taken minutes, and an operator may
|
|
1266
1352
|
// have reset the ledger meanwhile.
|
|
@@ -1278,7 +1364,8 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1278
1364
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
1279
1365
|
continue;
|
|
1280
1366
|
}
|
|
1281
|
-
const fastFailSecs =
|
|
1367
|
+
const fastFailSecs = taskMember ? getAdapter(loadTempRole(name).harness).exitPolicy.fastFailSecs
|
|
1368
|
+
: fastFailSecsFor(name, opts.configPath);
|
|
1282
1369
|
// The fast-fail boundary starts a recovery episode; it must not also be
|
|
1283
1370
|
// the boundary that declares recovery successful. Otherwise alternating
|
|
1284
1371
|
// 19s and 20s deaths erase one another forever. Require the configured
|
|
@@ -1315,7 +1402,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1315
1402
|
next.nextDelayMs = 0;
|
|
1316
1403
|
writeRestartLedger(dir, next);
|
|
1317
1404
|
deps.log(`[${name}] HELD DOWN after ${failures} immediate failures at ${next.openedAt} — ` +
|
|
1318
|
-
`${reason};
|
|
1405
|
+
`${reason}; recover with: ${taskMember ? `ours-fleet task recover-members ${readTempSupervisor(dir).taskOwner.taskId}` : `ours-fleet restart ${name}`}`);
|
|
1319
1406
|
continue;
|
|
1320
1407
|
}
|
|
1321
1408
|
writeRestartLedger(dir, next);
|
|
@@ -1330,7 +1417,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1330
1417
|
process.off('SIGINT', requestStop);
|
|
1331
1418
|
// An orderly shutdown clears the marker; an unhandled signal or OOM-kill
|
|
1332
1419
|
// leaves it so the successor can identify an abrupt termination.
|
|
1333
|
-
if (shouldStop())
|
|
1420
|
+
if (!taskMember && shouldStop())
|
|
1334
1421
|
await deps.releaseAgentOurs(findRole(loadConfig(opts.configPath), name));
|
|
1335
1422
|
releaseSupervisorRun(dir);
|
|
1336
1423
|
}
|
|
@@ -1350,8 +1437,58 @@ function fastFailSecsFor(name, configPath) {
|
|
|
1350
1437
|
}
|
|
1351
1438
|
}
|
|
1352
1439
|
/** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
|
|
1440
|
+
/** The parent uses the same adapter-relative sustained-uptime reset boundary. */
|
|
1441
|
+
export function stableSupervisorWindow(name, temp, configPath) {
|
|
1442
|
+
try {
|
|
1443
|
+
const role = temp ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
1444
|
+
return getAdapter(role.harness).exitPolicy.fastFailSecs * RESTART_FAIL_THRESHOLD;
|
|
1445
|
+
}
|
|
1446
|
+
catch {
|
|
1447
|
+
return 20 * RESTART_FAIL_THRESHOLD;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1353
1450
|
export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
1354
1451
|
const dir = agentDir(name, true);
|
|
1452
|
+
const metadata = readTempSupervisor(dir);
|
|
1453
|
+
// Non-resumable standalone temporaries retain their prior finite lifecycle:
|
|
1454
|
+
// a parent stop retires them rather than promising retained conversation.
|
|
1455
|
+
if (!metadata?.taskOwner && deps.suspendOnStop && !getAdapter(loadTempRole(name).harness).supportsResume)
|
|
1456
|
+
deps = { ...deps, suspendOnStop: false };
|
|
1457
|
+
if (!metadata?.taskOwner) {
|
|
1458
|
+
const role = loadTempRole(name);
|
|
1459
|
+
const provenance = readProvenance(dir);
|
|
1460
|
+
if (role.roomMemberStartup?.task_id
|
|
1461
|
+
|| (name.startsWith('layout-') && provenance?.creationActionId?.startsWith('task-')))
|
|
1462
|
+
throw Error('TASK_SUPERVISOR_OWNER_MISSING');
|
|
1463
|
+
}
|
|
1464
|
+
if (metadata?.taskOwner) {
|
|
1465
|
+
// Serialize the entire process lifetime before metadata/session writes.
|
|
1466
|
+
const lease = await acquireOwnerBinderLease(join(dir, '.task-supervisor'), 'task-supervisor', name);
|
|
1467
|
+
let ownershipVerified = false;
|
|
1468
|
+
try {
|
|
1469
|
+
const mayRun = taskSupervisorMayRun(name);
|
|
1470
|
+
ownershipVerified = true;
|
|
1471
|
+
if (!mayRun)
|
|
1472
|
+
return;
|
|
1473
|
+
await markTempSupervisorActive(dir);
|
|
1474
|
+
await runSupervised(name, { temp: true }, deps, attempt);
|
|
1475
|
+
}
|
|
1476
|
+
finally {
|
|
1477
|
+
try {
|
|
1478
|
+
if (ownershipVerified && requestedTempStopReason(dir)) {
|
|
1479
|
+
// Parent shutdown suspends; an explicit stop retires the exact saved
|
|
1480
|
+
// owner even when no harness attempt ran (for example held circuits).
|
|
1481
|
+
await (deps.releaseAgentOurs ?? releaseManagedAgent)(loadTempRole(name), taskMemberRetirementProof(name));
|
|
1482
|
+
const archived = archiveTempState(name, 'operator-stop', 'retired', 'explicit task member retirement');
|
|
1483
|
+
deps.log?.(`[${name}] task member retired: operator-stop${archived ? `; evidence archived at ${archived}` : ''}`);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
finally {
|
|
1487
|
+
lease.release();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1355
1492
|
await markTempSupervisorActive(dir);
|
|
1356
1493
|
let signal;
|
|
1357
1494
|
const onTerm = () => { signal = 'SIGTERM'; };
|
|
@@ -1378,7 +1515,8 @@ export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
|
1378
1515
|
process.off('SIGTERM', onTerm);
|
|
1379
1516
|
process.off('SIGINT', onInt);
|
|
1380
1517
|
// A recycle replaces the supervisor process, not the logical temporary agent.
|
|
1381
|
-
if (!(
|
|
1518
|
+
if (!((signal || deps.shouldStop?.()) && deps.suspendOnStop && !requestedTempStopReason(dir))
|
|
1519
|
+
&& (!(failure instanceof SupervisorRecycleRequiredError) || signal || deps.shouldStop?.())) {
|
|
1382
1520
|
const requested = requestedTempStopReason(dir);
|
|
1383
1521
|
const reason = requested
|
|
1384
1522
|
?? result?.retirementReason
|
package/dist/session/acp.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface AcpSessionOptions {
|
|
|
51
51
|
inheritEnvironment?: boolean;
|
|
52
52
|
stateDir: string;
|
|
53
53
|
mode: 'fresh' | 'resume';
|
|
54
|
+
requireResume?: boolean;
|
|
54
55
|
permissions: CommonPermissions;
|
|
55
56
|
/** Native permission-mode id to request via session/set_mode; undefined keeps the agent default. */
|
|
56
57
|
modeId?: string;
|
package/dist/session/acp.js
CHANGED
|
@@ -467,6 +467,7 @@ export class AcpSession {
|
|
|
467
467
|
readiness: this.readiness === 'idle' && this.steeringOccupied
|
|
468
468
|
? 'running' : this.readiness,
|
|
469
469
|
sessionId: this.sessionId,
|
|
470
|
+
...(this.activeTurn?.origin ? { activePromptSource: this.activeTurn.origin.kind } : {}),
|
|
470
471
|
lastError: this.lastError,
|
|
471
472
|
pendingPermissionId: this.pendingPermissions.keys().next().value,
|
|
472
473
|
runtimeModel: this.runtimeModel,
|
|
@@ -581,6 +582,10 @@ export class AcpSession {
|
|
|
581
582
|
* steering response from becoming a tight replay loop.
|
|
582
583
|
*/
|
|
583
584
|
async steerOrQueueWake(text, options) {
|
|
585
|
+
// A tool-boundary wait may outlive its original turn. Recheck the current
|
|
586
|
+
// typed source at delivery, since an Owner turn can begin during the wait.
|
|
587
|
+
if (options.origin?.kind === 'fleet-monitor' && this.activeTurn?.origin?.kind === 'owner-admin-console')
|
|
588
|
+
return this.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
584
589
|
if (this.stallRecoveryClaimed)
|
|
585
590
|
return this.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
586
591
|
const steered = await this.steerPrompt(text);
|
|
@@ -1130,6 +1135,8 @@ export class AcpSession {
|
|
|
1130
1135
|
const persisted = this.options.mode === 'resume' && existsSync(this.sessionFile)
|
|
1131
1136
|
? readFileSync(this.sessionFile, 'utf8').trim()
|
|
1132
1137
|
: '';
|
|
1138
|
+
if (this.options.requireResume && (!persisted || !(this.agentCapabilities?.sessionCapabilities?.resume != null || this.agentCapabilities?.loadSession)))
|
|
1139
|
+
throw new Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
|
|
1133
1140
|
let advertisedConfigOptions;
|
|
1134
1141
|
let advertisedModes;
|
|
1135
1142
|
let advertisedModelId;
|
|
@@ -54,6 +54,11 @@ export declare class RoleTurnArbiter implements AgentSession {
|
|
|
54
54
|
*/
|
|
55
55
|
retireStalledAdmission(): void;
|
|
56
56
|
queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
|
|
57
|
+
/** Inbox traffic must not cancel or steer an active human console/voice turn.
|
|
58
|
+
* Queue it once; the monitor commits its cursor after that turn succeeds.
|
|
59
|
+
* Other origins retain their configured monitor policy.
|
|
60
|
+
*/
|
|
61
|
+
private monitorAdmissionOptions;
|
|
57
62
|
submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
58
63
|
/**
|
|
59
64
|
* Do not hold `exclusive` while ACP waits for a tool boundary: permission
|
package/dist/session/arbiter.js
CHANGED
|
@@ -90,8 +90,20 @@ export class RoleTurnArbiter {
|
|
|
90
90
|
retired.retire();
|
|
91
91
|
}
|
|
92
92
|
queuePrompt(text, options = {}) {
|
|
93
|
-
return this.exclusive(async () => {
|
|
94
|
-
|
|
93
|
+
return this.exclusive(async () => {
|
|
94
|
+
if (this.retiring)
|
|
95
|
+
throw new Error('Chat is closing');
|
|
96
|
+
return this.track(await this.session.queuePrompt(text, this.monitorAdmissionOptions(options)));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Inbox traffic must not cancel or steer an active human console/voice turn.
|
|
100
|
+
* Queue it once; the monitor commits its cursor after that turn succeeds.
|
|
101
|
+
* Other origins retain their configured monitor policy.
|
|
102
|
+
*/
|
|
103
|
+
monitorAdmissionOptions(options) {
|
|
104
|
+
return options.origin?.kind === 'fleet-monitor'
|
|
105
|
+
&& this.session.snapshot().activePromptSource === 'owner-admin-console'
|
|
106
|
+
? { ...options, interrupt: false, steer: false } : options;
|
|
95
107
|
}
|
|
96
108
|
async submitPrompt(text, options = {}) {
|
|
97
109
|
return (await this.queuePrompt(text, options)).completion;
|
|
@@ -103,6 +115,8 @@ export class RoleTurnArbiter {
|
|
|
103
115
|
submitPromptAfterTool(text, options = {}) {
|
|
104
116
|
if (this.retiring)
|
|
105
117
|
return Promise.reject(new Error('Chat is closing'));
|
|
118
|
+
if (this.monitorAdmissionOptions(options) !== options)
|
|
119
|
+
return this.submitPrompt(text, options);
|
|
106
120
|
return this.session.submitPromptAfterTool?.(text, options)
|
|
107
121
|
?? this.session.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
108
122
|
}
|
|
@@ -11,6 +11,7 @@ export interface CodexAppServerSessionOptions {
|
|
|
11
11
|
env: Record<string, string>;
|
|
12
12
|
stateDir: string;
|
|
13
13
|
mode: 'fresh' | 'resume';
|
|
14
|
+
requireResume?: boolean;
|
|
14
15
|
permissions: CommonPermissions;
|
|
15
16
|
permissionMode: NonNullable<SessionSnapshot['permissionMode']>;
|
|
16
17
|
model?: string | null;
|
|
@@ -173,6 +173,7 @@ export class CodexAppServerSession {
|
|
|
173
173
|
alive: this.isAlive(),
|
|
174
174
|
readiness: this.readiness,
|
|
175
175
|
sessionId: this.threadId,
|
|
176
|
+
...(this.activeTurn?.origin ? { activePromptSource: this.activeTurn.origin.kind } : {}),
|
|
176
177
|
lastError: this.lastError,
|
|
177
178
|
pendingPermissionId: this.pendingPermissions.keys().next().value,
|
|
178
179
|
runtimeModel: this.runtimeModel,
|
|
@@ -406,6 +407,8 @@ export class CodexAppServerSession {
|
|
|
406
407
|
this.transport.notify('initialized');
|
|
407
408
|
const persisted = this.options.mode === 'resume' && existsSync(this.threadFile)
|
|
408
409
|
? readFileSync(this.threadFile, 'utf8').trim() : '';
|
|
410
|
+
if (this.options.requireResume && !persisted)
|
|
411
|
+
throw new Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
|
|
409
412
|
const common = {
|
|
410
413
|
cwd: this.options.cwd,
|
|
411
414
|
approvalPolicy: this.options.approvalPolicy,
|
|
@@ -425,6 +428,8 @@ export class CodexAppServerSession {
|
|
|
425
428
|
this.threadId = string(thread?.id);
|
|
426
429
|
if (!this.threadId)
|
|
427
430
|
throw new Error('Codex app-server did not return a thread id');
|
|
431
|
+
if (this.options.requireResume && this.threadId !== persisted)
|
|
432
|
+
throw new Error('TASK_CONTEXT_RESUME_MISMATCH');
|
|
428
433
|
writeFileSync(this.threadFile, this.threadId + '\n', { mode: 0o600 });
|
|
429
434
|
const model = string(response.model) ?? this.options.model ?? undefined;
|
|
430
435
|
if (model)
|
package/dist/session/types.d.ts
CHANGED
|
@@ -186,6 +186,8 @@ export interface SessionSnapshot {
|
|
|
186
186
|
backend: SessionBackendId;
|
|
187
187
|
alive: boolean;
|
|
188
188
|
readiness: SessionReadiness;
|
|
189
|
+
/** Typed source of the active prompt; excludes prompt bodies and command IDs. */
|
|
190
|
+
activePromptSource?: PromptOrigin['kind'];
|
|
189
191
|
sessionId?: string;
|
|
190
192
|
lastError?: string;
|
|
191
193
|
pendingPermissionId?: string;
|
package/dist/spawn.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type CreationDeps, type CreationProvenance } from './creation.js';
|
|
|
6
6
|
import './harness/claude-code.js';
|
|
7
7
|
import './harness/codex.js';
|
|
8
8
|
import './harness/hermes.js';
|
|
9
|
-
import { type SupervisorLauncher } from './temp-lifecycle.js';
|
|
9
|
+
import { type SupervisorLauncher, type TaskSupervisorOwner } from './temp-lifecycle.js';
|
|
10
10
|
/**
|
|
11
11
|
* The provenance record written by the most recent spawn in this process, so
|
|
12
12
|
* the CLI can print the same summary it persisted rather than rebuilding it.
|
|
@@ -15,6 +15,8 @@ export declare let lastProvenance: CreationProvenance | undefined;
|
|
|
15
15
|
export interface SpawnOpts {
|
|
16
16
|
name: string;
|
|
17
17
|
temp?: boolean;
|
|
18
|
+
/** Internal exact task-layout ownership, never a standalone manifest entry. */
|
|
19
|
+
taskOwner?: TaskSupervisorOwner;
|
|
18
20
|
brain?: AgentSelection;
|
|
19
21
|
role?: AgentSelection;
|
|
20
22
|
/** Trusted canonical definition used by room provisioning; never a second schema. */
|
|
@@ -85,3 +87,5 @@ export declare function spawnPermanent(o: SpawnOpts, deps: OpsDeps, creation?: C
|
|
|
85
87
|
export type { SupervisorLauncher } from './temp-lifecycle.js';
|
|
86
88
|
/** Temp spawn: live state under ~/.ours-fleet/tmp, independent transient supervision. */
|
|
87
89
|
export declare function spawnTemp(o: SpawnOpts, binPath: string, launch?: SupervisorLauncher, creation?: CreationDeps): Promise<string>;
|
|
90
|
+
/** Resume the exact durable task launch; no creation, invite issuance or state rotation. */
|
|
91
|
+
export declare function resumeTaskSupervisor(name: string, binPath: string): Promise<void>;
|
package/dist/spawn.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { taskSupervisorMayRun } from './task-supervision.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { storeRoomSecret, storeTemporaryLaunch, preparePermanentAssignment } from './agent-ours/service.js';
|
|
3
4
|
import { ensureWorkspace } from './rooms-tasks/workspace.js';
|
|
@@ -16,7 +17,8 @@ import { recordGeneratedAgentSource } from './generated-agent-source.js';
|
|
|
16
17
|
import './harness/claude-code.js';
|
|
17
18
|
import './harness/codex.js';
|
|
18
19
|
import './harness/hermes.js';
|
|
19
|
-
import {
|
|
20
|
+
import { getAdapter } from './harness/registry.js';
|
|
21
|
+
import { archiveTempState, makeTempSupervisorLauncher, prepareTempSupervisor, reclaimStaleTempState, stopTempSupervisor, secureStoppedTempArchive, readTempSupervisor, requestedTempStopReason, } from './temp-lifecycle.js';
|
|
20
22
|
/**
|
|
21
23
|
* The provenance record written by the most recent spawn in this process, so
|
|
22
24
|
* the CLI can print the same summary it persisted rather than rebuilding it.
|
|
@@ -322,6 +324,8 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
322
324
|
if (o.isolationFile)
|
|
323
325
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
324
326
|
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
327
|
+
if ((o.taskOwner || o.roomMemberStartup?.task_id) && !getAdapter(prepared.role.harness).supportsResume)
|
|
328
|
+
throw Error('TASK_HARNESS_RESUME_UNSUPPORTED: task agents require a resumable harness');
|
|
325
329
|
if (o.roomMemberStartup?.workspace) {
|
|
326
330
|
const workspace = o.roomMemberStartup.workspace;
|
|
327
331
|
if (prepared.role.cwd !== workspace.path)
|
|
@@ -368,7 +372,14 @@ async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
|
368
372
|
lastProvenance = provenance;
|
|
369
373
|
tx.record({
|
|
370
374
|
stage: `temp state dir ${dir}`,
|
|
371
|
-
undo: () => {
|
|
375
|
+
undo: async () => {
|
|
376
|
+
if (o.taskOwner || role.roomMemberStartup?.task_id) {
|
|
377
|
+
await stopTempSupervisor(o.name);
|
|
378
|
+
const supervisor = readTempSupervisor(dir);
|
|
379
|
+
if (supervisor)
|
|
380
|
+
await secureStoppedTempArchive(o.name, supervisor.launchId);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
372
383
|
// A failed launch is still lifecycle evidence: briefing, provenance,
|
|
373
384
|
// metadata and supervisor output explain what happened. Remove it from
|
|
374
385
|
// the live roster by atomic archive, never recursive deletion.
|
|
@@ -381,7 +392,14 @@ async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
|
381
392
|
// out; a lone temp spawn still waits zero (time-based gate).
|
|
382
393
|
if (cfg.startStaggerMs > 0)
|
|
383
394
|
writeFileSync(join(dir, START_STAGGER_FILE), String(cfg.startStaggerMs));
|
|
384
|
-
prepareTempSupervisor(dir, o.name
|
|
395
|
+
prepareTempSupervisor(dir, o.name, o.taskOwner ?? (o.roomMemberStartup?.task_id ? {
|
|
396
|
+
taskId: o.roomMemberStartup.task_id,
|
|
397
|
+
roomId: o.roomMemberStartup.room_id,
|
|
398
|
+
roomIdentityCid: o.roomMemberStartup.room_identity_cid,
|
|
399
|
+
creationActionId: o.creationActionId,
|
|
400
|
+
} : undefined));
|
|
401
|
+
if (o.taskOwner || o.roomMemberStartup?.task_id)
|
|
402
|
+
taskSupervisorMayRun(o.name);
|
|
385
403
|
// Run the supervisor independently. On a service-managed host the temp
|
|
386
404
|
// runner gets its own transient unit/job, so stopping the coordinator's unit
|
|
387
405
|
// cannot kill a live worker in the coordinator's cgroup.
|
|
@@ -389,3 +407,14 @@ async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
|
389
407
|
await launch(binPath, ['_run-temp', o.name], dir);
|
|
390
408
|
return dir;
|
|
391
409
|
}
|
|
410
|
+
/** Resume the exact durable task launch; no creation, invite issuance or state rotation. */
|
|
411
|
+
export async function resumeTaskSupervisor(name, binPath) {
|
|
412
|
+
if (!taskSupervisorMayRun(name))
|
|
413
|
+
throw Error('TASK_SUPERVISOR_RETIRED');
|
|
414
|
+
const dir = agentDir(name, true);
|
|
415
|
+
if (requestedTempStopReason(dir))
|
|
416
|
+
throw Error('TASK_SUPERVISOR_STOP_REQUESTED');
|
|
417
|
+
const launcher = readTempSupervisor(dir)?.kind === 'detached'
|
|
418
|
+
? makeTempSupervisorLauncher({ spawnDetached, supervisor: 'none' }) : independentSupervisor;
|
|
419
|
+
await launcher(binPath, ['_run-temp', name], dir);
|
|
420
|
+
}
|
|
@@ -9,5 +9,5 @@ interface ReadinessDeps {
|
|
|
9
9
|
env: NodeJS.ProcessEnv;
|
|
10
10
|
}
|
|
11
11
|
/** Read-only preflight: never binds identities, starts a harness or edits its ledger. */
|
|
12
|
-
export declare function waitForRoleDaemon(name: string, configPath?: string, partial?: Partial<ReadinessDeps
|
|
12
|
+
export declare function waitForRoleDaemon(name: string, configPath?: string, partial?: Partial<ReadinessDeps>, temp?: boolean): Promise<void>;
|
|
13
13
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { agentDir } from './paths.js';
|
|
2
2
|
import { findRole, loadConfig } from './config.js';
|
|
3
|
-
import { resolveConfigPath } from './runner.js';
|
|
3
|
+
import { resolveConfigPath, loadTempRole } from './runner.js';
|
|
4
4
|
import { probeDaemonGeneration } from './daemon-recovery.js';
|
|
5
5
|
export const STARTUP_WAIT_MS = 240_000;
|
|
6
6
|
export const STARTUP_RETRY_MS = 5_000;
|
|
7
7
|
/** Read-only preflight: never binds identities, starts a harness or edits its ledger. */
|
|
8
|
-
export async function waitForRoleDaemon(name, configPath, partial = {}) {
|
|
8
|
+
export async function waitForRoleDaemon(name, configPath, partial = {}, temp = false) {
|
|
9
9
|
const deps = {
|
|
10
10
|
probe: env => probeDaemonGeneration(((url, init) => globalThis.fetch(url, {
|
|
11
11
|
...init, signal: AbortSignal.timeout(10_000),
|
|
@@ -17,7 +17,7 @@ export async function waitForRoleDaemon(name, configPath, partial = {}) {
|
|
|
17
17
|
...partial,
|
|
18
18
|
};
|
|
19
19
|
const path = resolveConfigPath(agentDir(name), configPath);
|
|
20
|
-
const role = findRole(loadConfig(path), name);
|
|
20
|
+
const role = temp ? loadTempRole(name) : findRole(loadConfig(path), name);
|
|
21
21
|
const env = { ...deps.env, ...role.env };
|
|
22
22
|
const deadline = deps.now() + STARTUP_WAIT_MS;
|
|
23
23
|
let lastReason = '';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Exec } from '../exec.js';
|
|
2
|
+
export declare function adoptConfiguredFleet(binPath: string, exec: Exec, platform: NodeJS.Platform, configPath?: string, selectedRole?: string): Promise<void>;
|
|
3
|
+
/** Boot resumes recorded transfers only. It does not rediscover removed agents
|
|
4
|
+
* or decide that every configured role should be started. */
|
|
5
|
+
export declare function resumeFleetTransfers(binPath: string, exec?: Exec, platform?: NodeJS.Platform): Promise<void>;
|
|
6
|
+
/** A remove racing the native-retired → registered seam must not be undone by
|
|
7
|
+
* boot receipt recovery. Serialize with that seam and close its cursor first. */
|
|
8
|
+
export declare function retirePermanentRegistration(name: string, exec: Exec): Promise<boolean>;
|