@ours.network/fleet 1.0.1 → 1.0.2
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 +14 -7
- package/dist/briefing.d.ts +2 -1
- package/dist/briefing.js +47 -86
- package/dist/build-info.json +4 -4
- package/dist/config.d.ts +8 -6
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +18 -6
- package/dist/harness/claude-code.js +1 -1
- package/dist/harness/types.d.ts +3 -3
- package/dist/rooms-tasks/cli.js +8 -19
- package/dist/rooms-tasks/close.js +3 -0
- package/dist/rooms-tasks/cowork-adapter.d.ts +3 -0
- package/dist/rooms-tasks/cowork-adapter.js +3 -1
- package/dist/rooms-tasks/member-startup.d.ts +5 -38
- package/dist/rooms-tasks/member-startup.js +3 -119
- package/dist/rooms-tasks/provision.js +194 -409
- package/dist/rooms-tasks/room-state.d.ts +1 -2
- package/dist/rooms-tasks/room-state.js +0 -9
- package/dist/rooms-tasks/types.d.ts +4 -20
- package/dist/session/acp.d.ts +4 -11
- package/dist/session/acp.js +7 -12
- package/dist/spawn.d.ts +3 -3
- package/dist/spawn.js +6 -14
- package/package.json +1 -1
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -266,12 +266,8 @@ function roomStartupSections(room) {
|
|
|
266
266
|
heading: 'Fleet startup evidence',
|
|
267
267
|
markdownItems: room.member_seats.map(seat => {
|
|
268
268
|
const launch = seat.launch?.state ?? 'unrecorded';
|
|
269
|
-
const briefing = seat.briefing?.state ?? 'unrecorded';
|
|
270
269
|
return `${markdownCode(seat.role_name)} — ${markdownProse(seat.cowork_role)} — `
|
|
271
|
-
+ `seat ${markdownProse(seat.seat_state)}, launch ${markdownProse(launch)}
|
|
272
|
-
+ (seat.briefing?.acknowledgement_message_id ? ' — acknowledgement recorded' : '')
|
|
273
|
-
+ (seat.briefing?.last_rejected_ack_reason
|
|
274
|
-
? ' — rejected acknowledgement recorded; inspect role logs' : '')
|
|
270
|
+
+ `seat ${markdownProse(seat.seat_state)}, launch ${markdownProse(launch)}`
|
|
275
271
|
+ (seat.launch?.error ? ' — launch failure recorded; inspect role logs' : '');
|
|
276
272
|
}),
|
|
277
273
|
});
|
|
@@ -813,12 +809,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
813
809
|
result.recovery_actions.push('Owner CID mismatch — verify rooms.owner.expected_cid matches Messenger identity');
|
|
814
810
|
if (room.provisioning_detail === 'member_failed')
|
|
815
811
|
result.recovery_actions.push(`Member creation failed at saga step ${room.saga.step_index} — inspect and retry`);
|
|
816
|
-
if (room.provisioning_detail === '
|
|
817
|
-
result.recovery_actions.push('Members
|
|
818
|
-
|
|
819
|
-
result.recovery_actions.push('Cowork briefing relay failed terminally — replace the seat or use a Cowork-supported redelivery');
|
|
820
|
-
const resumable = ['create_members', 'configure_briefings', 'join_role_groups', 'wait_seats',
|
|
821
|
-
'launch_work', 'wait_briefing_acks', 'activate'];
|
|
812
|
+
if (room.provisioning_detail === 'waiting_seats')
|
|
813
|
+
result.recovery_actions.push('Members have not accepted their one-time room invites yet — inspect role logs, then retry recovery');
|
|
814
|
+
const resumable = ['create_members', 'join_role_groups', 'wait_seats', 'launch_work', 'activate'];
|
|
822
815
|
if (resumable.includes(room.saga.phase)) {
|
|
823
816
|
const template = durableTaskRoomTemplate(t, room);
|
|
824
817
|
if (template) {
|
|
@@ -998,8 +991,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
998
991
|
// The task already has a room mid-provisioning (e.g. seats were still
|
|
999
992
|
// pending on the last run). Resume it exactly as `task recover` does.
|
|
1000
993
|
const room = getRoomRecord(t.room_id);
|
|
1001
|
-
const resumable = ['create_members', '
|
|
1002
|
-
'launch_work', 'wait_briefing_acks', 'activate'];
|
|
994
|
+
const resumable = ['create_members', 'join_role_groups', 'wait_seats', 'launch_work', 'activate'];
|
|
1003
995
|
if (room && resumable.includes(room.saga.phase)) {
|
|
1004
996
|
try {
|
|
1005
997
|
await provisionMembers({
|
|
@@ -1397,13 +1389,10 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1397
1389
|
actions.push('Check ours-cowork service status');
|
|
1398
1390
|
if (r?.provisioning_detail === 'waiting_owner_invite')
|
|
1399
1391
|
actions.push('Rotate rooms.owner.public_invite in config, then re-run recover');
|
|
1400
|
-
if (r?.provisioning_detail === '
|
|
1401
|
-
actions.push('Inspect
|
|
1402
|
-
if (r?.provisioning_detail === 'briefing_delivery_failed')
|
|
1403
|
-
actions.push('Replace the failed seat or use a Cowork-supported briefing redelivery');
|
|
1392
|
+
if (r?.provisioning_detail === 'waiting_seats')
|
|
1393
|
+
actions.push('Inspect temporary member logs for invite acceptance, then re-run recover');
|
|
1404
1394
|
if (r && r.state === 'provisioning') {
|
|
1405
|
-
const resumable = ['create_members', '
|
|
1406
|
-
'launch_work', 'wait_briefing_acks', 'activate'];
|
|
1395
|
+
const resumable = ['create_members', 'join_role_groups', 'wait_seats', 'launch_work', 'activate'];
|
|
1407
1396
|
if (resumable.includes(r.saga.phase) && r.template_snapshot) {
|
|
1408
1397
|
try {
|
|
1409
1398
|
const template = r.task_id
|
|
@@ -73,6 +73,9 @@ async function removeExactMemberIdentity(seat) {
|
|
|
73
73
|
const before = listedIdentity(await client.listIdentities(), seat.role_name);
|
|
74
74
|
if (!before)
|
|
75
75
|
return;
|
|
76
|
+
if (!seat.identity_cid) {
|
|
77
|
+
throw new Error(`room member '${seat.role_name}' exists without a recorded authenticated CID; refusing removal`);
|
|
78
|
+
}
|
|
76
79
|
if (before.cid?.toLowerCase() !== seat.identity_cid.toLowerCase()) {
|
|
77
80
|
throw new Error(`room member '${seat.role_name}' identity CID mismatch: recorded ${seat.identity_cid}, found ${before.cid ?? 'none'}`);
|
|
78
81
|
}
|
|
@@ -22,6 +22,8 @@ export interface CoworkInviteResult {
|
|
|
22
22
|
}
|
|
23
23
|
export interface CoworkSeatInfo {
|
|
24
24
|
identity_cid: string;
|
|
25
|
+
display_name: string;
|
|
26
|
+
invite_id: string;
|
|
25
27
|
role: string;
|
|
26
28
|
seat_state: 'pending' | 'active' | 'removed';
|
|
27
29
|
}
|
|
@@ -61,6 +63,7 @@ export interface CoworkAdapter {
|
|
|
61
63
|
expected_cid: string;
|
|
62
64
|
}): Promise<CoworkInviteAcceptResult>;
|
|
63
65
|
issueInvite(roomId: string, opts: {
|
|
66
|
+
mode?: 'one_time' | 'public';
|
|
64
67
|
role: string;
|
|
65
68
|
min_accepts: number;
|
|
66
69
|
}): Promise<CoworkInviteResult>;
|
|
@@ -56,6 +56,8 @@ function projectSeat(value, operation) {
|
|
|
56
56
|
const seat = object(value, operation, 'seat');
|
|
57
57
|
return {
|
|
58
58
|
identity_cid: string(seat.identity, operation, 'seat.identity'),
|
|
59
|
+
display_name: string(seat.display_name, operation, 'seat.display_name'),
|
|
60
|
+
invite_id: string(seat.invite_id, operation, 'seat.invite_id'),
|
|
59
61
|
role: string(seat.role, operation, 'seat.role'),
|
|
60
62
|
seat_state: seatState(seat.state, operation),
|
|
61
63
|
};
|
|
@@ -311,7 +313,7 @@ export function createCoworkAdapter(options = {}) {
|
|
|
311
313
|
async issueInvite(roomId, opts) {
|
|
312
314
|
const receipt = object(await call('room.invite', {
|
|
313
315
|
room_id: roomId,
|
|
314
|
-
mode: 'public',
|
|
316
|
+
mode: opts.mode ?? 'public',
|
|
315
317
|
role: opts.role,
|
|
316
318
|
min_accepts: opts.min_accepts,
|
|
317
319
|
}), 'room.invite', 'receipt');
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { RoomHistoryEvidence, RoomMemberBriefingState } from './types.js';
|
|
2
1
|
export declare const sha256Text: (text: string) => string;
|
|
3
|
-
export interface
|
|
2
|
+
export interface RoomTaskMember {
|
|
4
3
|
role_name: string;
|
|
5
4
|
cowork_role: string;
|
|
6
|
-
identity_cid: string;
|
|
7
5
|
persona?: string;
|
|
8
6
|
}
|
|
9
|
-
|
|
7
|
+
/** Build the complete task text Fleet places directly in a temporary member's briefing. */
|
|
8
|
+
export declare function buildRoomMemberTask(input: {
|
|
10
9
|
taskId?: string;
|
|
11
10
|
roomId: string;
|
|
12
11
|
roomIdentityCid: string;
|
|
@@ -14,38 +13,6 @@ export declare function buildRoomMemberCharter(input: {
|
|
|
14
13
|
goal?: string;
|
|
15
14
|
brief?: string;
|
|
16
15
|
contract?: string;
|
|
17
|
-
member:
|
|
18
|
-
roster:
|
|
16
|
+
member: RoomTaskMember;
|
|
17
|
+
roster: RoomTaskMember[];
|
|
19
18
|
}): string;
|
|
20
|
-
export interface RoomBriefingAck {
|
|
21
|
-
kind: 'fleet_room_briefing_ack';
|
|
22
|
-
schema_version: 1;
|
|
23
|
-
room_id: string;
|
|
24
|
-
room_identity_cid: string;
|
|
25
|
-
briefing_role: string;
|
|
26
|
-
briefing_version: number;
|
|
27
|
-
briefing_sha256: string;
|
|
28
|
-
briefing_message_id: string;
|
|
29
|
-
owner_seat_cid: string | null;
|
|
30
|
-
accepted: true;
|
|
31
|
-
applied: true;
|
|
32
|
-
profile_applied: true;
|
|
33
|
-
}
|
|
34
|
-
export declare function parseRoomBriefingAck(text: string): RoomBriefingAck | undefined;
|
|
35
|
-
export interface ExpectedBriefingAck {
|
|
36
|
-
roomId: string;
|
|
37
|
-
roomIdentityCid: string;
|
|
38
|
-
ownerSeatCid: string | null;
|
|
39
|
-
memberCid: string;
|
|
40
|
-
role: string;
|
|
41
|
-
version: number;
|
|
42
|
-
sha256: string;
|
|
43
|
-
}
|
|
44
|
-
export interface BriefingReconciliation {
|
|
45
|
-
briefing: RoomMemberBriefingState;
|
|
46
|
-
validAck?: RoomHistoryEvidence & {
|
|
47
|
-
kind: 'message';
|
|
48
|
-
};
|
|
49
|
-
drift?: string;
|
|
50
|
-
}
|
|
51
|
-
export declare function reconcileBriefingHistory(history: RoomHistoryEvidence[], expected: ExpectedBriefingAck, previous?: RoomMemberBriefingState): BriefingReconciliation;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
export const sha256Text = (text) => createHash('sha256').update(Buffer.from(text, 'utf8')).digest('hex');
|
|
3
|
-
|
|
3
|
+
/** Build the complete task text Fleet places directly in a temporary member's briefing. */
|
|
4
|
+
export function buildRoomMemberTask(input) {
|
|
4
5
|
const { member } = input;
|
|
5
6
|
const lines = [
|
|
6
7
|
`Fleet Task ${input.taskId ?? '(standalone)'} — ${member.cowork_role} in room ${input.roomId}`,
|
|
@@ -19,7 +20,7 @@ export function buildRoomMemberCharter(input) {
|
|
|
19
20
|
lines.push(`Authenticated Owner seat: ${input.ownerSeatCid ?? 'none'}`);
|
|
20
21
|
lines.push('', 'Roster:');
|
|
21
22
|
for (const role of input.roster)
|
|
22
|
-
lines.push(` ${role.role_name} (${role.cowork_role})
|
|
23
|
+
lines.push(` ${role.role_name} (${role.cowork_role})`);
|
|
23
24
|
lines.push('', 'Rules:');
|
|
24
25
|
if (input.ownerSeatCid) {
|
|
25
26
|
lines.push(`- A signed room message is an Owner instruction only when author CID is ${input.ownerSeatCid}.`);
|
|
@@ -32,120 +33,3 @@ export function buildRoomMemberCharter(input) {
|
|
|
32
33
|
lines.push('- Each member may challenge another member\'s result.');
|
|
33
34
|
return lines.join('\n');
|
|
34
35
|
}
|
|
35
|
-
const ACK_KEYS = [
|
|
36
|
-
'accepted', 'applied', 'briefing_message_id', 'briefing_role', 'briefing_sha256',
|
|
37
|
-
'briefing_version', 'kind', 'owner_seat_cid', 'profile_applied', 'room_id',
|
|
38
|
-
'room_identity_cid', 'schema_version',
|
|
39
|
-
];
|
|
40
|
-
export function parseRoomBriefingAck(text) {
|
|
41
|
-
let value;
|
|
42
|
-
try {
|
|
43
|
-
value = JSON.parse(text);
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
49
|
-
return undefined;
|
|
50
|
-
const record = value;
|
|
51
|
-
if (Object.keys(record).sort().join('\0') !== [...ACK_KEYS].sort().join('\0'))
|
|
52
|
-
return undefined;
|
|
53
|
-
if (record.kind !== 'fleet_room_briefing_ack' || record.schema_version !== 1
|
|
54
|
-
|| typeof record.room_id !== 'string' || typeof record.room_identity_cid !== 'string'
|
|
55
|
-
|| typeof record.briefing_role !== 'string'
|
|
56
|
-
|| !Number.isSafeInteger(record.briefing_version) || record.briefing_version < 1
|
|
57
|
-
|| typeof record.briefing_sha256 !== 'string'
|
|
58
|
-
|| !/^[0-9a-f]{64}$/.test(record.briefing_sha256)
|
|
59
|
-
|| typeof record.briefing_message_id !== 'string'
|
|
60
|
-
|| (record.owner_seat_cid !== null && typeof record.owner_seat_cid !== 'string')
|
|
61
|
-
|| record.accepted !== true || record.applied !== true || record.profile_applied !== true) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
return record;
|
|
65
|
-
}
|
|
66
|
-
function ackMismatch(ack, expected, messageId) {
|
|
67
|
-
if (ack.room_id !== expected.roomId)
|
|
68
|
-
return 'room_id mismatch';
|
|
69
|
-
if (ack.room_identity_cid !== expected.roomIdentityCid)
|
|
70
|
-
return 'room_identity_cid mismatch';
|
|
71
|
-
if (ack.owner_seat_cid !== expected.ownerSeatCid)
|
|
72
|
-
return 'owner_seat_cid mismatch';
|
|
73
|
-
if (ack.briefing_role !== expected.role)
|
|
74
|
-
return 'briefing_role mismatch';
|
|
75
|
-
if (ack.briefing_version !== expected.version)
|
|
76
|
-
return 'briefing_version mismatch';
|
|
77
|
-
if (ack.briefing_sha256 !== expected.sha256)
|
|
78
|
-
return 'briefing_sha256 mismatch';
|
|
79
|
-
if (ack.briefing_message_id !== messageId)
|
|
80
|
-
return 'briefing_message_id mismatch';
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
export function reconcileBriefingHistory(history, expected, previous) {
|
|
84
|
-
const state = previous ? { ...previous } : {
|
|
85
|
-
role: expected.role, state: 'pending', rejected_ack_count: 0,
|
|
86
|
-
};
|
|
87
|
-
state.checked_at = new Date().toISOString();
|
|
88
|
-
let drift;
|
|
89
|
-
let validAck;
|
|
90
|
-
const records = [...history]
|
|
91
|
-
.filter(record => record.seq > (previous?.last_processed_seq ?? 0))
|
|
92
|
-
.sort((a, b) => a.seq - b.seq);
|
|
93
|
-
for (const record of records) {
|
|
94
|
-
state.last_processed_seq = Math.max(state.last_processed_seq ?? 0, record.seq);
|
|
95
|
-
if (record.kind === 'message' && record.category === 'role_briefing'
|
|
96
|
-
&& record.author.identity === expected.roomIdentityCid
|
|
97
|
-
&& record.briefing_role === expected.role
|
|
98
|
-
&& record.recipient_identities.includes(expected.memberCid)) {
|
|
99
|
-
const hash = sha256Text(record.text);
|
|
100
|
-
if ((record.briefing_version ?? 0) > expected.version
|
|
101
|
-
|| (record.briefing_version === expected.version && hash !== expected.sha256)) {
|
|
102
|
-
drift = `room role briefing drift at history seq ${record.seq}`;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (record.briefing_version === expected.version && hash === expected.sha256) {
|
|
106
|
-
if (state.message_id && state.message_id !== record.message_id)
|
|
107
|
-
drift = `multiple exact role briefing messages for ${expected.memberCid}`;
|
|
108
|
-
else
|
|
109
|
-
state.message_id = record.message_id;
|
|
110
|
-
}
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (record.kind === 'relay_intent' && state.message_id
|
|
114
|
-
&& record.message_id === state.message_id
|
|
115
|
-
&& record.recipient_identity === expected.memberCid) {
|
|
116
|
-
state.relay_intent_record_id = record.record_id;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (record.kind === 'relay_result' && state.message_id && state.relay_intent_record_id
|
|
120
|
-
&& record.intent_record_id === state.relay_intent_record_id
|
|
121
|
-
&& record.message_id === state.message_id
|
|
122
|
-
&& record.recipient_identity === expected.memberCid) {
|
|
123
|
-
state.relay_result_record_id = record.record_id;
|
|
124
|
-
state.relay_wire_id = record.wire_id;
|
|
125
|
-
state.state = record.status === 'queued' ? 'relay_queued' : 'relay_failed';
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (record.kind !== 'message' || record.category !== 'chat'
|
|
129
|
-
|| record.author.identity !== expected.memberCid
|
|
130
|
-
|| !record.text.includes('fleet_room_briefing_ack'))
|
|
131
|
-
continue;
|
|
132
|
-
const ack = parseRoomBriefingAck(record.text);
|
|
133
|
-
const mismatch = !ack ? 'invalid or non-canonical ACK JSON'
|
|
134
|
-
: !state.message_id ? 'no matching room role briefing'
|
|
135
|
-
: state.state !== 'relay_queued' ? 'briefing relay was not queued'
|
|
136
|
-
: ackMismatch(ack, expected, state.message_id);
|
|
137
|
-
if (mismatch) {
|
|
138
|
-
state.rejected_ack_count++;
|
|
139
|
-
state.last_rejected_ack_reason = mismatch;
|
|
140
|
-
state.last_rejected_ack_seq = record.seq;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
state.state = 'acknowledged';
|
|
144
|
-
state.acknowledged_at = record.at;
|
|
145
|
-
state.acknowledgement_message_id = record.message_id;
|
|
146
|
-
state.acknowledgement_seq = record.seq;
|
|
147
|
-
validAck ??= record;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return { briefing: state, validAck, drift };
|
|
151
|
-
}
|