@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.
- package/README.md +20 -2
- package/dist/application/task-room-service.d.ts +30 -1
- package/dist/application/task-room-service.js +167 -104
- package/dist/build-info.json +4 -4
- package/dist/cli.js +9 -1
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +20 -2
- package/dist/fleet-command-audit.d.ts +11 -0
- package/dist/fleet-command-audit.js +47 -3
- package/dist/owner-channel/channel.d.ts +4 -0
- package/dist/owner-channel/channel.js +90 -8
- package/dist/owner-channel/commands.d.ts +7 -2
- package/dist/owner-channel/commands.js +36 -2
- package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
- package/dist/owner-channel/lifecycle-outbox.js +87 -0
- package/dist/rooms-tasks/cli.js +139 -106
- package/dist/rooms-tasks/external-worker.d.ts +8 -0
- package/dist/rooms-tasks/external-worker.js +38 -0
- package/dist/rooms-tasks/provision.js +14 -25
- package/dist/runner.js +2 -0
- package/dist/session/control.d.ts +1 -1
- package/dist/session/control.js +14 -1
- package/package.json +1 -1
|
@@ -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 =
|
|
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;
|
package/dist/session/control.js
CHANGED
|
@@ -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.
|
|
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",
|