@ours.network/fleet 1.1.0-nightly.25 → 1.1.0-nightly.26

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.
@@ -21,7 +21,6 @@ import { buildRoomMemberTask, sha256Text } from './member-startup.js';
21
21
  import { agentDir } from '../paths.js';
22
22
  import { readProvenance } from '../creation.js';
23
23
  import { readTempSupervisor, secureStoppedTempArchive, tempArchiveForCreationAction, tempSupervisorLiveness, } from '../temp-lifecycle.js';
24
- import { recordFleetAuditPresentation } from '../fleet-command-audit.js';
25
24
  export function getBinPath() {
26
25
  try {
27
26
  return realpathSync(process.argv[1]);
@@ -30,17 +29,6 @@ export function getBinPath() {
30
29
  return process.argv[1];
31
30
  }
32
31
  }
33
- function selectionSummary(selection) {
34
- if ('ref' in selection)
35
- return `ref:${selection.ref} (reference)`;
36
- return `inline:sha256:${createHash('sha256').update(canonicalJson(selection.inline)).digest('hex').slice(0, 16)} (inline)`;
37
- }
38
- function permissionSummary(definition) {
39
- const permissions = definition.permissions;
40
- if (!permissions)
41
- return undefined;
42
- return `approval=${permissions.approval},filesystem=${permissions.filesystem},unattended=${permissions.unattended}`;
43
- }
44
32
  function launchDefinition(definition) {
45
33
  return {
46
34
  projection: redactLaunchDefinition(definition),
@@ -324,13 +312,6 @@ async function launchMemberUnlocked(input) {
324
312
  ...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
325
313
  launch_id: supervisor.launchId, updated_at: new Date().toISOString(),
326
314
  } });
327
- recordFleetAuditPresentation({ kind: 'agent_started', id: member.name, name: member.name,
328
- lifetime: 'temporary', brain: selectionSummary(settings.definition.brain),
329
- role: selectionSummary(settings.definition.role),
330
- harness: presentation.harness, session: 'acp',
331
- permissions: permissionSummary(settings.definition), parent: provision.roomId,
332
- actionId: launched.creationActionId, inherited: [],
333
- configuration: presentation });
334
315
  }
335
316
  catch (error) {
336
317
  updateMemberStartup(provision.roomId, member.name, { launch: {
@@ -341,8 +322,6 @@ async function launchMemberUnlocked(input) {
341
322
  updated_at: new Date().toISOString(),
342
323
  error: error instanceof Error ? error.message : String(error),
343
324
  } });
344
- recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Agent', id: member.name,
345
- state: 'failed', category: 'readiness_failed', eventId: effectiveActionId });
346
325
  throw error;
347
326
  }
348
327
  }
@@ -360,9 +339,10 @@ function exactCoworkSeat(observed, member, expectedInviteId) {
360
339
  }
361
340
  return seat;
362
341
  }
