@ours.network/fleet 0.18.1 → 0.19.0-nightly.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.
Files changed (79) hide show
  1. package/README.md +57 -91
  2. package/dist/application/fleet-query-service.js +0 -12
  3. package/dist/application/role-creation-service.js +10 -7
  4. package/dist/application/types.d.ts +0 -11
  5. package/dist/briefing.js +6 -15
  6. package/dist/build-info.json +5 -5
  7. package/dist/cli.js +12 -37
  8. package/dist/config.d.ts +8 -6
  9. package/dist/config.js +46 -30
  10. package/dist/creation.d.ts +38 -22
  11. package/dist/creation.js +111 -24
  12. package/dist/docs.d.ts +1 -1
  13. package/dist/docs.js +44 -95
  14. package/dist/doctor.d.ts +5 -1
  15. package/dist/doctor.js +18 -11
  16. package/dist/fleet-proxy.d.ts +0 -5
  17. package/dist/harness/acp-agent.js +6 -11
  18. package/dist/harness/claude-code.js +11 -200
  19. package/dist/harness/codex.d.ts +1 -4
  20. package/dist/harness/codex.js +12 -70
  21. package/dist/harness/types.d.ts +4 -54
  22. package/dist/loops/manager.d.ts +1 -30
  23. package/dist/loops/manager.js +6 -69
  24. package/dist/loops/state.d.ts +0 -18
  25. package/dist/loops/state.js +0 -4
  26. package/dist/monitor.js +1 -1
  27. package/dist/ops.d.ts +3 -0
  28. package/dist/ops.js +8 -3
  29. package/dist/owner-channel/attachments.d.ts +25 -2
  30. package/dist/owner-channel/attachments.js +61 -5
  31. package/dist/owner-channel/channel.d.ts +29 -30
  32. package/dist/owner-channel/channel.js +291 -291
  33. package/dist/owner-channel/commands.js +89 -0
  34. package/dist/owner-channel/message-recovery.d.ts +25 -0
  35. package/dist/owner-channel/message-recovery.js +114 -0
  36. package/dist/owner-channel/notices.d.ts +0 -7
  37. package/dist/owner-channel/notices.js +0 -9
  38. package/dist/owner-channel/ours-client.d.ts +148 -0
  39. package/dist/owner-channel/ours-client.js +231 -0
  40. package/dist/rooms-tasks/cli.d.ts +4 -0
  41. package/dist/rooms-tasks/cli.js +565 -0
  42. package/dist/rooms-tasks/config.d.ts +15 -0
  43. package/dist/rooms-tasks/config.js +171 -0
  44. package/dist/rooms-tasks/cowork-adapter.d.ts +80 -0
  45. package/dist/rooms-tasks/cowork-adapter.js +48 -0
  46. package/dist/rooms-tasks/index.d.ts +7 -0
  47. package/dist/rooms-tasks/index.js +7 -0
  48. package/dist/rooms-tasks/room-state.d.ts +22 -0
  49. package/dist/rooms-tasks/room-state.js +117 -0
  50. package/dist/rooms-tasks/task-state.d.ts +31 -0
  51. package/dist/rooms-tasks/task-state.js +173 -0
  52. package/dist/rooms-tasks/templates.d.ts +6 -0
  53. package/dist/rooms-tasks/templates.js +80 -0
  54. package/dist/rooms-tasks/types.d.ts +153 -0
  55. package/dist/rooms-tasks/types.js +20 -0
  56. package/dist/runner.d.ts +0 -48
  57. package/dist/runner.js +94 -236
  58. package/dist/session/acp.d.ts +0 -104
  59. package/dist/session/acp.js +10 -213
  60. package/dist/session/conversation-normalizer.d.ts +0 -6
  61. package/dist/session/conversation-normalizer.js +10 -153
  62. package/dist/session/conversation-types.d.ts +4 -23
  63. package/dist/session/types.d.ts +0 -35
  64. package/dist/spawn.js +26 -33
  65. package/dist/supervisor/systemd.js +29 -2
  66. package/dist/watchdog/briefing.js +0 -7
  67. package/dist/watchdog/run.js +3 -3
  68. package/dist/web-app/assets/{TerminalView-C_G1ID2P.js → TerminalView-BAVk1Bot.js} +1 -1
  69. package/dist/web-app/assets/{index-BCBK78hw.js → index-C3S-xFRU.js} +5 -5
  70. package/dist/web-app/index.html +1 -1
  71. package/dist/worklog.d.ts +1 -7
  72. package/dist/worklog.js +39 -191
  73. package/package.json +3 -1
  74. package/dist/model-env.d.ts +0 -71
  75. package/dist/model-env.js +0 -106
  76. package/dist/owner-channel/mcp.d.ts +0 -24
  77. package/dist/owner-channel/mcp.js +0 -145
  78. package/dist/session/activity.d.ts +0 -31
  79. package/dist/session/activity.js +0 -48
