@ours.network/fleet 0.17.3 → 0.17.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.
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "0.17.3",
3
- "buildId": "789acfed6bbd",
4
- "commit": "47f1efd0f90321cf233edc694a9af5fb5ba05647",
2
+ "version": "0.17.4",
3
+ "buildId": "b94609824510",
4
+ "commit": "094a6fcb714541b03404e0fb39605105e390510b",
5
5
  "dirty": false,
6
- "builtAt": "2026-08-14T20:15:30.780Z",
6
+ "builtAt": "2026-08-14T21:36:46.539Z",
7
7
  "capabilities": [
8
8
  "monitor.interrupt.after_tool"
9
9
  ]
package/dist/runner.d.ts CHANGED
@@ -3,7 +3,9 @@ import type { Launch } from './harness/types.js';
3
3
  import { Tmux } from './tmux.js';
4
4
  import { type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
5
5
  import { type Exec } from './exec.js';
6
- import type { ExitRecord } from './session/types.js';
6
+ import { type AcpSessionOptions } from './session/acp.js';
7
+ import { RoleControlServer } from './session/control.js';
8
+ import type { ExitRecord, SessionHandle, TurnResult } from './session/types.js';
7
9
  import { type OwnerChannelHandle, type OwnerChannelOptions } from './owner-channel/channel.js';
8
10
  import { type OwnerBinderLease } from './owner-channel/binder.js';
9
11
  export interface RunnerDeps {
@@ -20,6 +22,10 @@ export interface RunnerDeps {
20
22
  createMonitor(opts: MonitorOpts): MonitorHandle;
21
23
  /** Construct trusted owner ingress (injectable for lifecycle tests). */
22
24
  createOwnerChannel(opts: OwnerChannelOptions): OwnerChannelHandle;
25
+ /** Start the ACP transport (injectable for deterministic runner lifecycle tests). */
26
+ startAcpSession(opts: AcpSessionOptions): Promise<SessionHandle>;
27
+ /** Construct the authenticated role control route (injectable where sockets are unavailable). */
28
+ createControlServer(stateDir: string, session: SessionHandle, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'>;
23
29
  /** Acquire the cross-process owner-channel binder lease before replacing the control socket. */
24
30
  acquireOwnerBinder(stateDir: string, role: string, identity: string): Promise<OwnerBinderLease>;
25
31
  /** Ask the still-authenticated predecessor to emit the fixed recovery notice. */
@@ -107,6 +113,8 @@ export interface AttemptResult {
107
113
  export declare const TEMP_IDENTITY_CLOSE_DEBOUNCE_MS = 5000;
108
114
  /** Lifecycle polling is deliberately slower than the 500ms stop-signal loop. */
109
115
  export declare const TEMP_IDENTITY_POLL_MS = 2000;
116
+ /** Only authenticated wake interrupts may turn a temp startup cancellation into readiness. */
117
+ export declare function isRecoverableTempStartupCancellation(temp: boolean, result: TurnResult): boolean;
110
118
  /** One session lifecycle. `runSupervised` (or a one-shot caller) drives it. */
111
119
  export declare function runOnce(name: string, opts?: {
112
120
  temp?: boolean;
package/dist/runner.js CHANGED
@@ -42,6 +42,8 @@ const defaultDeps = () => ({
42
42
  fetch: (url, init) => globalThis.fetch(url, init),
43
43
  createMonitor: opts => createMonitor(opts),
44
44
  createOwnerChannel: opts => new OwnerChannel(opts),
45
+ startAcpSession: opts => AcpSession.start(opts),
46
+ createControlServer: (stateDir, session, log) => new RoleControlServer(stateDir, session, log),
45
47
  acquireOwnerBinder: (stateDir, role, identity) => acquireOwnerBinderLease(stateDir, role, identity),
46
48
  reportOwnerStartupFailure: async (stateDir) => {
47
49
  const response = await controlRequest(stateDir, {
@@ -352,6 +354,12 @@ function resolveConfigPath(dir, explicit) {
352
354
  export const TEMP_IDENTITY_CLOSE_DEBOUNCE_MS = 5_000;
353
355
  /** Lifecycle polling is deliberately slower than the 500ms stop-signal loop. */
354
356
  export const TEMP_IDENTITY_POLL_MS = 2_000;
357
+ /** Only authenticated wake interrupts may turn a temp startup cancellation into readiness. */
358
+ export function isRecoverableTempStartupCancellation(temp, result) {
359
+ return temp && result.accepted && result.outcome === 'cancelled'
360
+ && (result.cancellationSource === 'local-console'
361
+ || result.cancellationSource === 'fleet-monitor');
362
+ }
355
363
  /** One session lifecycle. `runSupervised` (or a one-shot caller) drives it. */
356
364
  export async function runOnce(name, opts = {}, partialDeps = {}) {
357
365
  const deps = { ...defaultDeps(), ...partialDeps };
@@ -512,7 +520,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
512
520
  if (perms.unattended === 'deny')
513
521
  deps.log(`[${name}] permission policy: unattended=deny — with no console attached, ` +
514
522
  `permission requests are automatically denied once each (reject_once) and the turn continues`);
515
- acpSession = await AcpSession.start({
523
+ acpSession = await deps.startAcpSession({
516
524
  name,
517
525
  argv: wrappedArgv,
518
526
  cwd: runCwd,
@@ -555,7 +563,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
555
563
  + `${error?.message ?? String(error)}`);
556
564
  }
557
565
  }
558
- control = new RoleControlServer(dir, arbiter, deps.log);
566
+ control = deps.createControlServer(dir, arbiter, deps.log);
559
567
  try {
560
568
  await control.start();
561
569
  }
@@ -640,7 +648,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
640
648
  // success, so there is neither a deaf gap nor a boot-cancellation loop.
641
649
  monitorLoop = monitor?.run(pid);
642
650
  const started = await starting;
643
- if (!started.succeeded) {
651
+ // A temporary role's first turn can be the active turn when an ours wake
652
+ // needs immediate attention. A typed console/monitor cancellation ends
653
+ // only that turn: the already-live ACP session and any queued wake remain
654
+ // valid. Keep every unproven cancellation, refusal, shutdown, and genuine
655
+ // failure terminal so a role that never accepted its briefing is not
656
+ // silently reported as healthy.
657
+ const interruptedForWake = isRecoverableTempStartupCancellation(temp, started);
658
+ if (!started.succeeded && !interruptedForWake) {
644
659
  monitor?.stop();
645
660
  await control.close();
646
661
  ownerBinder?.release();
@@ -664,6 +679,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
664
679
  throw new Error(`[${name}] ACP startup prompt ${started.outcome}` +
665
680
  `${started.detail ? `: ${started.detail}` : ''}`);
666
681
  }
682
+ if (interruptedForWake)
683
+ deps.log(`[${name}] ACP startup prompt cancelled by ${started.cancellationSource}; `
684
+ + 'keeping temporary supervisor alive');
667
685
  acpStartupComplete = true;
668
686
  if (role.owner_channel) {
669
687
  ownerChannel = deps.createOwnerChannel({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "0.17.3",
3
+ "version": "0.17.4",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",