@ours.network/fleet 1.2.0-nightly.12 → 1.2.0-nightly.14
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 +2 -0
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/task-room-service.d.ts +3 -0
- package/dist/application/task-room-service.js +1 -1
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/build-info.json +4 -4
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +1 -0
- package/dist/fleet-command-audit.js +9 -5
- package/dist/init-wizard.js +10 -3
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/rooms-tasks/close.d.ts +2 -2
- package/dist/rooms-tasks/close.js +128 -22
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +41 -9
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +169 -0
- package/dist/rooms-tasks/room-state.d.ts +3 -1
- package/dist/rooms-tasks/room-state.js +37 -7
- package/dist/rooms-tasks/task-state.d.ts +10 -4
- package/dist/rooms-tasks/task-state.js +54 -12
- package/dist/rooms-tasks/types.d.ts +2 -0
- package/dist/temp-lifecycle.d.ts +5 -0
- package/dist/temp-lifecycle.js +28 -12
- package/docs/task-workspaces.md +34 -2
- package/package.json +1 -1
- package/presets/README.md +66 -0
- package/presets/brain-catalog.json +7 -2
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/manifest.json +2 -2
|
@@ -7,14 +7,23 @@ import { getTask } from './task-state.js';
|
|
|
7
7
|
import { storedRoomLaunchPolicy } from './types.js';
|
|
8
8
|
import { releaseLaunchSnapshot } from './launch-snapshot.js';
|
|
9
9
|
export const roomsDir = () => join(stateRoot(), 'rooms');
|
|
10
|
-
function roomPath(id) {
|
|
10
|
+
function roomPath(id) {
|
|
11
|
+
if (!/^[a-zA-Z0-9_-]{1,120}$/.test(id))
|
|
12
|
+
throw new RoomStateError('Invalid room ID');
|
|
13
|
+
return join(roomsDir(), `${id}.json`);
|
|
14
|
+
}
|
|
11
15
|
export class RoomStateError extends Error {
|
|
12
16
|
}
|
|
13
17
|
function readRoom(id) {
|
|
14
18
|
const p = roomPath(id);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT')
|
|
24
|
+
throw new RoomStateError(`room not found: ${id}`);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
18
27
|
}
|
|
19
28
|
function writeRoom(record) {
|
|
20
29
|
mkdirSync(roomsDir(), { recursive: true });
|
|
@@ -54,8 +63,10 @@ export function getRoomRecord(id) {
|
|
|
54
63
|
try {
|
|
55
64
|
return readRoom(id);
|
|
56
65
|
}
|
|
57
|
-
catch {
|
|
58
|
-
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof RoomStateError && error.message === `room not found: ${id}`)
|
|
68
|
+
return undefined;
|
|
69
|
+
throw error;
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
/** Remove a terminal orchestration record from the live room inventory. */
|
|
@@ -211,7 +222,7 @@ export function beginRoomClose(id) {
|
|
|
211
222
|
const RETIREMENT_ORDER = [
|
|
212
223
|
'stop_requested', 'liveness_absent', 'archive_secured', 'identity_absent',
|
|
213
224
|
];
|
|
214
|
-
export function advanceMemberRetirement(id, roleName, phase, launchId, archivePath) {
|
|
225
|
+
export function advanceMemberRetirement(id, roleName, phase, launchId, archivePath, absenceVerified = false) {
|
|
215
226
|
const r = readRoom(id);
|
|
216
227
|
if (r.state !== 'closing')
|
|
217
228
|
throw new RoomStateError(`room ${id} is not closing`);
|
|
@@ -228,6 +239,7 @@ export function advanceMemberRetirement(id, roleName, phase, launchId, archivePa
|
|
|
228
239
|
throw new RoomStateError(`room ${id} member ${roleName} retirement cannot move backward to ${phase}`);
|
|
229
240
|
}
|
|
230
241
|
seat.retirement = {
|
|
242
|
+
...(absenceVerified || previous?.absence_verified ? { absence_verified: true } : {}),
|
|
231
243
|
phase,
|
|
232
244
|
launch_id: launchId,
|
|
233
245
|
updated_at: new Date().toISOString(),
|
|
@@ -280,3 +292,21 @@ export function setRoomCloseError(id, error, recoveryHint) {
|
|
|
280
292
|
writeRoom(r);
|
|
281
293
|
return r;
|
|
282
294
|
}
|
|
295
|
+
/** Settlement-only binding recovery; never publishes a seat or changes a launch. */
|
|
296
|
+
export function recordRetirementMemberCids(id, recovered) {
|
|
297
|
+
const room = readRoom(id);
|
|
298
|
+
if (room.state !== 'closing')
|
|
299
|
+
throw new RoomStateError('Room is not closing');
|
|
300
|
+
for (const proof of recovered) {
|
|
301
|
+
const seat = room.member_seats.find(value => value.role_name === proof.role_name);
|
|
302
|
+
if (!seat || seat.launch?.action_id !== proof.launch?.action_id || seat.invite_id !== proof.invite_id)
|
|
303
|
+
throw new RoomStateError('Member changed during identity recovery');
|
|
304
|
+
if (!proof.identity_cid)
|
|
305
|
+
continue;
|
|
306
|
+
if (seat.identity_cid && seat.identity_cid.toLowerCase() !== proof.identity_cid.toLowerCase())
|
|
307
|
+
throw new RoomStateError('Member identity changed during recovery');
|
|
308
|
+
seat.identity_cid = proof.identity_cid;
|
|
309
|
+
}
|
|
310
|
+
writeRoom(room);
|
|
311
|
+
return room;
|
|
312
|
+
}
|
|
@@ -62,9 +62,8 @@ export declare function getDeletingTask(id: string): TaskRecord;
|
|
|
62
62
|
export declare function taskDeletionState(id: string): 'none' | 'pending' | 'absent';
|
|
63
63
|
/**
|
|
64
64
|
* Durable, surface-independent audit evidence for a permanent deletion. The
|
|
65
|
-
* receipt
|
|
66
|
-
*
|
|
67
|
-
* room content.
|
|
65
|
+
* receipt supports in-flight recovery only: written with acceptance intent,
|
|
66
|
+
* then erased before settlement is reported. It may contain a bounded title.
|
|
68
67
|
*/
|
|
69
68
|
export interface TaskDeletionReceipt {
|
|
70
69
|
schema_version: 1;
|
|
@@ -88,7 +87,7 @@ export declare function readTaskDeletionReceipt(id: string): TaskDeletionReceipt
|
|
|
88
87
|
* settlement rather than deleting resources without audit evidence.
|
|
89
88
|
*/
|
|
90
89
|
export declare function ensureTaskDeletionReceipt(id: string): void;
|
|
91
|
-
/**
|
|
90
|
+
/** Erase the in-flight receipt; also heals a crash after task unlink. */
|
|
92
91
|
export declare function completeTaskDeletionReceipt(id: string): void;
|
|
93
92
|
export type TaskDeletionAcceptance = {
|
|
94
93
|
status: 'accepted' | 'pending';
|
|
@@ -129,6 +128,7 @@ interface SeatEvidence {
|
|
|
129
128
|
action_id?: string;
|
|
130
129
|
};
|
|
131
130
|
retirement?: {
|
|
131
|
+
absence_verified?: boolean;
|
|
132
132
|
phase: TaskDeletionMemberPhase;
|
|
133
133
|
launch_id: string;
|
|
134
134
|
archive_path?: string;
|
|
@@ -166,4 +166,10 @@ export declare function beginTaskWorkspaceCleanup(id: string): void;
|
|
|
166
166
|
* records are the idempotent already-settled outcome.
|
|
167
167
|
*/
|
|
168
168
|
export declare function unlinkDeletedTask(id: string): boolean;
|
|
169
|
+
/** Called only after current temp and exact identity absence have been checked. */
|
|
170
|
+
export declare function settleAbsentTaskDeletionMember(id: string, name: string): void;
|
|
171
|
+
/** Room-only erasure keeps the parent Task but removes its dead room/member links. */
|
|
172
|
+
export declare function detachDeletedRoom(taskId: string, roomId: string): void;
|
|
173
|
+
/** A completed room may consume its archives while other task members still retire. */
|
|
174
|
+
export declare function beginTaskArchiveCleanup(id: string): void;
|
|
169
175
|
export {};
|
|
@@ -535,14 +535,10 @@ export function ensureTaskDeletionReceipt(id) {
|
|
|
535
535
|
writeDeletionReceiptForIntent(stored);
|
|
536
536
|
});
|
|
537
537
|
}
|
|
538
|
-
/**
|
|
538
|
+
/** Erase the in-flight receipt; also heals a crash after task unlink. */
|
|
539
539
|
export function completeTaskDeletionReceipt(id) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return;
|
|
543
|
-
receipt.settled_at = new Date().toISOString();
|
|
544
|
-
receipt.result = 'deleted';
|
|
545
|
-
replaceFileAtomically(deletionReceiptPath(id), JSON.stringify(receipt, null, 2) + '\n');
|
|
540
|
+
assertCanonicalTaskId(id);
|
|
541
|
+
rmSync(deletionReceiptPath(id), { force: true });
|
|
546
542
|
}
|
|
547
543
|
/**
|
|
548
544
|
* Persist the first-wins durable deletion intent. Accepts every lifecycle
|
|
@@ -561,8 +557,10 @@ export function beginTaskDeletionIntent(id, actor) {
|
|
|
561
557
|
stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
562
558
|
}
|
|
563
559
|
catch (error) {
|
|
564
|
-
if (isNotFoundError(error))
|
|
560
|
+
if (isNotFoundError(error)) {
|
|
561
|
+
completeTaskDeletionReceipt(id);
|
|
565
562
|
return { status: 'already_absent' };
|
|
563
|
+
}
|
|
566
564
|
throw error;
|
|
567
565
|
}
|
|
568
566
|
if (stored.deletion?.status === 'pending') {
|
|
@@ -712,12 +710,17 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
712
710
|
const evidence = seat.retirement;
|
|
713
711
|
if (evidence?.phase !== 'identity_absent')
|
|
714
712
|
throw new TaskStateError(`task ${id} deletion cannot checkpoint member '${seat.role_name}' before completed retirement`);
|
|
715
|
-
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
713
|
+
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
714
|
+
|| evidence.launch_id === DELETION_MEMBER_ABSENT_VERIFIED || evidence.absence_verified === true;
|
|
716
715
|
if (!neverLaunched && evidence.archive_path === undefined)
|
|
717
716
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' retirement evidence lacks its archive`);
|
|
718
717
|
if (!seat.identity_cid) {
|
|
719
|
-
if (neverLaunched)
|
|
720
|
-
|
|
718
|
+
if (neverLaunched) {
|
|
719
|
+
stored.deletion.absent_members ??= [];
|
|
720
|
+
if (!stored.deletion.absent_members.includes(seat.role_name))
|
|
721
|
+
stored.deletion.absent_members.push(seat.role_name);
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
721
724
|
const proof = proofs.find(value => value.name === seat.role_name);
|
|
722
725
|
if (proof && proof.launch_id === evidence.launch_id && proof.archive_path === evidence.archive_path
|
|
723
726
|
&& proof.launch_id === seat.launch?.launch_id && proof.action_id === seat.launch?.action_id) {
|
|
@@ -761,7 +764,7 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
761
764
|
export function beginTaskWorkspaceCleanup(id) {
|
|
762
765
|
withTaskLock(id, () => {
|
|
763
766
|
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
764
|
-
if (stored.deletion?.status !== 'pending'
|
|
767
|
+
if (stored.deletion?.status !== 'pending'
|
|
765
768
|
|| stored.deletion.members.some(member => member.phase !== 'identity_absent'))
|
|
766
769
|
throw new TaskStateError('Task is not ready for workspace cleanup');
|
|
767
770
|
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
@@ -798,3 +801,42 @@ export function unlinkDeletedTask(id) {
|
|
|
798
801
|
return true;
|
|
799
802
|
});
|
|
800
803
|
}
|
|
804
|
+
/** Called only after current temp and exact identity absence have been checked. */
|
|
805
|
+
export function settleAbsentTaskDeletionMember(id, name) {
|
|
806
|
+
withTaskLock(id, () => {
|
|
807
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
808
|
+
if (stored.deletion?.status !== 'pending')
|
|
809
|
+
throw new TaskStateError('Task is not deleting');
|
|
810
|
+
const member = stored.deletion.members.find(value => value.name === name);
|
|
811
|
+
if (!member)
|
|
812
|
+
throw new TaskStateError('Unknown deletion member');
|
|
813
|
+
member.phase = 'identity_absent';
|
|
814
|
+
member.launch_id ??= DELETION_MEMBER_ABSENT_VERIFIED;
|
|
815
|
+
member.updated_at = new Date().toISOString();
|
|
816
|
+
writeTask(stored);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
/** Room-only erasure keeps the parent Task but removes its dead room/member links. */
|
|
820
|
+
export function detachDeletedRoom(taskId, roomId) {
|
|
821
|
+
withTaskLock(taskId, () => {
|
|
822
|
+
if (!existsSync(taskPath(taskId)))
|
|
823
|
+
return;
|
|
824
|
+
const stored = JSON.parse(readFileSync(taskPath(taskId), 'utf8'));
|
|
825
|
+
if (stored.room_id !== roomId || stored.deletion?.status === 'pending')
|
|
826
|
+
return;
|
|
827
|
+
delete stored.room_id;
|
|
828
|
+
delete stored.room_identity_cid;
|
|
829
|
+
stored.member_roles = [];
|
|
830
|
+
writeTask(stored);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
/** A completed room may consume its archives while other task members still retire. */
|
|
834
|
+
export function beginTaskArchiveCleanup(id) {
|
|
835
|
+
withTaskLock(id, () => {
|
|
836
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
837
|
+
if (stored.deletion?.status !== 'pending')
|
|
838
|
+
throw new TaskStateError('Task is not deleting');
|
|
839
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
840
|
+
writeTask(stored);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
@@ -88,6 +88,7 @@ export interface TaskDeletionIntent {
|
|
|
88
88
|
/** Snapshot of managed members at acceptance; the missing-room retirement evidence. */
|
|
89
89
|
members: TaskDeletionMemberCursor[];
|
|
90
90
|
archived_absences?: ArchivedMemberAbsence[];
|
|
91
|
+
absent_members?: string[];
|
|
91
92
|
/** Archive proofs were verified before owned workspace cleanup may consume them. */
|
|
92
93
|
workspace_cleanup_started_at?: string;
|
|
93
94
|
error?: string;
|
|
@@ -212,6 +213,7 @@ export type RoomHistoryEvidence = {
|
|
|
212
213
|
};
|
|
213
214
|
export type MemberRetirementPhase = 'stop_requested' | 'liveness_absent' | 'archive_secured' | 'identity_absent';
|
|
214
215
|
export interface MemberRetirement {
|
|
216
|
+
absence_verified?: boolean;
|
|
215
217
|
phase: MemberRetirementPhase;
|
|
216
218
|
launch_id: string;
|
|
217
219
|
updated_at: string;
|
package/dist/temp-lifecycle.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export declare function makeTempSupervisorLauncher(options?: {
|
|
|
44
44
|
}): SupervisorLauncher;
|
|
45
45
|
export declare function markTempSupervisorActive(dir: string, pid?: number): Promise<void>;
|
|
46
46
|
export declare function requestedTempStopReason(dir: string): 'operator-stop' | undefined;
|
|
47
|
+
/** Explicit erasure removes only exact retired role/launch events. */
|
|
48
|
+
export declare function eraseTerminationEvents(launches: ReadonlyArray<{
|
|
49
|
+
role: string;
|
|
50
|
+
launchId: string;
|
|
51
|
+
}>): void;
|
|
47
52
|
/** Move retired state out of the live roster without deleting any evidence. */
|
|
48
53
|
export declare function archiveTempState(role: string, reason: TempTerminationReason, outcome: TempTerminationRecord['outcome'], detail: string, now?: Date): string | undefined;
|
|
49
54
|
/** Resolve only an exact role+launch archive; names and timestamps are never proof. */
|
package/dist/temp-lifecycle.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
|
-
import { replaceFileAtomically, withFileLock } from './atomic-file.js';
|
|
4
|
+
import { replaceFileAtomically, withFileLock, withSynchronousFileLock } from './atomic-file.js';
|
|
5
5
|
import { realExec } from './exec.js';
|
|
6
6
|
import { stateRoot, tmpRoot } from './paths.js';
|
|
7
7
|
export const TEMP_SUPERVISOR_FILE = '.temp-supervisor.json';
|
|
@@ -133,18 +133,34 @@ function appendTermination(dir, record) {
|
|
|
133
133
|
replaceFileAtomically(join(dir, TEMP_GLOBAL_TERMINATION_MARKER), line);
|
|
134
134
|
}
|
|
135
135
|
function appendGlobalTermination(line, checkExisting = false) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
withSynchronousFileLock(join(stateRoot(), 'locks', 'termination-journal'), () => {
|
|
137
|
+
mkdirSync(archiveRoot(), { recursive: true, mode: 0o700 });
|
|
138
|
+
const path = join(archiveRoot(), 'terminations.jsonl');
|
|
139
|
+
// Recovery may revisit a .retiring directory after a crash between the
|
|
140
|
+
// global append and final rename. Avoid duplicating that exact event.
|
|
141
|
+
if (checkExisting) {
|
|
142
|
+
try {
|
|
143
|
+
if (readFileSync(path, 'utf8').split('\n').includes(line.trimEnd()))
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
catch { /* the journal does not exist yet */ }
|
|
144
147
|
}
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
+
appendFileSync(path, line, { mode: 0o600 });
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Explicit erasure removes only exact retired role/launch events. */
|
|
152
|
+
export function eraseTerminationEvents(launches) {
|
|
153
|
+
withSynchronousFileLock(join(stateRoot(), 'locks', 'termination-journal'), () => {
|
|
154
|
+
const path = join(archiveRoot(), 'terminations.jsonl');
|
|
155
|
+
if (!existsSync(path))
|
|
156
|
+
return;
|
|
157
|
+
const lines = readFileSync(path, 'utf8').split('\n').filter(Boolean);
|
|
158
|
+
const kept = lines.filter(line => {
|
|
159
|
+
const row = JSON.parse(line);
|
|
160
|
+
return !launches.some(launch => launch.role === row.role && launch.launchId === row.launchId);
|
|
161
|
+
});
|
|
162
|
+
replaceFileAtomically(path, kept.length ? kept.join('\n') + '\n' : '');
|
|
163
|
+
});
|
|
148
164
|
}
|
|
149
165
|
/** Pick a sibling path without overwriting evidence from an earlier attempt. */
|
|
150
166
|
function collisionSafeArchivePaths(targetBase, retiringBase) {
|
package/docs/task-workspaces.md
CHANGED
|
@@ -52,8 +52,21 @@ matching archived launches, including failed and replaced attempts, using the
|
|
|
52
52
|
workspace descriptor plus role and termination/launch evidence. It preflights
|
|
53
53
|
these archives and atomically transfers them to `.fleet-retired-agents` in the
|
|
54
54
|
workspace before removal. Unrelated archives are not adopted by a name prefix.
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
Explicit deletion also erases legacy archives with exact launch/action or room
|
|
56
|
+
provenance, matching private supervisor journals and admission/launch descriptors,
|
|
57
|
+
and the exact retired-launch rows in the global termination journal. It never
|
|
58
|
+
infers ownership from an Agent's shared `cwd`. Artifact removal uses a durable
|
|
59
|
+
manifest, ownership fingerprints and per-entry rename/removal checkpoints, so
|
|
60
|
+
an interrupted deletion can resume without deleting a replacement at the old path.
|
|
61
|
+
Task deletion acceptance receipts exist only during cleanup and are erased after
|
|
62
|
+
settlement (including retry after a crash between Task unlink and receipt cleanup).
|
|
63
|
+
Shared Fleet command audit and lifecycle delivery ledgers have exactly attributed
|
|
64
|
+
target labels, configuration and command arguments removed; unrelated entries are
|
|
65
|
+
preserved. Delivery/deduplication metadata and a content-free erased-resource ID
|
|
66
|
+
registry remain so a running writer cannot resurrect erased content. Infrastructure
|
|
67
|
+
logs without structured ownership, backups, and copies already delivered to other
|
|
68
|
+
identities are external retention boundaries; this is not a claim to erase every
|
|
69
|
+
external copy.
|
|
57
70
|
|
|
58
71
|
Provisioning serializes Task operation then Room close locks, including bounded
|
|
59
72
|
seat waits, so concurrent retries cannot publish competing members and deletion
|
|
@@ -96,3 +109,22 @@ remote result; this change deduplicates locally recorded rooms and does not clai
|
|
|
96
109
|
to solve that remote transaction window. Inspect remote room ownership before
|
|
97
110
|
retrying ambiguous remote creation. No services or installed Fleet code are
|
|
98
111
|
changed by building or testing this feature.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Broken provisioning and deletion recovery
|
|
115
|
+
|
|
116
|
+
Deletion accepts every lifecycle state. Missing temporary directories, identities,
|
|
117
|
+
remote rooms and already-erased artifacts are settled outcomes. Retry the same
|
|
118
|
+
explicit delete after an interruption. A missing local member CID is recovered
|
|
119
|
+
only from a matching authenticated seat in the pinned room (exact invite and
|
|
120
|
+
role, uniquely matched), or a supervisor journal tied to the selected daemon,
|
|
121
|
+
temporary instance and recorded creation action. These sources must agree.
|
|
122
|
+
The recovered CID is persisted before retirement consumes the evidence.
|
|
123
|
+
|
|
124
|
+
A contradictory CID, replacement launch, ambiguous seat, unreadable/corrupt
|
|
125
|
+
record, or unreachable authority is not evidence of absence. Cleanup retains its
|
|
126
|
+
cursor and reports the conflict instead of deleting an unrelated resource or
|
|
127
|
+
claiming complete erasure. An identity with no recoverable authenticated binding
|
|
128
|
+
requires restoring its actual ownership evidence; a name-only force removal is
|
|
129
|
+
not supported. Room deletion removes the linked Task's dead room/member links and preserves its workspace; use explicit
|
|
130
|
+
Task deletion when the request includes the associated Task and its artifacts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "1.2.0-nightly.
|
|
3
|
+
"version": "1.2.0-nightly.14",
|
|
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",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Predefined Brains
|
|
2
|
+
|
|
3
|
+
`brain-catalog.json` is the source for generated `fleet/brains/*.yaml` files.
|
|
4
|
+
Run `node scripts/generate-brain-presets.mjs` after changing it, and run the same
|
|
5
|
+
command with `--check` to verify generated files. An empty `efforts` array generates
|
|
6
|
+
one `-default` preset with no `effort` field. Bootstrap adds missing presets and
|
|
7
|
+
preserves existing user files. Existing model presets remain available.
|
|
8
|
+
|
|
9
|
+
## Current model additions (verified 2026-09-24)
|
|
10
|
+
|
|
11
|
+
| Harness | Exact model ID | Preset efforts |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Codex | `gpt-6-sol` | low, medium, high, xhigh, max, ultra |
|
|
14
|
+
| Codex | `gpt-6-luna` | low, medium, high, xhigh, max |
|
|
15
|
+
| Claude Code | `claude-fable-5-1` | low, medium, high, xhigh, max |
|
|
16
|
+
| Claude Code | `claude-opus-5-5` | low, medium, high, xhigh, max |
|
|
17
|
+
| Claude Code (retained) | `claude-sonnet-5` | low, medium, high, xhigh, max |
|
|
18
|
+
| Claude Code | `claude-haiku-4-5-20251001` | default, with no effort setting |
|
|
19
|
+
|
|
20
|
+
For example, select `codex-gpt-6-sol-ultra`, `claude-opus-5-5-max`, or
|
|
21
|
+
`claude-haiku-4-5-20251001-default` as a Brain reference. The init wizard offers
|
|
22
|
+
models supporting its Quick/Balanced/Thorough (low/medium/high) choices; use the
|
|
23
|
+
named Brain directly for Haiku, which does not support effort.
|
|
24
|
+
|
|
25
|
+
### Runtime requirements and evidence
|
|
26
|
+
|
|
27
|
+
Codex CLI **0.155.0** freshly queried through `app-server` `initialize` then
|
|
28
|
+
`model/list` advertised the GPT rows above. A previously populated
|
|
29
|
+
`~/.codex/models_cache.json` did not contain these models; refresh runtime discovery
|
|
30
|
+
before concluding a model is unavailable. `ours-fleet doctor` compares presets to
|
|
31
|
+
the local cache. The catalog is not an account entitlement guarantee.
|
|
32
|
+
|
|
33
|
+
The [Sol](https://developers.openai.com/api/docs/models/gpt-6-sol) and
|
|
34
|
+
[Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) API model pages
|
|
35
|
+
also support `none`, but the verified Codex runtime does not advertise it.
|
|
36
|
+
Consequently there are no `none` presets. Sol's `ultra` is an advertised Codex
|
|
37
|
+
harness mode, not an additional API reasoning level; Luna does not advertise it.
|
|
38
|
+
|
|
39
|
+
Claude Code **2.1.281** was verified using the Agent SDK `supportedModels()`
|
|
40
|
+
control request, with `pathToClaudeCodeExecutable` pointing to the installed
|
|
41
|
+
Claude CLI, without sending an inference prompt. It reports Fable 5.1, Opus 5.5,
|
|
42
|
+
Sonnet 5 and Haiku 4.5 with the capabilities above. These are verified versions,
|
|
43
|
+
not asserted minimum versions. Provider references:
|
|
44
|
+
[model IDs](https://platform.claude.com/docs/en/models/overview) and
|
|
45
|
+
[Claude Code effort support](https://code.claude.com/docs/en/model-config#adjust-effort-level).
|
|
46
|
+
|
|
47
|
+
**Check the executable used by ACP before selecting the new Claude presets.**
|
|
48
|
+
The inspected `claude-agent-acp` **0.63.0** installation bundles Agent SDK
|
|
49
|
+
**0.3.220**, whose default executable reports older models. To use the verified
|
|
50
|
+
installed Claude CLI, set `CLAUDE_CODE_EXECUTABLE` to its absolute path in the
|
|
51
|
+
launch environment (for example `/usr/bin/claude` on the verification host).
|
|
52
|
+
Otherwise use a bundled SDK whose runtime advertises these models and efforts.
|
|
53
|
+
The presets do not change your executable or restart agents. An older bundled
|
|
54
|
+
runtime alone is not evidence of support for the new models.
|
|
55
|
+
|
|
56
|
+
No-prompt ACP startup and `session/set_config_option` probes also verified all
|
|
57
|
+
new GPT and Claude effort combinations, including Sol `ultra` and Claude `max`;
|
|
58
|
+
Haiku startup advertises no effort option. The probes used Codex ACP 1.10.0
|
|
59
|
+
with the installed Codex executable and Claude ACP 0.63.0 with the native override.
|
|
60
|
+
|
|
61
|
+
ACP builds effort options from the selected model's `supportedEffortLevels` and
|
|
62
|
+
applies the selection through the SDK's session flag settings. This supports
|
|
63
|
+
`max` without writing it to persistent `effortLevel` settings. Fleet verifies
|
|
64
|
+
that ACP applied the requested effort and fails on rejection. `ultracode` is a
|
|
65
|
+
separate Claude orchestration setting and is not a model effort preset. Account
|
|
66
|
+
and organization restrictions still apply at launch; no fallback model is added.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"catalog_revision":
|
|
3
|
+
"catalog_revision": 3,
|
|
4
4
|
"models": [
|
|
5
5
|
{ "harness": "codex", "session": "acp", "model": "gpt-6-astra", "efforts": ["low", "medium", "high", "xhigh", "max", "ultra"] },
|
|
6
6
|
{ "harness": "codex", "session": "acp", "model": "gpt-5.6-sol", "efforts": ["low", "medium", "high", "xhigh", "max", "ultra"] },
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
{ "harness": "codex", "session": "acp", "model": "gpt-5.3-codex-spark", "efforts": ["low", "medium", "high", "xhigh"] },
|
|
13
13
|
{ "harness": "claude-code", "session": "acp", "model": "claude-fable-5", "efforts": ["low", "medium", "high", "xhigh", "max"] },
|
|
14
14
|
{ "harness": "claude-code", "session": "acp", "model": "claude-opus-5", "efforts": ["low", "medium", "high", "xhigh", "max"] },
|
|
15
|
-
{ "harness": "claude-code", "session": "acp", "model": "claude-sonnet-5", "efforts": ["low", "medium", "high", "xhigh", "max"] }
|
|
15
|
+
{ "harness": "claude-code", "session": "acp", "model": "claude-sonnet-5", "efforts": ["low", "medium", "high", "xhigh", "max"] },
|
|
16
|
+
{ "harness": "codex", "session": "acp", "model": "gpt-6-sol", "efforts": ["low", "medium", "high", "xhigh", "max", "ultra"] },
|
|
17
|
+
{ "harness": "codex", "session": "acp", "model": "gpt-6-luna", "efforts": ["low", "medium", "high", "xhigh", "max"] },
|
|
18
|
+
{ "harness": "claude-code", "session": "acp", "model": "claude-fable-5-1", "efforts": ["low", "medium", "high", "xhigh", "max"] },
|
|
19
|
+
{ "harness": "claude-code", "session": "acp", "model": "claude-opus-5-5", "efforts": ["low", "medium", "high", "xhigh", "max"] },
|
|
20
|
+
{ "harness": "claude-code", "session": "acp", "model": "claude-haiku-4-5-20251001", "efforts": [] }
|
|
16
21
|
]
|
|
17
22
|
}
|