@@ -0,0 +1,565 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { loadConfig } from '../config.js';
3
+ import { resolveTemplate, listTemplates, snapshotTemplate, hashTemplate, } from './templates.js';
4
+ import { createTask, getTask, listTasks, startTask, blockTask, unblockTask, reviewTask, completeTask, cancelTask, } from './task-state.js';
5
+ import { createRoomRecord, getRoomRecord, listRoomRecords, closeRoom as closeRoomRecord, } from './room-state.js';
6
+ function die(e) {
7
+ const msg = e instanceof Error ? e.message : String(e);
8
+ process.stderr.write(`error: ${msg}\n`);
9
+ process.exit(1);
10
+ }
11
+ function loadCfg(opts) {
12
+ return loadConfig(opts.configuration);
13
+ }
14
+ function allTemplates(cfg) {
15
+ return cfg.roomTemplates ?? {};
16
+ }
17
+ export function registerTemplateCommands(parent, cOpt) {
18
+ const templateCmd = parent.command('template').description('room template operations');
19
+ cOpt(templateCmd.command('list'))
20
+ .description('list available room templates')
21
+ .option('--json', 'JSON output')
22
+ .action((opts) => {
23
+ try {
24
+ const cfg = loadCfg(opts);
25
+ const templates = listTemplates(allTemplates(cfg));
26
+ if (opts.json) {
27
+ console.log(JSON.stringify({ schema_version: 1, templates }, null, 2));
28
+ return;
29
+ }
30
+ if (!templates.length) {
31
+ console.log('No templates configured.');
32
+ return;
33
+ }
34
+ for (const t of templates) {
35
+ const tag = t.builtin ? ' (built-in)' : '';
36
+ console.log(`${t.name}@${t.version}${tag} ${t.description}`);
37
+ }
38
+ }
39
+ catch (e) {
40
+ die(e);
41
+ }
42
+ });
43
+ cOpt(templateCmd.command('show <name>'))
44
+ .description('show template details')
45
+ .option('--json', 'JSON output')
46
+ .action((name, opts) => {
47
+ try {
48
+ const cfg = loadCfg(opts);
49
+ const t = resolveTemplate(name, allTemplates(cfg));
50
+ if (!t)
51
+ die(new Error(`template not found: ${name}`));
52
+ if (opts.json) {
53
+ console.log(JSON.stringify({ schema_version: 1, template: { ...t, content_hash: hashTemplate(t) } }, null, 2));
54
+ return;
55
+ }
56
+ console.log(`${t.name}@${t.version} ${t.description}`);
57
+ if (t.contract)
58
+ console.log(`\nContract:\n${t.contract}`);
59
+ console.log(`\nMembers:`);
60
+ for (const m of t.members)
61
+ console.log(` ${m.slot}: ${m.count}× ${m.role} (ref: ${m.role_ref})`);
62
+ console.log(`\nContent hash: ${hashTemplate(t)}`);
63
+ }
64
+ catch (e) {
65
+ die(e);
66
+ }
67
+ });
68
+ cOpt(templateCmd.command('validate'))
69
+ .description('validate all room templates')
70
+ .option('--json', 'JSON output')
71
+ .action((opts) => {
72
+ try {
73
+ const cfg = loadCfg(opts);
74
+ const templates = listTemplates(allTemplates(cfg));
75
+ const problems = [];
76
+ for (const t of templates) {
77
+ const issues = [];
78
+ for (const m of t.members) {
79
+ if (!cfg.roles.some(r => r.name === m.role_ref))
80
+ issues.push(`member ${m.slot}: role_ref '${m.role_ref}' not found in fleet roles`);
81
+ }
82
+ if (issues.length)
83
+ problems.push({ template: `${t.name}@${t.version}`, issues });
84
+ }
85
+ if (opts.json) {
86
+ console.log(JSON.stringify({ schema_version: 1, valid: !problems.length, problems }, null, 2));
87
+ return;
88
+ }
89
+ if (!problems.length) {
90
+ console.log('All templates valid.');
91
+ return;
92
+ }
93
+ for (const p of problems) {
94
+ console.log(`${p.template}:`);
95
+ for (const i of p.issues)
96
+ console.log(` - ${i}`);
97
+ }
98
+ process.exit(1);
99
+ }
100
+ catch (e) {
101
+ die(e);
102
+ }
103
+ });
104
+ }
105
+ export function registerTaskCommands(parent, cOpt) {
106
+ const taskCmd = parent.command('task').description('task lifecycle operations');
107
+ cOpt(taskCmd.command('create'))
108
+ .description('create a new task')
109
+ .requiredOption('--title <title>', 'task title')
110
+ .option('--template <name>', 'room template')
111
+ .option('--brief <text>', 'task brief')
112
+ .option('--brief-file <path>', 'task brief from file')
113
+ .option('--backlog', 'create in backlog (do not start immediately)')
114
+ .option('--no-room', 'create task without a room')
115
+ .option('--idempotency-key <key>', 'idempotency key')
116
+ .option('--json', 'JSON output')
117
+ .action((opts) => {
118
+ try {
119
+ const cfg = loadCfg(opts);
120
+ let templateRef;
121
+ if (opts.template) {
122
+ const t = resolveTemplate(opts.template, allTemplates(cfg));
123
+ if (!t)
124
+ die(new Error(`template not found: ${opts.template}`));
125
+ const snap = snapshotTemplate(t);
126
+ templateRef = { name: snap.name, version: snap.version, content_hash: snap.content_hash };
127
+ }
128
+ else if (opts.room !== false) {
129
+ const defaultTpl = cfg.tasks?.default_room_template ?? cfg.rooms?.defaults?.template ?? 'development-team';
130
+ const t = resolveTemplate(defaultTpl, allTemplates(cfg));
131
+ if (t) {
132
+ const snap = snapshotTemplate(t);
133
+ templateRef = { name: snap.name, version: snap.version, content_hash: snap.content_hash };
134
+ }
135
+ }
136
+ let brief = opts.brief;
137
+ if (opts.briefFile) {
138
+ brief = readFileSync(opts.briefFile, 'utf8');
139
+ }
140
+ const origin = { type: 'cli' };
141
+ const record = createTask({
142
+ title: opts.title,
143
+ brief,
144
+ brief_file: opts.briefFile,
145
+ template: templateRef,
146
+ origin,
147
+ idempotency_key: opts.idempotencyKey,
148
+ start: !opts.backlog,
149
+ });
150
+ if (opts.json) {
151
+ console.log(JSON.stringify({ schema_version: 1, task: record }, null, 2));
152
+ return;
153
+ }
154
+ console.log(`Task ${record.task_id} created · ${record.state}`);
155
+ if (templateRef)
156
+ console.log(`Template: ${templateRef.name}@${templateRef.version}`);
157
+ }
158
+ catch (e) {
159
+ die(e);
160
+ }
161
+ });
162
+ cOpt(taskCmd.command('list'))
163
+ .description('list tasks')
164
+ .option('--state <state>', 'filter by state (backlog|active|provisioning|review|done|cancelled|failed|all)')
165
+ .option('--json', 'JSON output')
166
+ .action((opts) => {
167
+ try {
168
+ let stateFilter;
169
+ if (opts.state && opts.state !== 'all') {
170
+ stateFilter = opts.state;
171
+ }
172
+ const tasks = listTasks(stateFilter ? { state: stateFilter } : undefined);
173
+ if (opts.json) {
174
+ console.log(JSON.stringify({ schema_version: 1, tasks }, null, 2));
175
+ return;
176
+ }
177
+ if (!tasks.length) {
178
+ console.log('No tasks.');
179
+ return;
180
+ }
181
+ for (const t of tasks) {
182
+ const blocked = t.blocked ? ` [BLOCKED: ${t.blocked.reason}]` : '';
183
+ console.log(`${t.task_id} ${t.state}${blocked} ${t.title}`);
184
+ }
185
+ }
186
+ catch (e) {
187
+ die(e);
188
+ }
189
+ });
190
+ taskCmd.command('show <id>')
191
+ .description('show task details')
192
+ .option('--json', 'JSON output')
193
+ .action((id, opts) => {
194
+ try {
195
+ const t = getTask(id);
196
+ if (opts.json) {
197
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
198
+ return;
199
+ }
200
+ console.log(`Task: ${t.task_id}`);
201
+ console.log(`Title: ${t.title}`);
202
+ console.log(`State: ${t.state}${t.blocked ? ` [BLOCKED: ${t.blocked.reason}]` : ''}`);
203
+ if (t.template)
204
+ console.log(`Template: ${t.template.name}@${t.template.version}`);
205
+ if (t.room_id)
206
+ console.log(`Room: ${t.room_id}`);
207
+ if (t.room_identity_cid)
208
+ console.log(`Room CID: ${t.room_identity_cid}`);
209
+ if (t.member_roles.length) {
210
+ console.log('Members:');
211
+ for (const m of t.member_roles)
212
+ console.log(` ${m.name} (${m.cowork_role}) ${m.identity_cid}`);
213
+ }
214
+ console.log(`Origin: ${t.origin.type}`);
215
+ console.log(`Created: ${t.created_at}`);
216
+ if (t.started_at)
217
+ console.log(`Started: ${t.started_at}`);
218
+ if (t.ended_at)
219
+ console.log(`Ended: ${t.ended_at}`);
220
+ if (t.outcome)
221
+ console.log(`Outcome: ${t.outcome.summary}`);
222
+ }
223
+ catch (e) {
224
+ die(e);
225
+ }
226
+ });
227
+ taskCmd.command('start <id>')
228
+ .description('start a backlog task')
229
+ .option('--json', 'JSON output')
230
+ .action((id, opts) => {
231
+ try {
232
+ const t = startTask(id);
233
+ if (opts.json) {
234
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
235
+ return;
236
+ }
237
+ console.log(`Task ${t.task_id} · provisioning`);
238
+ }
239
+ catch (e) {
240
+ die(e);
241
+ }
242
+ });
243
+ taskCmd.command('block <id>')
244
+ .description('mark a task as blocked')
245
+ .requiredOption('--reason <reason>', 'blocking reason')
246
+ .option('--json', 'JSON output')
247
+ .action((id, opts) => {
248
+ try {
249
+ const t = blockTask(id, opts.reason);
250
+ if (opts.json) {
251
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
252
+ return;
253
+ }
254
+ console.log(`Task ${t.task_id} · blocked: ${opts.reason}`);
255
+ }
256
+ catch (e) {
257
+ die(e);
258
+ }
259
+ });
260
+ taskCmd.command('unblock <id>')
261
+ .description('unblock a task')
262
+ .option('--json', 'JSON output')
263
+ .action((id, opts) => {
264
+ try {
265
+ const t = unblockTask(id);
266
+ if (opts.json) {
267
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
268
+ return;
269
+ }
270
+ console.log(`Task ${t.task_id} · unblocked`);
271
+ }
272
+ catch (e) {
273
+ die(e);
274
+ }
275
+ });
276
+ taskCmd.command('review <id>')
277
+ .description('move a task to review')
278
+ .option('--json', 'JSON output')
279
+ .action((id, opts) => {
280
+ try {
281
+ const t = reviewTask(id);
282
+ if (opts.json) {
283
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
284
+ return;
285
+ }
286
+ console.log(`Task ${t.task_id} · review`);
287
+ }
288
+ catch (e) {
289
+ die(e);
290
+ }
291
+ });
292
+ taskCmd.command('done <id>')
293
+ .description('complete a task')
294
+ .option('--summary <text>', 'completion summary')
295
+ .option('--summary-file <path>', 'completion summary from file')
296
+ .option('--json', 'JSON output')
297
+ .action((id, opts) => {
298
+ try {
299
+ let summary = opts.summary;
300
+ if (opts.summaryFile)
301
+ summary = readFileSync(opts.summaryFile, 'utf8');
302
+ const outcome = summary ? { summary } : undefined;
303
+ const t = completeTask(id, outcome);
304
+ if (opts.json) {
305
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
306
+ return;
307
+ }
308
+ console.log(`Task ${t.task_id} · done`);
309
+ }
310
+ catch (e) {
311
+ die(e);
312
+ }
313
+ });
314
+ taskCmd.command('cancel <id> <confirm-id>')
315
+ .description('cancel a task (requires ID twice for confirmation)')
316
+ .option('--json', 'JSON output')
317
+ .action((id, confirmId, opts) => {
318
+ try {
319
+ if (id !== confirmId)
320
+ die(new Error('confirmation ID must match task ID'));
321
+ const t = cancelTask(id);
322
+ if (opts.json) {
323
+ console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
324
+ return;
325
+ }
326
+ console.log(`Task ${t.task_id} · cancelled`);
327
+ }
328
+ catch (e) {
329
+ die(e);
330
+ }
331
+ });
332
+ taskCmd.command('recover <id>')
333
+ .description('attempt to recover a stuck task')
334
+ .option('--json', 'JSON output')
335
+ .action((id, opts) => {
336
+ try {
337
+ const t = getTask(id);
338
+ const room = t.room_id ? getRoomRecord(t.room_id) : undefined;
339
+ const result = {
340
+ task: t,
341
+ room: room ?? null,
342
+ recovery_actions: [],
343
+ };
344
+ if (t.state === 'provisioning' && room) {
345
+ if (room.provisioning_detail === 'waiting_cowork')
346
+ result.recovery_actions.push('Cowork management socket unreachable — check ours-cowork service');
347
+ if (room.provisioning_detail === 'waiting_owner_invite')
348
+ result.recovery_actions.push('Owner invite invalid or expired — rotate rooms.owner.public_invite in config');
349
+ if (room.provisioning_detail === 'owner_cid_mismatch')
350
+ result.recovery_actions.push('Owner CID mismatch — verify rooms.owner.expected_cid matches Messenger identity');
351
+ if (room.provisioning_detail === 'member_failed')
352
+ result.recovery_actions.push(`Member creation failed at saga step ${room.saga.step_index} — inspect and retry`);
353
+ }
354
+ if (opts.json) {
355
+ console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
356
+ return;
357
+ }
358
+ console.log(`Task ${t.task_id} · ${t.state}`);
359
+ if (room)
360
+ console.log(`Room ${room.room_id} · ${room.state} · saga: ${room.saga.phase}`);
361
+ if (result.recovery_actions.length) {
362
+ console.log('Recovery actions:');
363
+ for (const a of result.recovery_actions)
364
+ console.log(` - ${a}`);
365
+ }
366
+ else {
367
+ console.log('No automated recovery actions available.');
368
+ }
369
+ }
370
+ catch (e) {
371
+ die(e);
372
+ }
373
+ });
374
+ }
375
+ export function registerRoomCommands(parent, cOpt) {
376
+ const roomCmd = parent.command('room').description('room orchestration operations');
377
+ cOpt(roomCmd.command('create'))
378
+ .description('create a standalone room')
379
+ .requiredOption('--name <name>', 'room name')
380
+ .option('--template <name>', 'room template')
381
+ .option('--goal <text>', 'room goal')
382
+ .option('--brief <text>', 'room briefing')
383
+ .option('--brief-file <path>', 'room briefing from file')
384
+ .option('--json', 'JSON output')
385
+ .action((opts) => {
386
+ try {
387
+ const cfg = loadCfg(opts);
388
+ let brief = opts.brief;
389
+ if (opts.briefFile)
390
+ brief = readFileSync(opts.briefFile, 'utf8');
391
+ const roomId = `room-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
392
+ let templateSnapshot;
393
+ if (opts.template) {
394
+ const t = resolveTemplate(opts.template, allTemplates(cfg));
395
+ if (!t)
396
+ die(new Error(`template not found: ${opts.template}`));
397
+ templateSnapshot = snapshotTemplate(t);
398
+ }
399
+ const record = createRoomRecord({
400
+ room_id: roomId,
401
+ room_name: opts.name,
402
+ template_snapshot: templateSnapshot,
403
+ });
404
+ if (opts.json) {
405
+ console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
406
+ return;
407
+ }
408
+ console.log(`Room ${record.room_id} created · ${record.state}`);
409
+ if (templateSnapshot)
410
+ console.log(`Template: ${templateSnapshot.name}@${templateSnapshot.version}`);
411
+ }
412
+ catch (e) {
413
+ die(e);
414
+ }
415
+ });
416
+ roomCmd.command('list')
417
+ .description('list rooms')
418
+ .option('--state <state>', 'filter by state (active|provisioning|closing|closed|all)')
419
+ .option('--json', 'JSON output')
420
+ .action((opts) => {
421
+ try {
422
+ let stateFilter;
423
+ if (opts.state && opts.state !== 'all') {
424
+ stateFilter = opts.state;
425
+ }
426
+ const rooms = listRoomRecords(stateFilter ? { state: stateFilter } : undefined);
427
+ if (opts.json) {
428
+ console.log(JSON.stringify({ schema_version: 1, rooms }, null, 2));
429
+ return;
430
+ }
431
+ if (!rooms.length) {
432
+ console.log('No rooms.');
433
+ return;
434
+ }
435
+ for (const r of rooms)
436
+ console.log(`${r.room_id} ${r.state} ${r.room_name}${r.task_id ? ` (task: ${r.task_id})` : ''}`);
437
+ }
438
+ catch (e) {
439
+ die(e);
440
+ }
441
+ });
442
+ roomCmd.command('show <id>')
443
+ .description('show room details')
444
+ .option('--json', 'JSON output')
445
+ .action((id, opts) => {
446
+ try {
447
+ const r = getRoomRecord(id);
448
+ if (!r)
449
+ die(new Error(`room not found: ${id}`));
450
+ if (opts.json) {
451
+ console.log(JSON.stringify({ schema_version: 1, room: r }, null, 2));
452
+ return;
453
+ }
454
+ console.log(`Room: ${r.room_id}`);
455
+ console.log(`Name: ${r.room_name}`);
456
+ console.log(`State: ${r.state}`);
457
+ if (r.task_id)
458
+ console.log(`Task: ${r.task_id}`);
459
+ if (r.room_identity_cid)
460
+ console.log(`Identity CID: ${r.room_identity_cid}`);
461
+ console.log(`Saga: ${r.saga.phase} (step ${r.saga.step_index})`);
462
+ if (r.provisioning_detail)
463
+ console.log(`Detail: ${r.provisioning_detail}`);
464
+ if (r.saga.error)
465
+ console.log(`Error: ${r.saga.error}`);
466
+ if (r.member_seats.length) {
467
+ console.log('Members:');
468
+ for (const s of r.member_seats)
469
+ console.log(` ${s.role_name} (${s.cowork_role}) ${s.seat_state}`);
470
+ }
471
+ console.log(`Created: ${r.created_at}`);
472
+ if (r.activated_at)
473
+ console.log(`Activated: ${r.activated_at}`);
474
+ if (r.closed_at)
475
+ console.log(`Closed: ${r.closed_at}`);
476
+ }
477
+ catch (e) {
478
+ die(e);
479
+ }
480
+ });
481
+ roomCmd.command('members <id>')
482
+ .description('show room members')
483
+ .option('--json', 'JSON output')
484
+ .action((id, opts) => {
485
+ try {
486
+ const r = getRoomRecord(id);
487
+ if (!r)
488
+ die(new Error(`room not found: ${id}`));
489
+ if (opts.json) {
490
+ console.log(JSON.stringify({
491
+ schema_version: 1,
492
+ room_id: r.room_id,
493
+ members: r.member_seats,
494
+ owner_seat_cid: r.owner_seat_cid ?? null,
495
+ }, null, 2));
496
+ return;
497
+ }
498
+ console.log(`Room ${r.room_id} — ${r.room_name}`);
499
+ if (r.owner_seat_cid)
500
+ console.log(`Owner: ${r.owner_seat_cid}`);
501
+ if (!r.member_seats.length) {
502
+ console.log('No members.');
503
+ return;
504
+ }
505
+ for (const s of r.member_seats)
506
+ console.log(` ${s.role_name} (${s.cowork_role}) ${s.seat_state} ${s.identity_cid}`);
507
+ }
508
+ catch (e) {
509
+ die(e);
510
+ }
511
+ });
512
+ roomCmd.command('close <id> <confirm-id>')
513
+ .description('close a room (requires ID twice for confirmation)')
514
+ .option('--json', 'JSON output')
515
+ .action((id, confirmId, opts) => {
516
+ try {
517
+ if (id !== confirmId)
518
+ die(new Error('confirmation ID must match room ID'));
519
+ const r = closeRoomRecord(id);
520
+ if (opts.json) {
521
+ console.log(JSON.stringify({ schema_version: 1, room: r }, null, 2));
522
+ return;
523
+ }
524
+ console.log(`Room ${r.room_id} · closed`);
525
+ }
526
+ catch (e) {
527
+ die(e);
528
+ }
529
+ });
530
+ roomCmd.command('recover <id>')
531
+ .description('attempt to recover a stuck room')
532
+ .option('--json', 'JSON output')
533
+ .action((id, opts) => {
534
+ try {
535
+ const r = getRoomRecord(id);
536
+ if (!r)
537
+ die(new Error(`room not found: ${id}`));
538
+ const actions = [];
539
+ if (r.saga.error)
540
+ actions.push(`Last error: ${r.saga.error}`);
541
+ if (r.saga.recovery_hint)
542
+ actions.push(r.saga.recovery_hint);
543
+ if (r.provisioning_detail === 'waiting_cowork')
544
+ actions.push('Check ours-cowork service status');
545
+ if (r.provisioning_detail === 'waiting_owner_invite')
546
+ actions.push('Rotate rooms.owner.public_invite in config, then re-run recover');
547
+ if (opts.json) {
548
+ console.log(JSON.stringify({ schema_version: 1, room: r, recovery_actions: actions }, null, 2));
549
+ return;
550
+ }
551
+ console.log(`Room ${r.room_id} · ${r.state} · saga: ${r.saga.phase}`);
552
+ if (actions.length) {
553
+ console.log('Recovery:');
554
+ for (const a of actions)
555
+ console.log(` - ${a}`);
556
+ }
557
+ else {
558
+ console.log('No recovery actions needed.');
559
+ }
560
+ }
561
+ catch (e) {
562
+ die(e);
563
+ }
564
+ });
565
+ }
@@ -0,0 +1,15 @@
1
+ import type { RoomsConfig, TasksConfig, RoomTemplatesConfig } from './types.js';
2
+ export declare class RoomsTasksConfigError extends Error {
3
+ readonly path: string;
4
+ constructor(path: string, message: string);
5
+ }
6
+ export declare function fingerprint(invite: string): string;
7
+ export declare function validateRoomsConfig(raw: unknown, vars: Record<string, string>, path: string): RoomsConfig & {
8
+ _invite?: {
9
+ value: string;
10
+ fingerprint: string;
11
+ };
12
+ };
13
+ export declare function validateTasksConfig(raw: unknown, path: string): TasksConfig;
14
+ export declare function validateRoomTemplatesConfig(raw: unknown, path: string): RoomTemplatesConfig;
15
+ export declare const FLEET_D_ALLOWED_KEYS: readonly ["roles", "rooms", "room_templates", "tasks"];