@cotal-ai/manager 0.101.2 → 0.101.4
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/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +21 -7
- package/dist/commands.js.map +1 -1
- package/dist/manager.d.ts +4 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +40 -16
- package/dist/manager.js.map +1 -1
- package/package.json +12 -12
package/dist/manager.js
CHANGED
|
@@ -64,6 +64,10 @@ export const READINESS_TIMEOUT_MS = 30_000;
|
|
|
64
64
|
* how long it waits past the window for the proof of its last read. An operator waits on this reply,
|
|
65
65
|
* so it asks more often than the exit watch that outlives readiness. */
|
|
66
66
|
const READINESS_EXIT_POLL_MS = 1_000;
|
|
67
|
+
/** How long the process of a launch that waits for the operator ({@link Connector.waitsForOperator})
|
|
68
|
+
* must run without exiting before it is ready: long enough that a harness refusing its arguments
|
|
69
|
+
* exits first and is reported as failed. */
|
|
70
|
+
const OPERATOR_READINESS_MS = 3_000;
|
|
67
71
|
/** Managed same-session crash recovery follows the Codex host precedent: three restarts are allowed
|
|
68
72
|
* inside a rolling two-minute window; the fourth crash is a loop and retires the seat loud. */
|
|
69
73
|
const SESSION_RESTART_LIMIT = 3;
|
|
@@ -3732,7 +3736,7 @@ export class Manager {
|
|
|
3732
3736
|
const refusal = material === undefined && !unshredded.length ? caughtEnvelope(e) : undefined;
|
|
3733
3737
|
return {
|
|
3734
3738
|
error: `agent auth preflight failed for "${name}": ${rejectionText(e)}${leftover}`,
|
|
3735
|
-
...(refusal?.outcome === "not-executed" ? { refused: refusal.code } : {}),
|
|
3739
|
+
...(refusal?.outcome === "not-executed" ? { refused: refusal.code, ...(refusal.details ? { details: refusal.details } : {}) } : {}),
|
|
3736
3740
|
};
|
|
3737
3741
|
}
|
|
3738
3742
|
}
|
|
@@ -5516,7 +5520,7 @@ export class Manager {
|
|
|
5516
5520
|
// manager's agent) refuses cleanly at accept, rather than minting the collision and black-
|
|
5517
5521
|
// holing on the broker/auth refusal (a pinned name live under another manager MUST refuse).
|
|
5518
5522
|
if (this.nameInUse(identityName))
|
|
5519
|
-
return { ok: false, error: `the name "${identityName}" is hard-pinned (${opts.resolved ? "manifest-declared" : "--name/identity override"}) but is already held by a live incarnation (managed here, an unmanaged foreground/connector session, or another manager's agent); a pinned same-name collision refuses at accept - pick another name or despawn the existing one` };
|
|
5523
|
+
return { ok: false, code: "already-exists", error: `the name "${identityName}" is hard-pinned (${opts.resolved ? "manifest-declared" : "--name/identity override"}) but is already held by a live incarnation (managed here, an unmanaged foreground/connector session, or another manager's agent); a pinned same-name collision refuses at accept - pick another name or despawn the existing one` };
|
|
5520
5524
|
name = identityName;
|
|
5521
5525
|
}
|
|
5522
5526
|
else {
|
|
@@ -5657,7 +5661,7 @@ export class Manager {
|
|
|
5657
5661
|
if ("error" in prep) {
|
|
5658
5662
|
if (prep.refused !== undefined)
|
|
5659
5663
|
hooks?.onUndone?.(prep.refused);
|
|
5660
|
-
return { ok: false, error: prep.error };
|
|
5664
|
+
return { ok: false, error: prep.error, ...(prep.details ? { details: prep.details } : {}) };
|
|
5661
5665
|
}
|
|
5662
5666
|
// THE HOST'S UID WINS (#1972). On the local arm this is the value passed in, unchanged. On
|
|
5663
5667
|
// the hosted enrollment arm the host selected it, and from here every lifecycle-keyed thing
|
|
@@ -5891,8 +5895,10 @@ export class Manager {
|
|
|
5891
5895
|
// #159 B1: reply on a REAL outcome, not a timer. Wait for the agent to actually join the mesh
|
|
5892
5896
|
// (presence) → started, the child to exit → failed (with its last output; already reaped), or
|
|
5893
5897
|
// neither in time → uncertain. `✓ started` therefore means "it joined", never just "a process
|
|
5894
|
-
// launched"
|
|
5895
|
-
|
|
5898
|
+
// launched", except for a launch whose options make the harness wait for the operator first: that
|
|
5899
|
+
// one is ready once its process runs, and its reply names the screen it waits at.
|
|
5900
|
+
const operator = connector.waitsForOperator?.(launchOptions);
|
|
5901
|
+
const readiness = await this.awaitReadiness(managed, readinessTimeoutMs, operator === undefined ? {} : { operator });
|
|
5896
5902
|
// Deliberately stopped mid-launch: reaped by onExit, and the despawn/stop path owns the
|
|
5897
5903
|
// goal terminal. Return BEFORE the failed/uncertain arms so this emits no competing
|
|
5898
5904
|
// outcome and does not re-arm an exit watcher on an agent already gone.
|
|
@@ -5915,7 +5921,7 @@ export class Manager {
|
|
|
5915
5921
|
this.watchExit(managed);
|
|
5916
5922
|
if (managed.launch.carried)
|
|
5917
5923
|
void this.refuseLateCarriedFork(managed, readinessTimeoutMs);
|
|
5918
|
-
await hooks?.onOutcome?.({ kind: "uncertain", data: { reason: readiness.detail } });
|
|
5924
|
+
await hooks?.onOutcome?.({ kind: "uncertain", data: { reason: readiness.detail, ...(readiness.running ? { running: true } : {}) } });
|
|
5919
5925
|
return { ok: false, error: readiness.detail };
|
|
5920
5926
|
}
|
|
5921
5927
|
if (managed.launch.carried) {
|
|
@@ -5987,7 +5993,7 @@ export class Manager {
|
|
|
5987
5993
|
// "no role", so dropping the key preserves the reply (surfaced by readiness:live).
|
|
5988
5994
|
// A host that granted other channels than this spawn asked for: the receipt carries both.
|
|
5989
5995
|
const acl = enrolled ? enrolledAcl({ allowSubscribe, allowPublish }, enrolled.material) : undefined;
|
|
5990
|
-
const okData = { name, agent, id: managed.id, mode: handle.kind, lifecycleUid, ...(role !== undefined ? { role } : {}), ...(opts.eventsNotice !== undefined ? { eventsNotice: opts.eventsNotice } : {}), ...(acl ? { acl } : {}) };
|
|
5996
|
+
const okData = { name, agent, id: managed.id, mode: handle.kind, lifecycleUid, ...(role !== undefined ? { role } : {}), ...(opts.eventsNotice !== undefined ? { eventsNotice: opts.eventsNotice } : {}), ...(acl ? { acl } : {}), ...(readiness.operator !== undefined ? { waitingFor: readiness.operator } : {}) };
|
|
5991
5997
|
await hooks?.onOutcome?.({ kind: "succeeded", data: okData });
|
|
5992
5998
|
return { ok: true, data: okData };
|
|
5993
5999
|
}
|
|
@@ -6713,7 +6719,10 @@ export class Manager {
|
|
|
6713
6719
|
* catches a join/exit that landed before we subscribed). A runtime whose `attach()` throws (an external
|
|
6714
6720
|
* surface) streams no output or exit, so its exit leg polls the runtime's exit proof ({@link pollProvedExit}),
|
|
6715
6721
|
* reads it once more when the window closes, and its failure carries no last output; one that cannot prove an
|
|
6716
|
-
* exit either races presence-vs-backstop only.
|
|
6722
|
+
* exit either races presence-vs-backstop only.
|
|
6723
|
+
* A launch that waits for the operator at `opts.operator`, a screen its connector names, is ready once a
|
|
6724
|
+
* process this runtime owns has run for {@link OPERATOR_READINESS_MS} without exiting. An uncertain verdict
|
|
6725
|
+
* says whether a process this runtime owns is still running. */
|
|
6717
6726
|
async awaitReadiness(a, readinessTimeoutMs, opts = {}) {
|
|
6718
6727
|
let session;
|
|
6719
6728
|
try {
|
|
@@ -6748,11 +6757,13 @@ export class Manager {
|
|
|
6748
6757
|
let timer;
|
|
6749
6758
|
let unsubExit = () => { };
|
|
6750
6759
|
let joinedPoll;
|
|
6760
|
+
let operatorTimer;
|
|
6751
6761
|
const finish = (r) => {
|
|
6752
6762
|
if (done)
|
|
6753
6763
|
return;
|
|
6754
6764
|
done = true;
|
|
6755
6765
|
clearTimeout(timer);
|
|
6766
|
+
clearTimeout(operatorTimer);
|
|
6756
6767
|
if (joinedPoll !== undefined)
|
|
6757
6768
|
clearInterval(joinedPoll);
|
|
6758
6769
|
this.ep.off("presence", onPresence);
|
|
@@ -6775,6 +6786,7 @@ export class Manager {
|
|
|
6775
6786
|
if (done)
|
|
6776
6787
|
return;
|
|
6777
6788
|
clearTimeout(timer);
|
|
6789
|
+
clearTimeout(operatorTimer);
|
|
6778
6790
|
// `stopHandle` sets `terminalizing` synchronously before it kills, and `onAgentExit` sets it
|
|
6779
6791
|
// through `freeSlot` when it retires the seat, so only a read before the await and the reap
|
|
6780
6792
|
// tells a deliberate stop from a launch failure.
|
|
@@ -6822,11 +6834,21 @@ export class Manager {
|
|
|
6822
6834
|
finish({ ok: false, detail: `${a.name} exited on launch${tail ? ` - last output: ${tail}` : ""}${exitDetail}` });
|
|
6823
6835
|
})();
|
|
6824
6836
|
};
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6837
|
+
// A process this runtime owns reports its own exit, so one still running here is running.
|
|
6838
|
+
const uncertain = () => {
|
|
6839
|
+
const running = s !== undefined && a.handle.status() === "running";
|
|
6840
|
+
finish({
|
|
6841
|
+
ok: false,
|
|
6842
|
+
uncertain: true,
|
|
6843
|
+
running,
|
|
6844
|
+
detail: running
|
|
6845
|
+
? `${a.name} (${a.id}): launch status uncertain - its process is running but did not join the mesh within ${readinessTimeoutMs}ms; it may still be starting or waiting at a prompt. Inspect with \`cotal attach ${a.name}\` / \`cotal ps\`; do not stop it solely because this bounded wait elapsed.`
|
|
6846
|
+
: `${a.name} (${a.id}): launch status uncertain - no process exit and no mesh presence within ${readinessTimeoutMs}ms; it may still be booting or stuck before connector startup. Inspect with \`cotal attach ${a.name}\` / \`cotal ps\`; do not stop it solely because this bounded wait elapsed.`,
|
|
6847
|
+
});
|
|
6848
|
+
};
|
|
6849
|
+
if (opts.operator !== undefined && s)
|
|
6850
|
+
operatorTimer = setTimeout(() => { if (a.handle.status() === "running")
|
|
6851
|
+
finish({ ok: true, operator: opts.operator }); }, OPERATOR_READINESS_MS);
|
|
6830
6852
|
// The exit poll read the runtime up to a poll ago, and a window can be shorter than a poll, so
|
|
6831
6853
|
// the window closes on a fresh read: a child that exited by then is reported as exited. Nothing
|
|
6832
6854
|
// bounds how long a runtime's proof takes, so it gets one more poll before the launch is uncertain.
|
|
@@ -8309,8 +8331,8 @@ export class Manager {
|
|
|
8309
8331
|
else {
|
|
8310
8332
|
// Forward the readiness detail as the terminal's reason: this manager owns the deadline,
|
|
8311
8333
|
// so it owns what elapsing it MEANS. Absent, core commits its own generic line (#605).
|
|
8312
|
-
const why = o.data
|
|
8313
|
-
({ fact } = await settleGoalUncertain(gw.ctx, { ref, now: Date.now(), committer: { instanceId: this.managerInstanceId, epoch }, ...(typeof why === "string" && why.length > 0 ? { reason: why } : {}) }));
|
|
8334
|
+
const { reason: why, running } = (o.data ?? {});
|
|
8335
|
+
({ fact } = await settleGoalUncertain(gw.ctx, { ref, now: Date.now(), committer: { instanceId: this.managerInstanceId, epoch }, ...(typeof why === "string" && why.length > 0 ? { reason: why } : {}), ...(running === true ? { running: true } : {}) }));
|
|
8314
8336
|
}
|
|
8315
8337
|
this.expireGoalAcceptance(ref);
|
|
8316
8338
|
this.emitGoalProgress(ref, epoch, { phase: "terminal", state: fact.state, ...(fact.data !== undefined ? { data: fact.data } : {}) });
|
|
@@ -8407,7 +8429,9 @@ export class Manager {
|
|
|
8407
8429
|
// outcome unknown.
|
|
8408
8430
|
if (acceptance === undefined) {
|
|
8409
8431
|
const details = reply.details;
|
|
8410
|
-
|
|
8432
|
+
// A hard-pinned name a live incarnation holds is the one refusal here that names its own code.
|
|
8433
|
+
const code = undone ?? (reply.code === "already-exists" ? "already-exists" : "failed-precondition");
|
|
8434
|
+
rejectAccept(new EpEnvelopeError(code, reply.error ?? "spawn refused at accept", details, effectStarted && undone === undefined ? undefined : "not-executed"));
|
|
8411
8435
|
return;
|
|
8412
8436
|
}
|
|
8413
8437
|
// H1: `run` CATCHES its own body (a throw in buildLaunch/runtime.spawn) and RESOLVES `{ok:false}`
|