@ours.network/fleet 1.1.0-nightly.22 → 1.1.0-nightly.23

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": "1.1.0-nightly.22",
3
- "buildId": "bfb51e6ced00",
4
- "commit": "5447c29d81481c7880fa9cf5bfbbcc2ff294950f",
2
+ "version": "1.1.0-nightly.23",
3
+ "buildId": "de853a9ef3d8",
4
+ "commit": "f970d01c44d8a5cad2627c1bdde833e412508613",
5
5
  "dirty": true,
6
- "builtAt": "2026-09-01T19:41:56.761Z",
6
+ "builtAt": "2026-09-02T07:30:34.670Z",
7
7
  "capabilities": [
8
8
  "monitor.interrupt.after_tool"
9
9
  ]
@@ -225,6 +225,8 @@ export declare class OwnerChannel implements OwnerChannelHandle {
225
225
  resourceIds?: Record<string, string>;
226
226
  presentations?: FleetAuditPresentation[];
227
227
  }): Promise<FleetAuditAttempt>;
228
+ private flushPendingFleetCommandAudits;
229
+ private deliverFleetCommandOutcome;
228
230
  recover(epoch: string): Promise<void>;
229
231
  private recoveryStage;
230
232
  private writeShutdownState;
@@ -199,6 +199,7 @@ export class OwnerChannel {
199
199
  }
200
200
  this.startedOnce = true;
201
201
  this.ready = true;
202
+ await this.flushPendingFleetCommandAudits();
202
203
  // Do not make role startup wait for an old owner request to finish a turn.
203
204
  // watchLoop itself drains before every establishment, including this first
204
205
  // one, so there is no drain-to-tip race.
@@ -301,33 +302,50 @@ export class OwnerChannel {
301
302
  return attempt;
302
303
  if (attempt.outcome?.delivery === 'uncertain')
303
304
  return attempt;
304
- let uncertain = false;
305
- try {
306
- for (const event of attempt.outcome?.presentations ?? []) {
307
- try {
308
- await this.sendProactiveMessage(renderFleetLifecycleEvent(event), `fleet-lifecycle\0${lifecycleEventDigestBasis(event)}`, 0);
309
- }
310
- catch (error) {
311
- if (error instanceof DuplicateSendError) {
312
- if (error.status !== 'delivered')
313
- uncertain = true;
314
- this.options.log(`[${this.options.role}] duplicate Fleet lifecycle event suppressed`);
315
- continue;
316
- }
317
- throw error;
318
- }
319
- }
320
- }
321
- catch (error) {
322
- uncertain = true;
323
- this.logError(`fleet lifecycle delivery uncertain correlation=${attempt.correlationId}`, error);
324
- }
325
- attempt = this.commandAudits.outcome(attempt.correlationId, this.options.role, uncertain ? 'uncertain' : 'delivered');
326
- return attempt;
305
+ // Before the Owner sink is attached, ordinary commands have nothing to
306
+ // deliver. Lifecycle presentations remain in the never-attempted
307
+ // `sending` state and are flushed exactly once when start() makes the
308
+ // authenticated sink ready. A restart converts them to `uncertain`, so
309
+ // effects are never replayed after an unproven delivery boundary.
310
+ if (!attempt.outcome?.presentations?.length)
311
+ return this.commandAudits.outcome(attempt.correlationId, this.options.role, 'delivered');
312
+ if (!this.ready)
313
+ return attempt;
314
+ return this.deliverFleetCommandOutcome(attempt);
327
315
  });
328
316
  this.managementTail = run.then(() => undefined, () => undefined);
329
317
  return run;
330
318
  }