363
- function reconcileMemberSeats(roomId, members, observed) {
342
+ function reconcileMemberSeats(roomId, members, observed, ownerSeatCid) {
364
343
  const current = getRoomRecord(roomId);
365
- let complete = true;
344
+ let complete = !ownerSeatCid || observed.some(seat => seat.identity_cid.toLowerCase() === ownerSeatCid.toLowerCase()
345
+ && seat.seat_state === 'active');
366
346
  const seats = current.member_seats.map(seat => {
367
347
  const member = members.find(candidate => candidate.name === seat.role_name);
368
348
  const found = exactCoworkSeat(observed, member, seat.invite_id);
@@ -442,7 +422,7 @@ export async function provisionMembers(input) {
442
422
  try {
443
423
  const initialRoom = await cowork.recoverRoom(roomId);
444
424
  assertCoworkRoomPolicy(initialRoom, roomPolicy.anonymous);
445
- reconcileMemberSeats(roomId, members, initialRoom.seats);
425
+ reconcileMemberSeats(roomId, members, initialRoom.seats, existing.owner_seat_cid);
446
426
  for (const member of members) {
447
427
  const task = tasks.get(member.name);
448
428
  const currentSeat = getRoomRecord(roomId).member_seats
@@ -510,7 +490,7 @@ export async function provisionMembers(input) {
510
490
  for (;;) {
511
491
  const remote = await cowork.recoverRoom(roomId);
512
492
  assertCoworkRoomPolicy(remote, roomPolicy.anonymous);
513
- const reconciled = reconcileMemberSeats(roomId, members, remote.seats);
493
+ const reconciled = reconcileMemberSeats(roomId, members, remote.seats, ownerSeatCid ?? undefined);
514
494
  if (reconciled.complete)
515
495
  break;
516
496
  if (policy.now() >= deadline) {
@@ -521,6 +501,15 @@ export async function provisionMembers(input) {
521
501
  await policy.sleep(delay);
522
502
  delay = Math.min(policy.maxDelayMs, Math.max(delay + 1, delay * 2));
523
503
  }
504
+ // Admission alone is insufficient: every expected member must still have
505
+ // the exact live launch that owns its authenticated Cowork seat.
506
+ for (const member of members) {
507
+ if (!await retainRunningLaunch({
508
+ provision: input, member, settings: settings.get(member.name),
509
+ task: tasks.get(member.name), roomIdentityCid,
510
+ }))
511
+ throw new Error(`active Cowork seat ${member.name} has no matching live Fleet launch`);
512
+ }
524
513
  if (taskId) {
525
514
  const taskMembers = getRoomRecord(roomId).member_seats.map(seat => {
526
515
  if (!seat.identity_cid)
package/dist/runner.js CHANGED
@@ -94,6 +94,7 @@ function localFleetAuditor(stateDir, caller, log) {
94
94
  attempt = store.outcome(attempt.correlationId, caller, 'delivered');
95
95
  return attempt;
96
96
  },
97
+ async present() { },
97
98
  };
98
99
  }
99
100
  /** Environment injected only into the managed harness process. */
@@ -655,6 +656,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
655
656
  control.setFleetAuditor(ownerChannel ? {
656
657
  begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
657
658
  finish: input => ownerChannel.finishFleetCommandAudit(input),
659
+ present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
658
660
  } : localFleetAuditor(dir, name, deps.log));
659
661
  await control.start();
660
662
  }
@@ -9,7 +9,7 @@ export interface ControlRequest {
9
9
  version: 1 | 2 | 3;
10
10
  id: string;
11
11
  token: string;
12
- command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_finish';
12
+ command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
13
13
  text?: string;
14
14
  permissionId?: string;
15
15
  optionId?: string;
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node
3
3
  import { createConnection, createServer } from 'node:net';
4
4
  import { join } from 'node:path';
5
5
  import { SessionControlError } from './types.js';
6
- import { validateFleetAuditBegin, validateFleetAuditFinish, } from '../fleet-command-audit.js';
6
+ import { validateFleetAuditBegin, validateFleetAuditFinish, validateFleetAuditPresentations, } from '../fleet-command-audit.js';
7
7
  import { interruptSession, queueSessionPrompt, respondSessionPermission, respondSessionPermissionV2, } from '../application/session-mutations.js';
8
8
  const MAX_LINE_BYTES = 64 * 1024;
9
9
  /** Commands that require protocol version 3. */
@@ -349,6 +349,19 @@ export class RoleControlServer {
349
349
  this.write(socket, { version: 1, id: request.id, ok: true, result });
350
350
  return;
351
351
  }
352
+ case 'fleet_audit_present': {
353
+ if (!this.fleetAuditor)
354
+ throw new SessionControlError('rejected', 'fleet auditing is unavailable');
355
+ try {
356
+ validateFleetAuditPresentations(request.audit?.presentations);
357
+ }
358
+ catch {
359
+ throw new SessionControlError('rejected', 'valid Fleet lifecycle presentations are required');
360
+ }
361
+ await this.fleetAuditor.present(request.audit.presentations);
362
+ this.write(socket, { version: 1, id: request.id, ok: true, result: { delivered: true } });
363
+ return;
364
+ }
352
365
  case 'owner_channel_manage': {
353
366
  if (!request.ownerChannel || typeof request.ownerChannel.action !== 'string')
354
367
  throw new SessionControlError('rejected', 'owner-channel management action is required');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.1.0-nightly.25",
3
+ "version": "1.1.0-nightly.26",
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",