@ours.network/fleet 0.19.0-nightly.1 → 0.19.0-nightly.11
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 +30 -0
- package/dist/briefing.js +92 -6
- package/dist/build-info.json +4 -4
- package/dist/cli.js +5 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +44 -3
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +28 -0
- package/dist/doctor.js +125 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex.js +2 -0
- package/dist/harness/types.d.ts +2 -0
- package/dist/owner-channel/channel.d.ts +7 -0
- package/dist/owner-channel/channel.js +47 -0
- package/dist/owner-channel/commands.d.ts +9 -0
- package/dist/owner-channel/commands.js +393 -0
- package/dist/rooms-tasks/cli.d.ts +4 -0
- package/dist/rooms-tasks/cli.js +1172 -0
- package/dist/rooms-tasks/close.d.ts +21 -0
- package/dist/rooms-tasks/close.js +159 -0
- package/dist/rooms-tasks/config.d.ts +15 -0
- package/dist/rooms-tasks/config.js +174 -0
- package/dist/rooms-tasks/cowork-adapter.d.ts +98 -0
- package/dist/rooms-tasks/cowork-adapter.js +385 -0
- package/dist/rooms-tasks/external-worker.d.ts +2 -0
- package/dist/rooms-tasks/external-worker.js +40 -0
- package/dist/rooms-tasks/index.d.ts +10 -0
- package/dist/rooms-tasks/index.js +10 -0
- package/dist/rooms-tasks/member-startup.d.ts +51 -0
- package/dist/rooms-tasks/member-startup.js +151 -0
- package/dist/rooms-tasks/provision.d.ts +29 -0
- package/dist/rooms-tasks/provision.js +514 -0
- package/dist/rooms-tasks/room-state.d.ts +34 -0
- package/dist/rooms-tasks/room-state.js +263 -0
- package/dist/rooms-tasks/task-state.d.ts +43 -0
- package/dist/rooms-tasks/task-state.js +277 -0
- package/dist/rooms-tasks/templates.d.ts +6 -0
- package/dist/rooms-tasks/templates.js +94 -0
- package/dist/rooms-tasks/terminal.d.ts +23 -0
- package/dist/rooms-tasks/terminal.js +53 -0
- package/dist/rooms-tasks/types.d.ts +260 -0
- package/dist/rooms-tasks/types.js +20 -0
- package/dist/spawn.d.ts +3 -1
- package/dist/spawn.js +1 -0
- package/dist/temp-lifecycle.d.ts +14 -2
- package/dist/temp-lifecycle.js +90 -0
- package/package.json +1 -1
package/dist/doctor.js
CHANGED
|
@@ -106,6 +106,126 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
106
106
|
ok: true,
|
|
107
107
|
detail: `warning: ${diagnostic.message}`,
|
|
108
108
|
});
|
|
109
|
+
// ── Rooms/tasks checks (§5.3) ────────────────────────────────────────
|
|
110
|
+
if (loaded.ok && loaded.rooms) {
|
|
111
|
+
const rooms = loaded.rooms;
|
|
112
|
+
// Cowork socket reachability
|
|
113
|
+
const coworkConfig = rooms.cowork?.config;
|
|
114
|
+
try {
|
|
115
|
+
const { createCoworkAdapter } = await import('./rooms-tasks/cowork-adapter.js');
|
|
116
|
+
const adapter = createCoworkAdapter({ configPath: coworkConfig });
|
|
117
|
+
const reachable = await adapter.available();
|
|
118
|
+
checks.push({
|
|
119
|
+
name: 'cowork', ok: reachable,
|
|
120
|
+
detail: reachable
|
|
121
|
+
? 'management socket reachable'
|
|
122
|
+
: `management socket unreachable${coworkConfig ? ` (config: ${coworkConfig})` : ''}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
checks.push({
|
|
127
|
+
name: 'cowork', ok: false,
|
|
128
|
+
detail: `management socket error${coworkConfig ? ` (config: ${coworkConfig})` : ''}: ${e?.message ?? e}`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Owner CID shape
|
|
132
|
+
const cidOk = /^[0-9a-fA-F]{64}$/.test(rooms.owner.expected_cid);
|
|
133
|
+
checks.push({
|
|
134
|
+
name: 'rooms: owner CID', ok: cidOk,
|
|
135
|
+
detail: cidOk ? 'valid 64-hex CID' : `invalid CID shape: ${rooms.owner.expected_cid.slice(0, 16)}...`,
|
|
136
|
+
});
|
|
137
|
+
// Invite presence (never content)
|
|
138
|
+
checks.push({
|
|
139
|
+
name: 'rooms: owner invite',
|
|
140
|
+
ok: !!loaded.ownerInviteFingerprint,
|
|
141
|
+
detail: loaded.ownerInviteFingerprint
|
|
142
|
+
? `present (fingerprint: ${loaded.ownerInviteFingerprint.slice(0, 12)}...)`
|
|
143
|
+
: 'not configured — room owner attachment will use waiting_owner_invite',
|
|
144
|
+
});
|
|
145
|
+
// Template validity and referenced role validity
|
|
146
|
+
const { listTemplates, resolveTemplate } = await import('./rooms-tasks/templates.js');
|
|
147
|
+
const customs = loaded.roomTemplates ?? {};
|
|
148
|
+
const templates = listTemplates(customs);
|
|
149
|
+
const roleNames = new Set(roles.map(r => r.name));
|
|
150
|
+
for (const t of templates) {
|
|
151
|
+
const badRefs = t.members
|
|
152
|
+
.filter(m => m.role_ref && !roleNames.has(m.role_ref))
|
|
153
|
+
.map(m => m.role_ref);
|
|
154
|
+
if (badRefs.length) {
|
|
155
|
+
checks.push({
|
|
156
|
+
name: `template: ${t.name}@${t.version}`,
|
|
157
|
+
ok: true,
|
|
158
|
+
detail: `warning: role_ref(s) ${badRefs.join(', ')} not found in configured roles`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Default template validity
|
|
163
|
+
const defaultTemplate = loaded.rooms.defaults?.template
|
|
164
|
+
?? loaded.tasks?.default_room_template;
|
|
165
|
+
if (defaultTemplate) {
|
|
166
|
+
const resolved = resolveTemplate(defaultTemplate, customs);
|
|
167
|
+
checks.push({
|
|
168
|
+
name: 'rooms: default template',
|
|
169
|
+
ok: !!resolved,
|
|
170
|
+
detail: resolved
|
|
171
|
+
? `${resolved.name}@${resolved.version}`
|
|
172
|
+
: `template '${defaultTemplate}' not found`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Shared-daemon selection coherence
|
|
176
|
+
if (rooms.cowork?.config) {
|
|
177
|
+
const { existsSync: exists } = await import('node:fs');
|
|
178
|
+
const configOk = exists(rooms.cowork.config);
|
|
179
|
+
checks.push({
|
|
180
|
+
name: 'rooms: cowork config', ok: configOk,
|
|
181
|
+
detail: configOk
|
|
182
|
+
? `cowork config at ${rooms.cowork.config}`
|
|
183
|
+
: `cowork config not found: ${rooms.cowork.config}`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// Hard prerelease capability check
|
|
187
|
+
try {
|
|
188
|
+
const { createCoworkAdapter } = await import('./rooms-tasks/cowork-adapter.js');
|
|
189
|
+
const adapter = createCoworkAdapter({ configPath: coworkConfig });
|
|
190
|
+
const roomList = await adapter.listRooms();
|
|
191
|
+
checks.push({
|
|
192
|
+
name: 'rooms: capability', ok: true,
|
|
193
|
+
detail: `cowork room management operational (${roomList.length} room(s))`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
const msg = e?.message ?? String(e);
|
|
198
|
+
const isProto = msg.includes('protocol') || msg.includes('invalid');
|
|
199
|
+
checks.push({
|
|
200
|
+
name: 'rooms: capability', ok: false,
|
|
201
|
+
detail: isProto
|
|
202
|
+
? `cowork does not support room management protocol — upgrade ours-cowork to prerelease`
|
|
203
|
+
: `room management check failed: ${msg}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Stale task/room warnings
|
|
207
|
+
try {
|
|
208
|
+
const { listTasks } = await import('./rooms-tasks/task-state.js');
|
|
209
|
+
const { listRoomRecords } = await import('./rooms-tasks/room-state.js');
|
|
210
|
+
const tasks = listTasks();
|
|
211
|
+
const roomRecords = listRoomRecords();
|
|
212
|
+
const roomIds = new Set(roomRecords.map(r => r.room_id));
|
|
213
|
+
const taskIds = new Set(tasks.map(t => t.task_id));
|
|
214
|
+
const orphanedTasks = tasks.filter(t => t.room_id && !roomIds.has(t.room_id));
|
|
215
|
+
const orphanedRooms = roomRecords.filter(r => r.task_id && !taskIds.has(r.task_id));
|
|
216
|
+
if (orphanedTasks.length)
|
|
217
|
+
checks.push({
|
|
218
|
+
name: 'rooms: stale tasks', ok: true,
|
|
219
|
+
detail: `warning: ${orphanedTasks.length} task(s) reference missing rooms: ${orphanedTasks.map(t => t.task_id).join(', ')}`,
|
|
220
|
+
});
|
|
221
|
+
if (orphanedRooms.length)
|
|
222
|
+
checks.push({
|
|
223
|
+
name: 'rooms: stale rooms', ok: true,
|
|
224
|
+
detail: `warning: ${orphanedRooms.length} room(s) reference missing tasks: ${orphanedRooms.map(r => r.room_id).join(', ')}`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch { /* state dir may not exist yet */ }
|
|
228
|
+
}
|
|
109
229
|
if (roles.length === 0 || roles.some(role => (role.session ?? 'tmux') === 'tmux')) {
|
|
110
230
|
const tmux = await exec('tmux', ['-V']);
|
|
111
231
|
checks.push({
|
|
@@ -405,7 +525,11 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
405
525
|
function loadConfigResult(configPath, yamlMode) {
|
|
406
526
|
try {
|
|
407
527
|
const cfg = loadConfig(configPath, { yamlMode });
|
|
408
|
-
return {
|
|
528
|
+
return {
|
|
529
|
+
ok: true, roles: cfg.roles, files: cfg.files, diagnostics: cfg.diagnostics,
|
|
530
|
+
rooms: cfg.rooms, roomTemplates: cfg.roomTemplates, tasks: cfg.tasks,
|
|
531
|
+
ownerInviteFingerprint: cfg.ownerInviteFingerprint,
|
|
532
|
+
};
|
|
409
533
|
}
|
|
410
534
|
catch (e) {
|
|
411
535
|
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
@@ -285,6 +285,8 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
285
285
|
currentIdentityTool: 'current_identity',
|
|
286
286
|
sendTool: 'send_message',
|
|
287
287
|
getMessagesTool: 'get_messages',
|
|
288
|
+
listHistoryTool: 'list_history',
|
|
289
|
+
getHistoryItemTool: 'get_history_item',
|
|
288
290
|
monitorInstruction: id => {
|
|
289
291
|
const m = armMonitor(id);
|
|
290
292
|
return `${m.charAt(0).toUpperCase()}${m.slice(1)}.`;
|
package/dist/harness/codex.js
CHANGED
|
@@ -411,6 +411,8 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
411
411
|
currentIdentityTool: 'current_identity',
|
|
412
412
|
sendTool: 'send_message',
|
|
413
413
|
getMessagesTool: 'get_messages',
|
|
414
|
+
listHistoryTool: 'list_history',
|
|
415
|
+
getHistoryItemTool: 'get_history_item',
|
|
414
416
|
monitorInstruction: (id, configuredRole) => {
|
|
415
417
|
const consented = configuredRole?.harness_options?.monitor === true;
|
|
416
418
|
const consent = consented
|
package/dist/harness/types.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface BriefingVocab {
|
|
|
67
67
|
currentIdentityTool: string;
|
|
68
68
|
sendTool: string;
|
|
69
69
|
getMessagesTool: string;
|
|
70
|
+
listHistoryTool: string;
|
|
71
|
+
getHistoryItemTool: string;
|
|
70
72
|
monitorInstruction(identity: string, role?: ResolvedRole): string;
|
|
71
73
|
/** Wake-source wording for a role whose monitor is supervisor-owned (monitor.mode=fleet). */
|
|
72
74
|
supervisedWakeNote(identity: string, role?: ResolvedRole): string;
|
|
@@ -217,6 +217,13 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
217
217
|
* CLI is asked to bounce this very process; neither can happen afterwards.
|
|
218
218
|
*/
|
|
219
219
|
private restartSelf;
|
|
220
|
+
/**
|
|
221
|
+
* The caller may itself be a room member. Persist and acknowledge acceptance
|
|
222
|
+
* before an external worker starts a saga that can retire this process.
|
|
223
|
+
*/
|
|
224
|
+
private closeRoomFromOwner;
|
|
225
|
+
/** Carry a task terminal request through a worker that survives this role. */
|
|
226
|
+
private terminalTaskFromOwner;
|
|
220
227
|
/** Code-point-safe tail of the worklog, or undefined when there is none. */
|
|
221
228
|
private readWorklogTail;
|
|
222
229
|
/** Bound harness-command output to a single outbound message. */
|
|
@@ -996,6 +996,8 @@ export class OwnerChannel {
|
|
|
996
996
|
return this.commentsState();
|
|
997
997
|
},
|
|
998
998
|
fleetList: () => this.fleetOps.list(),
|
|
999
|
+
closeRoom: roomId => this.closeRoomFromOwner(sender, roomId, wireId),
|
|
1000
|
+
terminalTask: (taskId, kind, outcome) => this.terminalTaskFromOwner(sender, taskId, kind, outcome, wireId),
|
|
999
1001
|
recentEvents: limit => this.options.session.eventsSince(0).slice(-limit),
|
|
1000
1002
|
readWorklogTail: maxChars => this.readWorklogTail(maxChars),
|
|
1001
1003
|
reply: async (replyText) => { await this.send(sender.id, replyText, wireId); },
|
|
@@ -1046,6 +1048,51 @@ export class OwnerChannel {
|
|
|
1046
1048
|
this.options.log(`[${this.options.role}] owner requested ${command}`);
|
|
1047
1049
|
await this.fleetOps.restart(mode);
|
|
1048
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* The caller may itself be a room member. Persist and acknowledge acceptance
|
|
1053
|
+
* before an external worker starts a saga that can retire this process.
|
|
1054
|
+
*/
|
|
1055
|
+
async closeRoomFromOwner(sender, roomId, wireId) {
|
|
1056
|
+
const { acceptManagedRoomClose, recordManagedRoomCloseError } = await import('../rooms-tasks/close.js');
|
|
1057
|
+
const accepted = await acceptManagedRoomClose(roomId);
|
|
1058
|
+
if (accepted.state === 'closed') {
|
|
1059
|
+
await this.send(sender.id, `🔒 Room ${roomId} is already closed`, wireId);
|
|
1060
|
+
this.state.remember(wireId);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
await this.send(sender.id, `🔒 Room ${roomId} close accepted/pending`, wireId);
|
|
1064
|
+
this.state.remember(wireId);
|
|
1065
|
+
try {
|
|
1066
|
+
await this.fleetOps.closeRoom(roomId);
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
await recordManagedRoomCloseError(roomId, error instanceof Error ? error.message : String(error), `External close worker failed to start. Retry /room close ${roomId} ${roomId}.`);
|
|
1070
|
+
throw error;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/** Carry a task terminal request through a worker that survives this role. */
|
|
1074
|
+
async terminalTaskFromOwner(sender, taskId, kind, outcome, wireId) {
|
|
1075
|
+
const { getTask } = await import('../rooms-tasks/task-state.js');
|
|
1076
|
+
const { acceptTaskTerminalIntent, recordTaskTerminalIntentError, } = await import('../rooms-tasks/terminal.js');
|
|
1077
|
+
const task = getTask(taskId);
|
|
1078
|
+
const accepted = await acceptTaskTerminalIntent({
|
|
1079
|
+
taskId, kind, roomId: task.room_id, outcome,
|
|
1080
|
+
});
|
|
1081
|
+
if (accepted.terminal_intent?.status === 'settled') {
|
|
1082
|
+
await this.send(sender.id, `✅ Task ${taskId} → ${accepted.state}`, wireId);
|
|
1083
|
+
this.state.remember(wireId);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
await this.send(sender.id, `⏳ Task ${taskId} terminal intent accepted/pending`, wireId);
|
|
1087
|
+
this.state.remember(wireId);
|
|
1088
|
+
try {
|
|
1089
|
+
await this.fleetOps.settleTask(taskId);
|
|
1090
|
+
}
|
|
1091
|
+
catch (error) {
|
|
1092
|
+
await recordTaskTerminalIntentError(taskId, error instanceof Error ? error.message : String(error), `External settle worker failed to start. Retry the identical task command or run task recover ${taskId}.`);
|
|
1093
|
+
throw error;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1049
1096
|
/** Code-point-safe tail of the worklog, or undefined when there is none. */
|
|
1050
1097
|
async readWorklogTail(maxChars) {
|
|
1051
1098
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InterruptOutcome, SessionEvent, SessionSnapshot } from '../session/types.js';
|
|
2
|
+
import type { TaskOutcome, TaskTerminalIntent } from '../rooms-tasks/types.js';
|
|
2
3
|
import { type OwnerCommentsState } from './notices.js';
|
|
3
4
|
/**
|
|
4
5
|
* Fleet-level effects a deterministic owner command may trigger. Production
|
|
@@ -10,6 +11,10 @@ export interface OwnerFleetOps {
|
|
|
10
11
|
restart(mode: 'keep' | 'fresh'): Promise<void>;
|
|
11
12
|
/** `ours-fleet ls` output. */
|
|
12
13
|
list(): Promise<string>;
|
|
14
|
+
/** Start a room-close worker outside the caller role's supervisor lifecycle. */
|
|
15
|
+
closeRoom(roomId: string): Promise<void>;
|
|
16
|
+
/** Resume a task terminal intent outside the caller role's supervisor lifecycle. */
|
|
17
|
+
settleTask(taskId: string): Promise<void>;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* The narrow capability surface a command executor sees. Everything here is
|
|
@@ -40,6 +45,10 @@ export interface OwnerCommandContext {
|
|
|
40
45
|
*/
|
|
41
46
|
setComments(enabled: boolean): OwnerCommentsState;
|
|
42
47
|
fleetList(): Promise<string>;
|
|
48
|
+
/** Persist acceptance, acknowledge it, then launch the external close worker. */
|
|
49
|
+
closeRoom(roomId: string): Promise<void>;
|
|
50
|
+
/** Persist terminal intent, acknowledge it, then launch the external settle worker. */
|
|
51
|
+
terminalTask(taskId: string, kind: TaskTerminalIntent['kind'], outcome?: TaskOutcome): Promise<void>;
|
|
43
52
|
recentEvents(limit: number): SessionEvent[];
|
|
44
53
|
readWorklogTail(maxChars: number): Promise<string | undefined>;
|
|
45
54
|
reply(text: string): Promise<void>;
|