319
+ async flushPendingFleetCommandAudits() {
320
+ for (const attempt of this.commandAudits.list())
321
+ if (attempt.outcome?.delivery === 'sending')
322
+ await this.deliverFleetCommandOutcome(attempt);
323
+ }
324
+ async deliverFleetCommandOutcome(attempt) {
325
+ let uncertain = false;
326
+ try {
327
+ for (const event of attempt.outcome?.presentations ?? []) {
328
+ try {
329
+ await this.sendProactiveMessage(renderFleetLifecycleEvent(event), `fleet-lifecycle\0${lifecycleEventDigestBasis(event)}`, 0);
330
+ }
331
+ catch (error) {
332
+ if (error instanceof DuplicateSendError) {
333
+ if (error.status !== 'delivered')
334
+ uncertain = true;
335
+ this.options.log(`[${this.options.role}] duplicate Fleet lifecycle event suppressed`);
336
+ continue;
337
+ }
338
+ throw error;
339
+ }
340
+ }
341
+ }
342
+ catch (error) {
343
+ uncertain = true;
344
+ this.logError(`fleet lifecycle delivery uncertain correlation=${attempt.correlationId}`, error);
345
+ }
346
+ attempt = this.commandAudits.outcome(attempt.correlationId, this.options.role, uncertain ? 'uncertain' : 'delivered');
347
+ return attempt;
348
+ }
331
349
  recover(epoch) {
332
350
  if (this.stopping)
333
351
  return Promise.reject(new Error('owner channel is stopping'));
package/dist/runner.js CHANGED
@@ -26,6 +26,7 @@ import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-prox
26
26
  import { effectivePermissionMode } from './permissions.js';
27
27
  import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
28
28
  import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
29
+ import { FleetCommandAuditStore } from './fleet-command-audit.js';
29
30
  export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
30
31
  export class SupervisorRecycleRequiredError extends Error {
31
32
  code = SUPERVISOR_RECYCLE_REQUIRED;
@@ -70,6 +71,31 @@ const defaultDeps = () => ({
70
71
  });
71
72
  const MONITOR_OWNER_FILE = '.monitor-owner';
72
73
  const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
74
+ function localFleetAuditor(stateDir, caller, log) {
75
+ const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
76
+ return {
77
+ async begin(requestId, argv) {
78
+ let attempt = store.begin(requestId, caller, argv);
79
+ if (attempt.invocation === 'sending') {
80
+ log(`[${caller}] fleet proxy command ${attempt.correlationId} `
81
+ + `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
82
+ attempt = store.invocation(attempt.correlationId, caller, 'delivered');
83
+ }
84
+ return attempt;
85
+ },
86
+ async finish(input) {
87
+ let attempt = store.finish(input.correlationId, caller, {
88
+ class: input.class, effect: input.effect,
89
+ ...(input.exitCode === undefined ? {} : { exitCode: input.exitCode }),
90
+ ...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
91
+ ...(input.presentations ? { presentations: input.presentations } : {}),
92
+ });
93
+ if (attempt.outcome?.delivery === 'sending')
94
+ attempt = store.outcome(attempt.correlationId, caller, 'delivered');
95
+ return attempt;
96
+ },
97
+ };
98
+ }
73
99
  /** Environment injected only into the managed harness process. */
74
100
  export function managedFleetProxyEnv(role, stateDir) {
75
101
  const roleEnv = { ...(role.env ?? {}) };
@@ -612,11 +638,28 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
612
638
  + `${error?.message ?? String(error)}`);
613
639
  }
614
640
  }
615
- control = deps.createControlServer(dir, arbiter, deps.log);
616
641
  try {
642
+ if (role.owner_channel)
643
+ ownerChannel = deps.createOwnerChannel({
644
+ role: name,
645
+ harness: role.harness,
646
+ config: role.owner_channel,
647
+ session: arbiter,
648
+ stateDir: dir,
649
+ env: role.env,
650
+ log: deps.log,
651
+ ...(ownerBinder ? { binderLease: ownerBinder } : {}),
652
+ ...(configPath ? { configPath } : {}),
653
+ });
654
+ control = deps.createControlServer(dir, arbiter, deps.log);
655
+ control.setFleetAuditor(ownerChannel ? {
656
+ begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
657
+ finish: input => ownerChannel.finishFleetCommandAudit(input),
658
+ } : localFleetAuditor(dir, name, deps.log));
617
659
  await control.start();
618
660
  }
619
661
  catch (error) {
662
+ await ownerChannel?.close().catch(() => undefined);
620
663
  ownerBinder?.release();
621
664
  await agentSession.close();
622
665
  unsubscribeRecovery?.();
@@ -731,18 +774,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
731
774
  deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
732
775
  + 'keeping temporary supervisor alive');
733
776
  sessionStartupComplete = true;
734
- if (role.owner_channel) {
735
- ownerChannel = deps.createOwnerChannel({
736
- role: name,
737
- harness: role.harness,
738
- config: role.owner_channel,
739
- session: arbiter,
740
- stateDir: dir,
741
- env: role.env,
742
- log: deps.log,
743
- ...(ownerBinder ? { binderLease: ownerBinder } : {}),
744
- ...(configPath ? { configPath } : {}),
745
- });
777
+ if (ownerChannel) {
746
778
  try {
747
779
  await ownerChannel.start();
748
780
  }
@@ -761,10 +793,6 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
761
793
  }
762
794
  if (ownerChannel) {
763
795
  control.setOwnerChannel(ownerChannel);
764
- control.setFleetAuditor({
765
- begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
766
- finish: input => ownerChannel.finishFleetCommandAudit(input),
767
- });
768
796
  }
769
797
  reloadLoopConfig = async () => {
770
798
  const nextRole = findRole(loadConfig(configPath), name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.1.0-nightly.22",
3
+ "version": "1.1.0-nightly.23",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",