@ours.network/fleet 0.19.0-nightly.13 → 0.19.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 +8 -0
- package/dist/build-info.json +4 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +8 -0
- package/dist/owner-channel/channel.js +18 -3
- package/dist/owner-channel/commands.js +198 -87
- package/dist/rooms-tasks/cli.js +453 -192
- package/dist/rooms-tasks/markdown.d.ts +49 -0
- package/dist/rooms-tasks/markdown.js +206 -0
- package/package.json +1 -1
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -9,11 +9,171 @@ import { createTask, getTask, listTasks, startTask, activateTask, blockTask, unb
|
|
|
9
9
|
import { createRoomRecord, getRoomRecord, listRoomRecords, advanceSaga, setOwnerSeat, setSagaError, activateRoom, RoomStateError, } from './room-state.js';
|
|
10
10
|
import { createCoworkAdapter, CoworkProtocolError, CoworkUnavailableError } from './cowork-adapter.js';
|
|
11
11
|
import { TASK_CANCELLABLE_STATES, TASK_TERMINAL_STATES } from './types.js';
|
|
12
|
+
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
13
|
+
class TaskRoomPublicError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
fields;
|
|
16
|
+
constructor(code, fields = {}) {
|
|
17
|
+
super(code);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.fields = fields;
|
|
20
|
+
this.name = 'TaskRoomPublicError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const PUBLIC_ERROR_FIELD = /^[A-Za-z0-9._:@-]{1,160}$/u;
|
|
24
|
+
function taskRoomPublicError(code, fields = {}) {
|
|
25
|
+
const validated = Object.fromEntries(Object.entries(fields).flatMap(([key, value]) => {
|
|
26
|
+
const text = String(value ?? '');
|
|
27
|
+
return PUBLIC_ERROR_FIELD.test(text) ? [[key, text]] : [];
|
|
28
|
+
}));
|
|
29
|
+
return new TaskRoomPublicError(code, validated);
|
|
30
|
+
}
|
|
31
|
+
function taskRoomPublicFailure(error) {
|
|
32
|
+
const f = error.fields;
|
|
33
|
+
switch (error.code) {
|
|
34
|
+
case 'task_confirmation_mismatch': return {
|
|
35
|
+
legacy: 'confirmation ID must match task ID', kind: 'usage',
|
|
36
|
+
detail: 'The two task IDs must match.', action: 'Repeat the same task ID twice.',
|
|
37
|
+
};
|
|
38
|
+
case 'room_confirmation_mismatch': return {
|
|
39
|
+
legacy: 'confirmation ID must match room ID', kind: 'usage',
|
|
40
|
+
detail: 'The two room IDs must match.', action: 'Repeat the same room ID twice.',
|
|
41
|
+
};
|
|
42
|
+
case 'template_not_found': return {
|
|
43
|
+
legacy: `template not found: ${f.template ?? 'requested-template'}`, kind: 'not_found',
|
|
44
|
+
detail: `The requested template ${f.template ?? ''}`.trim() + ' was not found.',
|
|
45
|
+
action: 'Run ours-fleet template list and retry with an available template.',
|
|
46
|
+
};
|
|
47
|
+
case 'template_mismatch': return {
|
|
48
|
+
legacy: `template ${f.requested ?? 'requested-template'} does not match room ${f.room ?? 'requested-room'}'s provisioned template ${f.provisioned ?? 'recorded-template'}`,
|
|
49
|
+
kind: 'state', detail: 'The requested template does not match the room’s provisioned template.',
|
|
50
|
+
action: 'Use the room’s recorded template or create a new room.',
|
|
51
|
+
};
|
|
52
|
+
case 'task_template_drift': return {
|
|
53
|
+
legacy: `task template snapshot no longer matches ${f.template ?? 'the recorded template'}`,
|
|
54
|
+
kind: 'state', detail: 'The task template snapshot no longer matches the recorded template.',
|
|
55
|
+
action: 'Run the matching show and recover commands before retrying.',
|
|
56
|
+
};
|
|
57
|
+
case 'task_terminal': return {
|
|
58
|
+
legacy: `task ${f.task ?? 'requested-task'} is in terminal state '${f.state ?? 'terminal'}'`,
|
|
59
|
+
kind: 'state', detail: 'The task is already in a terminal state.',
|
|
60
|
+
action: 'Run ours-fleet task show to inspect the completed task.',
|
|
61
|
+
};
|
|
62
|
+
case 'task_terminal_already': return {
|
|
63
|
+
legacy: `task ${f.task ?? 'requested-task'} is already in terminal state '${f.state ?? 'terminal'}'`,
|
|
64
|
+
kind: 'state', detail: 'The task is already in a terminal state.',
|
|
65
|
+
action: 'Run ours-fleet task show to inspect the completed task.',
|
|
66
|
+
};
|
|
67
|
+
case 'task_non_resumable': return {
|
|
68
|
+
legacy: `task ${f.task ?? 'requested-task'} has room ${f.room ?? 'requested-room'} in a non-resumable state — run 'task recover ${f.task ?? 'requested-task'}'`,
|
|
69
|
+
kind: 'state', detail: 'The task’s room is in a non-resumable state.',
|
|
70
|
+
action: `Run ours-fleet task recover ${f.task ?? '<id>'}.`,
|
|
71
|
+
};
|
|
72
|
+
case 'room_filter': return {
|
|
73
|
+
legacy: 'room state filter must be active, provisioning, or all', kind: 'usage',
|
|
74
|
+
detail: 'The room state filter must be active, provisioning, or all.',
|
|
75
|
+
action: 'Choose one of the supported room state filters.',
|
|
76
|
+
};
|
|
77
|
+
case 'room_not_found': return {
|
|
78
|
+
legacy: `room not found: ${f.room ?? 'requested-room'}`, kind: 'not_found',
|
|
79
|
+
detail: `Room ${f.room ?? ''}`.trim() + ' was not found.',
|
|
80
|
+
action: 'Run ours-fleet room list to find a live room ID.',
|
|
81
|
+
};
|
|
82
|
+
case 'room_record_not_found': return {
|
|
83
|
+
legacy: `room not found in Fleet orchestration: ${f.room ?? 'requested-room'}`, kind: 'not_found',
|
|
84
|
+
detail: `Room ${f.room ?? ''}`.trim() + ' was not found in Fleet orchestration.',
|
|
85
|
+
action: 'Run ours-fleet room list to find a live room ID.',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
12
89
|
function die(e) {
|
|
13
|
-
const msg = e instanceof
|
|
90
|
+
const msg = e instanceof TaskRoomPublicError
|
|
91
|
+
? taskRoomPublicFailure(e).legacy : e instanceof Error ? e.message : String(e);
|
|
14
92
|
process.stderr.write(`error: ${msg}\n`);
|
|
15
93
|
process.exit(1);
|
|
16
94
|
}
|
|
95
|
+
const TASK_STATE_WORD = '(?:backlog|provisioning|active|review|done|cancelled|failed)';
|
|
96
|
+
const SAFE_ID_WORD = '[A-Za-z0-9_-]{1,128}';
|
|
97
|
+
function isKnownTaskStateMessage(message) {
|
|
98
|
+
return [
|
|
99
|
+
new RegExp(`^cannot transition from '${TASK_STATE_WORD}' to '${TASK_STATE_WORD}'$`, 'u'),
|
|
100
|
+
new RegExp(`^task ${SAFE_ID_WORD} has a pending '(?:done|cancelled)' terminal intent$`, 'u'),
|
|
101
|
+
new RegExp(`^cannot (?:block|cancel) a '${TASK_STATE_WORD}' task$`, 'u'),
|
|
102
|
+
/^task is not blocked$/u,
|
|
103
|
+
new RegExp(`^task ${SAFE_ID_WORD} already has a conflicting '(?:done|cancelled)' terminal intent$`, 'u'),
|
|
104
|
+
new RegExp(`^task ${SAFE_ID_WORD} is already in terminal state '${TASK_STATE_WORD}'$`, 'u'),
|
|
105
|
+
new RegExp(`^task ${SAFE_ID_WORD} is not tied to room ${SAFE_ID_WORD}$`, 'u'),
|
|
106
|
+
new RegExp(`^task ${SAFE_ID_WORD} has no terminal intent$`, 'u'),
|
|
107
|
+
new RegExp(`^task ${SAFE_ID_WORD} reached terminal state '${TASK_STATE_WORD}' outside its intent$`, 'u'),
|
|
108
|
+
new RegExp(`^cannot delete a '${TASK_STATE_WORD}' task; only 'done' tasks can be deleted$`, 'u'),
|
|
109
|
+
].some(pattern => pattern.test(message));
|
|
110
|
+
}
|
|
111
|
+
function isKnownRoomStateMessage(message) {
|
|
112
|
+
return [
|
|
113
|
+
new RegExp(`^room ${SAFE_ID_WORD} history cursor cannot move backward$`, 'u'),
|
|
114
|
+
new RegExp(`^room ${SAFE_ID_WORD} is not closing$`, 'u'),
|
|
115
|
+
new RegExp(`^room ${SAFE_ID_WORD} has no recorded member .{1,128}$`, 'u'),
|
|
116
|
+
new RegExp(`^room ${SAFE_ID_WORD} member .{1,128} (?:launch|briefing|retirement) cannot move backward to [A-Za-z_]+$`, 'u'),
|
|
117
|
+
new RegExp(`^room ${SAFE_ID_WORD} member .{1,128} launch changed from ${SAFE_ID_WORD} to ${SAFE_ID_WORD}$`, 'u'),
|
|
118
|
+
new RegExp(`^room ${SAFE_ID_WORD} close cannot move backward to [A-Za-z_]+$`, 'u'),
|
|
119
|
+
].some(pattern => pattern.test(message));
|
|
120
|
+
}
|
|
121
|
+
function dieTaskRoom(e) {
|
|
122
|
+
if (e instanceof TaskRoomPublicError) {
|
|
123
|
+
const failure = taskRoomPublicFailure(e);
|
|
124
|
+
const output = renderMarkdownFailure({
|
|
125
|
+
kind: failure.kind, subject: 'ours-fleet task/room', detail: failure.detail,
|
|
126
|
+
action: failure.action,
|
|
127
|
+
});
|
|
128
|
+
process.stderr.write(`${output}\n`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
const taskMissing = e instanceof TaskStateError
|
|
132
|
+
? /^task not found: ([A-Za-z0-9_-]{1,128})$/u.exec(e.message) : null;
|
|
133
|
+
const roomMissing = e instanceof RoomStateError
|
|
134
|
+
? /^room not found: ([A-Za-z0-9_-]{1,128})$/u.exec(e.message) : null;
|
|
135
|
+
const notFoundId = taskMissing?.[1] ?? roomMissing?.[1];
|
|
136
|
+
const state = e instanceof TaskStateError ? isKnownTaskStateMessage(e.message)
|
|
137
|
+
: e instanceof RoomStateError ? isKnownRoomStateMessage(e.message) : false;
|
|
138
|
+
const validation = e instanceof ConfigError
|
|
139
|
+
|| (e instanceof TaskStateError
|
|
140
|
+
&& e.message === 'invalid task ID: expected the canonical 17-character lowercase ID');
|
|
141
|
+
const kind = notFoundId ? 'not_found' : state ? 'state' : validation ? 'usage' : 'unexpected';
|
|
142
|
+
const output = renderMarkdownFailure({
|
|
143
|
+
kind,
|
|
144
|
+
subject: 'ours-fleet task/room',
|
|
145
|
+
...(notFoundId ? { detail: `${taskMissing ? 'Task' : 'Room'} ${notFoundId} was not found.` }
|
|
146
|
+
: state ? { detail: 'The current task or room state does not allow that action.' }
|
|
147
|
+
: validation ? { detail: 'Fleet configuration is invalid or incomplete.' } : {}),
|
|
148
|
+
action: notFoundId ? 'Run the matching list command to find a valid ID.'
|
|
149
|
+
: state ? 'Run the matching show command to inspect the current state.'
|
|
150
|
+
: validation ? 'Check the command options and configuration, then retry.'
|
|
151
|
+
: 'Retry once; if it repeats, run ours-fleet doctor and inspect the role logs.',
|
|
152
|
+
});
|
|
153
|
+
process.stderr.write(`${output}\n`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
157
|
+
icon: '📋', title, empty: 'No tasks found.',
|
|
158
|
+
records: tasks.map(t => `${taskStatus(t.state)} ${markdownCode(t.task_id)} — ${markdownProse(t.title)}`
|
|
159
|
+
+ (t.blocked ? ` — 🚧 Blocked: ${markdownProse(t.blocked.reason)}` : '')),
|
|
160
|
+
});
|
|
161
|
+
const taskActionMarkdown = (title, task, fields = []) => renderMarkdownResult({
|
|
162
|
+
icon: '📋', title,
|
|
163
|
+
fields: [
|
|
164
|
+
{ label: 'ID', value: task.task_id, kind: 'code' },
|
|
165
|
+
{ label: 'Status', value: taskStatus(task.state), kind: 'markdown' },
|
|
166
|
+
...fields,
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
const roomActionMarkdown = (title, room, fields = []) => renderMarkdownResult({
|
|
170
|
+
icon: '🏠', title,
|
|
171
|
+
fields: [
|
|
172
|
+
{ label: 'ID', value: room.room_id, kind: 'code' },
|
|
173
|
+
{ label: 'Status', value: roomStatus(room.state), kind: 'markdown' },
|
|
174
|
+
...fields,
|
|
175
|
+
],
|
|
176
|
+
});
|
|
17
177
|
const PUBLIC_SETTLE_WAIT_MS = 60_000;
|
|
18
178
|
const PUBLIC_SETTLE_POLL_MS = 100;
|
|
19
179
|
const sleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms); });
|
|
@@ -92,27 +252,31 @@ function durableTaskRoomTemplate(task, room) {
|
|
|
92
252
|
}
|
|
93
253
|
return snapshot;
|
|
94
254
|
}
|
|
95
|
-
function
|
|
255
|
+
function roomStartupSections(room) {
|
|
256
|
+
const sections = [];
|
|
96
257
|
const definitions = Object.values(room.role_briefings ?? {});
|
|
97
258
|
if (definitions.length) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
259
|
+
sections.push({
|
|
260
|
+
heading: 'Role briefings',
|
|
261
|
+
markdownItems: definitions.sort((a, b) => a.role.localeCompare(b.role)).map(definition => `${markdownCode(definition.role)} — version ${markdownCode(definition.version ?? '?')} — ${markdownProse(definition.state)}`),
|
|
262
|
+
});
|
|
101
263
|
}
|
|
102
264
|
if (room.member_seats.length) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
265
|
+
sections.push({
|
|
266
|
+
heading: 'Fleet startup evidence',
|
|
267
|
+
markdownItems: room.member_seats.map(seat => {
|
|
268
|
+
const launch = seat.launch?.state ?? 'unrecorded';
|
|
269
|
+
const briefing = seat.briefing?.state ?? 'unrecorded';
|
|
270
|
+
return `${markdownCode(seat.role_name)} — ${markdownProse(seat.cowork_role)} — `
|
|
271
|
+
+ `seat ${markdownProse(seat.seat_state)}, launch ${markdownProse(launch)}, briefing ${markdownProse(briefing)}`
|
|
272
|
+
+ (seat.briefing?.acknowledgement_message_id ? ' — acknowledgement recorded' : '')
|
|
273
|
+
+ (seat.briefing?.last_rejected_ack_reason
|
|
274
|
+
? ' — rejected acknowledgement recorded; inspect role logs' : '')
|
|
275
|
+
+ (seat.launch?.error ? ' — launch failure recorded; inspect role logs' : '');
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
115
278
|
}
|
|
279
|
+
return sections;
|
|
116
280
|
}
|
|
117
281
|
async function provisionRoom(cfg, input) {
|
|
118
282
|
const rooms = cfg.rooms;
|
|
@@ -284,7 +448,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
284
448
|
if (opts.template) {
|
|
285
449
|
const t = resolveTemplate(opts.template, allTemplates(cfg));
|
|
286
450
|
if (!t)
|
|
287
|
-
|
|
451
|
+
throw taskRoomPublicError('template_not_found', { template: opts.template });
|
|
288
452
|
const snap = snapshotTemplate(t);
|
|
289
453
|
templateRef = { name: snap.name, version: snap.version, content_hash: snap.content_hash };
|
|
290
454
|
}
|
|
@@ -336,12 +500,15 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
336
500
|
console.log(JSON.stringify({ schema_version: 1, task: record }, null, 2));
|
|
337
501
|
return;
|
|
338
502
|
}
|
|
339
|
-
console.log(
|
|
340
|
-
|
|
341
|
-
|
|
503
|
+
console.log(taskActionMarkdown('Task created', record, [
|
|
504
|
+
{ label: 'Title', value: record.title },
|
|
505
|
+
...(templateRef ? [{ label: 'Template', value: `${templateRef.name}@${templateRef.version}`, kind: 'code' }] : []),
|
|
506
|
+
]));
|
|
342
507
|
}
|
|
343
508
|
catch (e) {
|
|
344
|
-
|
|
509
|
+
if (opts.json)
|
|
510
|
+
die(e);
|
|
511
|
+
dieTaskRoom(e);
|
|
345
512
|
}
|
|
346
513
|
});
|
|
347
514
|
cOpt(taskCmd.command('list'))
|
|
@@ -359,17 +526,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
359
526
|
console.log(JSON.stringify({ schema_version: 1, tasks }, null, 2));
|
|
360
527
|
return;
|
|
361
528
|
}
|
|
362
|
-
|
|
363
|
-
console.log('No tasks.');
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
for (const t of tasks) {
|
|
367
|
-
const blocked = t.blocked ? ` [BLOCKED: ${t.blocked.reason}]` : '';
|
|
368
|
-
console.log(`${t.task_id} ${t.state}${blocked} ${t.title}`);
|
|
369
|
-
}
|
|
529
|
+
console.log(taskListMarkdown(tasks));
|
|
370
530
|
}
|
|
371
531
|
catch (e) {
|
|
372
|
-
|
|
532
|
+
if (opts.json)
|
|
533
|
+
die(e);
|
|
534
|
+
dieTaskRoom(e);
|
|
373
535
|
}
|
|
374
536
|
});
|
|
375
537
|
taskCmd.command('show <id>')
|
|
@@ -385,33 +547,34 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
385
547
|
}, null, 2));
|
|
386
548
|
return;
|
|
387
549
|
}
|
|
388
|
-
console.log(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
console.log(`Outcome: ${t.outcome.summary}`);
|
|
550
|
+
console.log(renderMarkdownResult({
|
|
551
|
+
icon: '📋', title: 'Task details',
|
|
552
|
+
fields: [
|
|
553
|
+
{ label: 'ID', value: t.task_id, kind: 'code' },
|
|
554
|
+
{ label: 'Title', value: t.title },
|
|
555
|
+
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
556
|
+
...(t.blocked ? [{ label: 'Blocked', value: t.blocked.reason }] : []),
|
|
557
|
+
...(t.template ? [{ label: 'Template', value: `${t.template.name}@${t.template.version}`, kind: 'code' }] : []),
|
|
558
|
+
...(t.room_id ? [{ label: 'Room', value: t.room_id, kind: 'code' }] : []),
|
|
559
|
+
...(t.room_identity_cid ? [{ label: 'Room CID', value: t.room_identity_cid, kind: 'code' }] : []),
|
|
560
|
+
{ label: 'Origin', value: t.origin.type },
|
|
561
|
+
{ label: 'Created', value: t.created_at, kind: 'code' },
|
|
562
|
+
...(t.started_at ? [{ label: 'Started', value: t.started_at, kind: 'code' }] : []),
|
|
563
|
+
...(t.ended_at ? [{ label: 'Ended', value: t.ended_at, kind: 'code' }] : []),
|
|
564
|
+
...(t.outcome ? [{ label: 'Outcome', value: t.outcome.summary, multiline: true }] : []),
|
|
565
|
+
],
|
|
566
|
+
sections: [
|
|
567
|
+
...(t.member_roles.length ? [{
|
|
568
|
+
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`),
|
|
569
|
+
}] : []),
|
|
570
|
+
...(room ? roomStartupSections(room) : []),
|
|
571
|
+
],
|
|
572
|
+
}));
|
|
412
573
|
}
|
|
413
574
|
catch (e) {
|
|
414
|
-
|
|
575
|
+
if (opts.json)
|
|
576
|
+
die(e);
|
|
577
|
+
dieTaskRoom(e);
|
|
415
578
|
}
|
|
416
579
|
});
|
|
417
580
|
cOpt(taskCmd.command('start <id>'))
|
|
@@ -424,7 +587,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
424
587
|
if (t.template && !t.room_id) {
|
|
425
588
|
const template = resolveRoomTemplate(cfg, t.template.name);
|
|
426
589
|
if (!template || template.content_hash !== t.template.content_hash)
|
|
427
|
-
throw
|
|
590
|
+
throw taskRoomPublicError('task_template_drift', {
|
|
591
|
+
template: `${t.template.name}@${t.template.version}`,
|
|
592
|
+
});
|
|
428
593
|
await provisionRoom(cfg, {
|
|
429
594
|
name: t.title,
|
|
430
595
|
goal: t.title,
|
|
@@ -441,10 +606,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
441
606
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
442
607
|
return;
|
|
443
608
|
}
|
|
444
|
-
console.log(
|
|
609
|
+
console.log(taskActionMarkdown('Task started', t));
|
|
445
610
|
}
|
|
446
611
|
catch (e) {
|
|
447
|
-
|
|
612
|
+
if (opts.json)
|
|
613
|
+
die(e);
|
|
614
|
+
dieTaskRoom(e);
|
|
448
615
|
}
|
|
449
616
|
});
|
|
450
617
|
taskCmd.command('block <id>')
|
|
@@ -458,10 +625,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
458
625
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
459
626
|
return;
|
|
460
627
|
}
|
|
461
|
-
console.log(
|
|
628
|
+
console.log(taskActionMarkdown('Task blocked', t, [{ label: 'Reason', value: opts.reason }]));
|
|
462
629
|
}
|
|
463
630
|
catch (e) {
|
|
464
|
-
|
|
631
|
+
if (opts.json)
|
|
632
|
+
die(e);
|
|
633
|
+
dieTaskRoom(e);
|
|
465
634
|
}
|
|
466
635
|
});
|
|
467
636
|
taskCmd.command('unblock <id>')
|
|
@@ -474,10 +643,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
474
643
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
475
644
|
return;
|
|
476
645
|
}
|
|
477
|
-
console.log(
|
|
646
|
+
console.log(taskActionMarkdown('Task unblocked', t));
|
|
478
647
|
}
|
|
479
648
|
catch (e) {
|
|
480
|
-
|
|
649
|
+
if (opts.json)
|
|
650
|
+
die(e);
|
|
651
|
+
dieTaskRoom(e);
|
|
481
652
|
}
|
|
482
653
|
});
|
|
483
654
|
taskCmd.command('review <id>')
|
|
@@ -490,10 +661,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
490
661
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
491
662
|
return;
|
|
492
663
|
}
|
|
493
|
-
console.log(
|
|
664
|
+
console.log(taskActionMarkdown('Task ready for review', t));
|
|
494
665
|
}
|
|
495
666
|
catch (e) {
|
|
496
|
-
|
|
667
|
+
if (opts.json)
|
|
668
|
+
die(e);
|
|
669
|
+
dieTaskRoom(e);
|
|
497
670
|
}
|
|
498
671
|
});
|
|
499
672
|
cOpt(taskCmd.command('done <id>'))
|
|
@@ -532,13 +705,19 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
532
705
|
return;
|
|
533
706
|
}
|
|
534
707
|
if (settled.timedOut) {
|
|
535
|
-
console.log(
|
|
708
|
+
console.log(renderMarkdownFailure({
|
|
709
|
+
kind: 'pending', subject: `task done ${id}`,
|
|
710
|
+
detail: 'The completion request was accepted and is still being settled.',
|
|
711
|
+
action: `Run ours-fleet task recover ${id}.`,
|
|
712
|
+
}));
|
|
536
713
|
return;
|
|
537
714
|
}
|
|
538
|
-
console.log(
|
|
715
|
+
console.log(taskActionMarkdown('Task completed', t, t.outcome ? [{ label: 'Summary', value: t.outcome.summary, multiline: true }] : []));
|
|
539
716
|
}
|
|
540
717
|
catch (e) {
|
|
541
|
-
|
|
718
|
+
if (opts.json)
|
|
719
|
+
die(e);
|
|
720
|
+
dieTaskRoom(e);
|
|
542
721
|
}
|
|
543
722
|
});
|
|
544
723
|
cOpt(taskCmd.command('cancel <id> <confirm-id>'))
|
|
@@ -547,7 +726,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
547
726
|
.action(async (id, confirmId, opts) => {
|
|
548
727
|
try {
|
|
549
728
|
if (id !== confirmId)
|
|
550
|
-
|
|
729
|
+
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
551
730
|
const current = getTask(id);
|
|
552
731
|
if (!TASK_CANCELLABLE_STATES.includes(current.state))
|
|
553
732
|
throw new TaskStateError(`cannot cancel a '${current.state}' task`);
|
|
@@ -565,13 +744,19 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
565
744
|
return;
|
|
566
745
|
}
|
|
567
746
|
if (settled.timedOut) {
|
|
568
|
-
console.log(
|
|
747
|
+
console.log(renderMarkdownFailure({
|
|
748
|
+
kind: 'pending', subject: `task cancel ${id} ${id}`,
|
|
749
|
+
detail: 'The cancellation request was accepted and is still being settled.',
|
|
750
|
+
action: `Run ours-fleet task recover ${id}.`,
|
|
751
|
+
}));
|
|
569
752
|
return;
|
|
570
753
|
}
|
|
571
|
-
console.log(
|
|
754
|
+
console.log(taskActionMarkdown('Task cancelled', t));
|
|
572
755
|
}
|
|
573
756
|
catch (e) {
|
|
574
|
-
|
|
757
|
+
if (opts.json)
|
|
758
|
+
die(e);
|
|
759
|
+
dieTaskRoom(e);
|
|
575
760
|
}
|
|
576
761
|
});
|
|
577
762
|
cOpt(taskCmd.command('delete <id> <confirm-id>'))
|
|
@@ -580,18 +765,21 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
580
765
|
.action((id, confirmId, opts) => {
|
|
581
766
|
try {
|
|
582
767
|
if (id !== confirmId)
|
|
583
|
-
|
|
768
|
+
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
584
769
|
const deleted = deleteTask(id);
|
|
585
770
|
if (opts.json) {
|
|
586
771
|
console.log(JSON.stringify({ schema_version: 1, task_id: id, deleted }, null, 2));
|
|
587
772
|
return;
|
|
588
773
|
}
|
|
589
|
-
console.log(
|
|
590
|
-
?
|
|
591
|
-
:
|
|
774
|
+
console.log(renderMarkdownResult({
|
|
775
|
+
icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
|
|
776
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
777
|
+
}));
|
|
592
778
|
}
|
|
593
779
|
catch (e) {
|
|
594
|
-
|
|
780
|
+
if (opts.json)
|
|
781
|
+
die(e);
|
|
782
|
+
dieTaskRoom(e);
|
|
595
783
|
}
|
|
596
784
|
});
|
|
597
785
|
cOpt(taskCmd.command('recover <id>'))
|
|
@@ -661,20 +849,26 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
661
849
|
console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
|
|
662
850
|
return;
|
|
663
851
|
}
|
|
664
|
-
console.log(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
852
|
+
console.log(renderMarkdownResult({
|
|
853
|
+
icon: '🛟', title: 'Task recovery',
|
|
854
|
+
fields: [
|
|
855
|
+
{ label: 'Task', value: t.task_id, kind: 'code' },
|
|
856
|
+
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
857
|
+
...(room ? [
|
|
858
|
+
{ label: 'Room', value: room.room_id, kind: 'code' },
|
|
859
|
+
{ label: 'Room status', value: roomStatus(room.state), kind: 'markdown' },
|
|
860
|
+
{ label: 'Saga', value: room.saga.phase, kind: 'code' },
|
|
861
|
+
] : []),
|
|
862
|
+
],
|
|
863
|
+
sections: result.recovery_actions.length
|
|
864
|
+
? [{ heading: 'Next steps', items: result.recovery_actions }]
|
|
865
|
+
: [{ heading: 'Result', items: ['No automated recovery action is available.'] }],
|
|
866
|
+
}));
|
|
675
867
|
}
|
|
676
868
|
catch (e) {
|
|
677
|
-
|
|
869
|
+
if (opts.json)
|
|
870
|
+
die(e);
|
|
871
|
+
dieTaskRoom(e);
|
|
678
872
|
}
|
|
679
873
|
});
|
|
680
874
|
cOpt(taskCmd.command('_settle <id>', { hidden: true }))
|
|
@@ -688,11 +882,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
688
882
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
689
883
|
return;
|
|
690
884
|
}
|
|
691
|
-
console.log(
|
|
885
|
+
console.log(taskActionMarkdown('Task terminal action settled', t));
|
|
692
886
|
}
|
|
693
887
|
catch (e) {
|
|
694
888
|
await recordTaskTerminalIntentError(id, errorText(e), `External settle worker failed. Retry task recover ${id}.`).catch(() => { });
|
|
695
|
-
|
|
889
|
+
if (opts.json)
|
|
890
|
+
die(e);
|
|
891
|
+
dieTaskRoom(e);
|
|
696
892
|
}
|
|
697
893
|
});
|
|
698
894
|
cOpt(taskCmd.command('work <id>'))
|
|
@@ -704,24 +900,27 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
704
900
|
const cfg = loadCfg(opts);
|
|
705
901
|
let t = getTask(id);
|
|
706
902
|
if (TASK_TERMINAL_STATES.includes(t.state))
|
|
707
|
-
|
|
903
|
+
throw taskRoomPublicError('task_terminal', { task: id, state: t.state });
|
|
708
904
|
if (t.state === 'active' && t.room_id) {
|
|
709
905
|
// An explicit --template must agree with the room the task already
|
|
710
906
|
// runs in; a conflicting override is an error, not a silent no-op.
|
|
711
907
|
if (opts.template) {
|
|
712
908
|
const override = resolveTemplate(opts.template, allTemplates(cfg));
|
|
713
909
|
if (!override)
|
|
714
|
-
|
|
910
|
+
throw taskRoomPublicError('template_not_found', { template: opts.template });
|
|
715
911
|
const overrideSnap = snapshotTemplate(override);
|
|
716
912
|
const roomSnap = getRoomRecord(t.room_id)?.template_snapshot ?? t.template;
|
|
717
913
|
if (roomSnap && (roomSnap.name !== overrideSnap.name || roomSnap.content_hash !== overrideSnap.content_hash))
|
|
718
|
-
|
|
914
|
+
throw taskRoomPublicError('template_mismatch', {
|
|
915
|
+
requested: `${overrideSnap.name}@${overrideSnap.version}`, room: t.room_id,
|
|
916
|
+
provisioned: `${roomSnap.name}@${roomSnap.version}`,
|
|
917
|
+
});
|
|
719
918
|
}
|
|
720
919
|
if (opts.json) {
|
|
721
920
|
console.log(JSON.stringify({ schema_version: 1, task: t, status: 'already_active' }, null, 2));
|
|
722
921
|
return;
|
|
723
922
|
}
|
|
724
|
-
console.log('Task already
|
|
923
|
+
console.log(taskActionMarkdown('Task already active', t, t.room_id ? [{ label: 'Room', value: t.room_id, kind: 'code' }] : []));
|
|
725
924
|
return;
|
|
726
925
|
}
|
|
727
926
|
// Select and validate the template snapshot before any state change or
|
|
@@ -738,25 +937,33 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
738
937
|
const resolved = durableSnapshot && !opts.template
|
|
739
938
|
? undefined : resolveTemplate(templateName, allTemplates(cfg));
|
|
740
939
|
if (!durableSnapshot && !resolved)
|
|
741
|
-
|
|
940
|
+
throw taskRoomPublicError('template_not_found', { template: templateName });
|
|
742
941
|
if (opts.template && !resolved)
|
|
743
|
-
|
|
942
|
+
throw taskRoomPublicError('template_not_found', { template: templateName });
|
|
744
943
|
if (durableSnapshot && resolved) {
|
|
745
944
|
const requested = snapshotTemplate(resolved);
|
|
746
945
|
if (requested.name !== durableSnapshot.name
|
|
747
946
|
|| requested.content_hash !== durableSnapshot.content_hash) {
|
|
748
|
-
|
|
947
|
+
throw taskRoomPublicError('template_mismatch', {
|
|
948
|
+
requested: `${requested.name}@${requested.version}`, room: existingRoom.room_id,
|
|
949
|
+
provisioned: `${durableSnapshot.name}@${durableSnapshot.version}`,
|
|
950
|
+
});
|
|
749
951
|
}
|
|
750
952
|
}
|
|
751
953
|
const snap = durableSnapshot ?? snapshotTemplate(resolved);
|
|
752
954
|
if (!opts.template && t.template && snap.content_hash !== t.template.content_hash)
|
|
753
|
-
|
|
955
|
+
throw taskRoomPublicError('task_template_drift', {
|
|
956
|
+
template: `${t.template.name}@${t.template.version}`,
|
|
957
|
+
});
|
|
754
958
|
// A provisioned room is pinned to its snapshot: never resume or re-pin
|
|
755
959
|
// an existing room under a different template.
|
|
756
960
|
if (t.room_id) {
|
|
757
961
|
const roomSnap = getRoomRecord(t.room_id)?.template_snapshot;
|
|
758
962
|
if (roomSnap && (roomSnap.name !== snap.name || roomSnap.content_hash !== snap.content_hash))
|
|
759
|
-
|
|
963
|
+
throw taskRoomPublicError('template_mismatch', {
|
|
964
|
+
requested: `${snap.name}@${snap.version}`, room: t.room_id,
|
|
965
|
+
provisioned: `${roomSnap.name}@${roomSnap.version}`,
|
|
966
|
+
});
|
|
760
967
|
}
|
|
761
968
|
let templateRef = t.template;
|
|
762
969
|
if (!templateRef || templateRef.name !== snap.name || templateRef.content_hash !== snap.content_hash) {
|
|
@@ -815,26 +1022,30 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
815
1022
|
t = getTask(t.task_id);
|
|
816
1023
|
}
|
|
817
1024
|
else {
|
|
818
|
-
|
|
1025
|
+
throw taskRoomPublicError('task_non_resumable', { task: id, room: t.room_id });
|
|
819
1026
|
}
|
|
820
1027
|
}
|
|
821
1028
|
if (opts.json) {
|
|
822
1029
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
823
1030
|
return;
|
|
824
1031
|
}
|
|
825
|
-
console.log(
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1032
|
+
console.log(renderMarkdownResult({
|
|
1033
|
+
icon: '🛠️', title: 'Task work ready',
|
|
1034
|
+
fields: [
|
|
1035
|
+
{ label: 'ID', value: t.task_id, kind: 'code' },
|
|
1036
|
+
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
1037
|
+
...(templateRef ? [{ label: 'Template', value: `${templateRef.name}@${templateRef.version}`, kind: 'code' }] : []),
|
|
1038
|
+
...(t.room_id ? [{ label: 'Room', value: t.room_id, kind: 'code' }] : []),
|
|
1039
|
+
],
|
|
1040
|
+
sections: t.member_roles.length ? [{
|
|
1041
|
+
heading: 'Agents', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)}`),
|
|
1042
|
+
}] : [],
|
|
1043
|
+
}));
|
|
835
1044
|
}
|
|
836
1045
|
catch (e) {
|
|
837
|
-
|
|
1046
|
+
if (opts.json)
|
|
1047
|
+
die(e);
|
|
1048
|
+
dieTaskRoom(e);
|
|
838
1049
|
}
|
|
839
1050
|
});
|
|
840
1051
|
cOpt(taskCmd.command('finish <id>'))
|
|
@@ -850,7 +1061,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
850
1061
|
const outcome = summary ? { summary } : undefined;
|
|
851
1062
|
let t = getTask(id);
|
|
852
1063
|
if (TASK_TERMINAL_STATES.includes(t.state))
|
|
853
|
-
|
|
1064
|
+
throw taskRoomPublicError('task_terminal_already', { task: id, state: t.state });
|
|
854
1065
|
if (t.state === 'active') {
|
|
855
1066
|
reviewTask(id);
|
|
856
1067
|
}
|
|
@@ -866,13 +1077,19 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
866
1077
|
return;
|
|
867
1078
|
}
|
|
868
1079
|
if (settled.timedOut) {
|
|
869
|
-
console.log(
|
|
1080
|
+
console.log(renderMarkdownFailure({
|
|
1081
|
+
kind: 'pending', subject: `task finish ${id}`,
|
|
1082
|
+
detail: 'The finish request was accepted and is still being settled.',
|
|
1083
|
+
action: `Run ours-fleet task recover ${id}.`,
|
|
1084
|
+
}));
|
|
870
1085
|
return;
|
|
871
1086
|
}
|
|
872
|
-
console.log(
|
|
1087
|
+
console.log(taskActionMarkdown('Task finished', t, t.outcome ? [{ label: 'Summary', value: t.outcome.summary, multiline: true }] : []));
|
|
873
1088
|
}
|
|
874
1089
|
catch (e) {
|
|
875
|
-
|
|
1090
|
+
if (opts.json)
|
|
1091
|
+
die(e);
|
|
1092
|
+
dieTaskRoom(e);
|
|
876
1093
|
}
|
|
877
1094
|
});
|
|
878
1095
|
}
|
|
@@ -896,7 +1113,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
896
1113
|
if (opts.template) {
|
|
897
1114
|
const t = resolveTemplate(opts.template, allTemplates(cfg));
|
|
898
1115
|
if (!t)
|
|
899
|
-
|
|
1116
|
+
throw taskRoomPublicError('template_not_found', { template: opts.template });
|
|
900
1117
|
templateSnapshot = snapshotTemplate(t);
|
|
901
1118
|
}
|
|
902
1119
|
const record = await provisionRoom(cfg, {
|
|
@@ -909,12 +1126,15 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
909
1126
|
console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
|
|
910
1127
|
return;
|
|
911
1128
|
}
|
|
912
|
-
console.log(
|
|
913
|
-
|
|
914
|
-
|
|
1129
|
+
console.log(roomActionMarkdown('Room created', record, [
|
|
1130
|
+
{ label: 'Name', value: record.room_name },
|
|
1131
|
+
...(templateSnapshot ? [{ label: 'Template', value: `${templateSnapshot.name}@${templateSnapshot.version}`, kind: 'code' }] : []),
|
|
1132
|
+
]));
|
|
915
1133
|
}
|
|
916
1134
|
catch (e) {
|
|
917
|
-
|
|
1135
|
+
if (opts.json)
|
|
1136
|
+
die(e);
|
|
1137
|
+
dieTaskRoom(e);
|
|
918
1138
|
}
|
|
919
1139
|
});
|
|
920
1140
|
cOpt(roomCmd.command('list'))
|
|
@@ -925,7 +1145,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
925
1145
|
try {
|
|
926
1146
|
const cfg = loadCfg(opts);
|
|
927
1147
|
if (opts.state && !['active', 'provisioning', 'all'].includes(opts.state))
|
|
928
|
-
throw
|
|
1148
|
+
throw taskRoomPublicError('room_filter');
|
|
929
1149
|
const stateFilter = opts.state === 'active' || opts.state === 'provisioning'
|
|
930
1150
|
? opts.state : undefined;
|
|
931
1151
|
const adapter = coworkFor(cfg);
|
|
@@ -944,15 +1164,16 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
944
1164
|
console.log(JSON.stringify({ schema_version: 1, rooms }, null, 2));
|
|
945
1165
|
return;
|
|
946
1166
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
console.log(`${r.room_id} ${r.state} ${r.room_name}${r.orchestration?.task_id ? ` (task: ${r.orchestration.task_id})` : ''}`);
|
|
1167
|
+
console.log(renderMarkdownList({
|
|
1168
|
+
icon: '🏠', title: 'Rooms', empty: 'No rooms found.',
|
|
1169
|
+
records: rooms.map(r => `${roomStatus(r.state)} ${markdownCode(r.room_id)} — ${markdownProse(r.room_name)}`
|
|
1170
|
+
+ (r.orchestration?.task_id ? ` — Task ${markdownCode(r.orchestration.task_id)}` : '')),
|
|
1171
|
+
}));
|
|
953
1172
|
}
|
|
954
1173
|
catch (e) {
|
|
955
|
-
|
|
1174
|
+
if (opts.json)
|
|
1175
|
+
die(e);
|
|
1176
|
+
dieTaskRoom(e);
|
|
956
1177
|
}
|
|
957
1178
|
});
|
|
958
1179
|
cOpt(roomCmd.command('show <id>'))
|
|
@@ -963,39 +1184,40 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
963
1184
|
const cfg = loadCfg(opts);
|
|
964
1185
|
const tracked = getRoomRecord(id);
|
|
965
1186
|
if (tracked?.state === 'closing' || tracked?.state === 'closed')
|
|
966
|
-
|
|
1187
|
+
throw taskRoomPublicError('room_not_found', { room: id });
|
|
967
1188
|
const cowork = await coworkFor(cfg).getRoom(id);
|
|
968
1189
|
if (!cowork || cowork.state === 'closing' || cowork.state === 'closed')
|
|
969
|
-
|
|
1190
|
+
throw taskRoomPublicError('room_not_found', { room: id });
|
|
970
1191
|
const r = tracked;
|
|
971
1192
|
if (opts.json) {
|
|
972
1193
|
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null }, null, 2));
|
|
973
1194
|
return;
|
|
974
1195
|
}
|
|
975
|
-
console.log(
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
console.log(`Tracked by Fleet since: ${r.created_at}`);
|
|
1196
|
+
console.log(renderMarkdownResult({
|
|
1197
|
+
icon: '🏠', title: 'Room details',
|
|
1198
|
+
fields: [
|
|
1199
|
+
{ label: 'ID', value: cowork.room_id, kind: 'code' },
|
|
1200
|
+
{ label: 'Name', value: cowork.room_name },
|
|
1201
|
+
{ label: 'Status', value: roomStatus(cowork.state), kind: 'markdown' },
|
|
1202
|
+
{ label: 'Identity CID', value: cowork.identity_cid, kind: 'code' },
|
|
1203
|
+
...(r?.task_id ? [{ label: 'Task', value: r.task_id, kind: 'code' }] : []),
|
|
1204
|
+
...(r ? [{ label: 'Saga', value: `${r.saga.phase} (step ${r.saga.step_index})` }] : []),
|
|
1205
|
+
...(r?.provisioning_detail ? [{ label: 'Detail', value: r.provisioning_detail }] : []),
|
|
1206
|
+
...(r?.saga.error ? [{ label: 'Last error', value: 'Provisioning failure recorded; inspect role logs.' }] : []),
|
|
1207
|
+
...(r ? [{ label: 'Tracked since', value: r.created_at, kind: 'code' }] : []),
|
|
1208
|
+
],
|
|
1209
|
+
sections: [
|
|
1210
|
+
...(cowork.seats.length ? [{
|
|
1211
|
+
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`),
|
|
1212
|
+
}] : []),
|
|
1213
|
+
...(r ? roomStartupSections(r) : []),
|
|
1214
|
+
],
|
|
1215
|
+
}));
|
|
996
1216
|
}
|
|
997
1217
|
catch (e) {
|
|
998
|
-
|
|
1218
|
+
if (opts.json)
|
|
1219
|
+
die(e);
|
|
1220
|
+
dieTaskRoom(e);
|
|
999
1221
|
}
|
|
1000
1222
|
});
|
|
1001
1223
|
cOpt(roomCmd.command('open <id>'))
|
|
@@ -1006,20 +1228,28 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1006
1228
|
const cfg = loadCfg(opts);
|
|
1007
1229
|
const tracked = getRoomRecord(id);
|
|
1008
1230
|
if (tracked?.state === 'closing' || tracked?.state === 'closed')
|
|
1009
|
-
|
|
1231
|
+
throw taskRoomPublicError('room_not_found', { room: id });
|
|
1010
1232
|
const room = await coworkFor(cfg).getRoom(id);
|
|
1011
1233
|
if (!room || room.state === 'closing' || room.state === 'closed')
|
|
1012
|
-
|
|
1234
|
+
throw taskRoomPublicError('room_not_found', { room: id });
|
|
1013
1235
|
const url = `http://localhost:4460/room/${id}`;
|
|
1014
1236
|
if (opts.json) {
|
|
1015
1237
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, url, room_name: room.room_name }, null, 2));
|
|
1016
1238
|
return;
|
|
1017
1239
|
}
|
|
1018
|
-
console.log(
|
|
1019
|
-
|
|
1240
|
+
console.log(renderMarkdownResult({
|
|
1241
|
+
icon: '🏠', title: 'Room console',
|
|
1242
|
+
fields: [
|
|
1243
|
+
{ label: 'Room', value: id, kind: 'code' },
|
|
1244
|
+
{ label: 'Name', value: room.room_name },
|
|
1245
|
+
{ label: 'Local console', value: url, kind: 'code' },
|
|
1246
|
+
],
|
|
1247
|
+
}));
|
|
1020
1248
|
}
|
|
1021
1249
|
catch (e) {
|
|
1022
|
-
|
|
1250
|
+
if (opts.json)
|
|
1251
|
+
die(e);
|
|
1252
|
+
dieTaskRoom(e);
|
|
1023
1253
|
}
|
|
1024
1254
|
});
|
|
1025
1255
|
cOpt(roomCmd.command('members <id>'))
|
|
@@ -1030,11 +1260,11 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1030
1260
|
const cfg = loadCfg(opts);
|
|
1031
1261
|
const r = getRoomRecord(id);
|
|
1032
1262
|
if (r?.state === 'closing' || r?.state === 'closed')
|
|
1033
|
-
|
|
1263
|
+
throw taskRoomPublicError('room_not_found', { room: id });
|
|
1034
1264
|
const adapter = coworkFor(cfg);
|
|
1035
1265
|
const room = await adapter.getRoom(id);
|
|
1036
1266
|
if (!room || room.state === 'closing' || room.state === 'closed')
|
|
1037
|
-
|
|
1267
|
+
throw taskRoomPublicError('room_not_found', { room: id });
|
|
1038
1268
|
const members = await adapter.getSeats(id);
|
|
1039
1269
|
if (opts.json) {
|
|
1040
1270
|
console.log(JSON.stringify({
|
|
@@ -1045,28 +1275,34 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1045
1275
|
}, null, 2));
|
|
1046
1276
|
return;
|
|
1047
1277
|
}
|
|
1048
|
-
console.log(
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1278
|
+
console.log(renderMarkdownResult({
|
|
1279
|
+
icon: '👥', title: 'Room members',
|
|
1280
|
+
fields: [
|
|
1281
|
+
{ label: 'Room', value: id, kind: 'code' },
|
|
1282
|
+
...(r ? [{ label: 'Name', value: r.room_name }] : []),
|
|
1283
|
+
...(r?.owner_seat_cid ? [{ label: 'Owner', value: r.owner_seat_cid, kind: 'code' }] : []),
|
|
1284
|
+
],
|
|
1285
|
+
sections: [{
|
|
1286
|
+
heading: 'Members',
|
|
1287
|
+
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`) }
|
|
1288
|
+
: { items: ['No members found.'] }),
|
|
1289
|
+
}],
|
|
1290
|
+
}));
|
|
1057
1291
|
}
|
|
1058
1292
|
catch (e) {
|
|
1059
|
-
|
|
1293
|
+
if (opts.json)
|
|
1294
|
+
die(e);
|
|
1295
|
+
dieTaskRoom(e);
|
|
1060
1296
|
}
|
|
1061
1297
|
});
|
|
1062
1298
|
const deleteAction = async (id, confirmId, opts) => {
|
|
1063
1299
|
try {
|
|
1064
1300
|
if (id !== confirmId)
|
|
1065
|
-
|
|
1301
|
+
throw taskRoomPublicError('room_confirmation_mismatch');
|
|
1066
1302
|
coworkFor(loadCfg(opts));
|
|
1067
1303
|
const existing = getRoomRecord(id);
|
|
1068
1304
|
if (!existing)
|
|
1069
|
-
throw
|
|
1305
|
+
throw taskRoomPublicError('room_record_not_found', { room: id });
|
|
1070
1306
|
await acceptManagedRoomClose(id);
|
|
1071
1307
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1072
1308
|
if (opts.json) {
|
|
@@ -1074,13 +1310,22 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1074
1310
|
return;
|
|
1075
1311
|
}
|
|
1076
1312
|
if (settled.timedOut) {
|
|
1077
|
-
console.log(
|
|
1313
|
+
console.log(renderMarkdownFailure({
|
|
1314
|
+
kind: 'pending', subject: `room delete ${id} ${id}`,
|
|
1315
|
+
detail: 'The deletion request was accepted and is still being settled.',
|
|
1316
|
+
action: `Run ours-fleet room delete ${id} ${id} or room recover ${id}.`,
|
|
1317
|
+
}));
|
|
1078
1318
|
return;
|
|
1079
1319
|
}
|
|
1080
|
-
console.log(
|
|
1320
|
+
console.log(renderMarkdownResult({
|
|
1321
|
+
icon: '🗑️', title: 'Room deleted',
|
|
1322
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1323
|
+
}));
|
|
1081
1324
|
}
|
|
1082
1325
|
catch (e) {
|
|
1083
|
-
|
|
1326
|
+
if (opts.json)
|
|
1327
|
+
die(e);
|
|
1328
|
+
dieTaskRoom(e);
|
|
1084
1329
|
}
|
|
1085
1330
|
};
|
|
1086
1331
|
cOpt(roomCmd.command('delete <id> <confirm-id>'))
|
|
@@ -1110,9 +1355,16 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1110
1355
|
}, null, 2));
|
|
1111
1356
|
return;
|
|
1112
1357
|
}
|
|
1113
|
-
console.log(
|
|
1114
|
-
|
|
1115
|
-
:
|
|
1358
|
+
console.log(renderMarkdownResult({
|
|
1359
|
+
icon: settled.timedOut ? '⏳' : '🗑️',
|
|
1360
|
+
title: settled.timedOut ? 'Room deletion pending' : 'Room deleted',
|
|
1361
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1362
|
+
sections: [{ heading: 'Next step', items: [
|
|
1363
|
+
settled.timedOut
|
|
1364
|
+
? `Run ours-fleet room delete ${id} ${id} or room recover ${id} again.`
|
|
1365
|
+
: 'No recovery action is needed.',
|
|
1366
|
+
] }],
|
|
1367
|
+
}));
|
|
1116
1368
|
return;
|
|
1117
1369
|
}
|
|
1118
1370
|
const cowork = await adapter.recoverRoom(id);
|
|
@@ -1138,9 +1390,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1138
1390
|
}
|
|
1139
1391
|
const actions = [];
|
|
1140
1392
|
if (r?.saga.error)
|
|
1141
|
-
actions.push(
|
|
1393
|
+
actions.push('A provisioning failure is recorded; inspect role logs for diagnostics.');
|
|
1142
1394
|
if (r?.saga.recovery_hint)
|
|
1143
|
-
actions.push(
|
|
1395
|
+
actions.push('Recovery guidance is recorded; inspect role logs for diagnostics.');
|
|
1144
1396
|
if (r?.provisioning_detail === 'waiting_cowork')
|
|
1145
1397
|
actions.push('Check ours-cowork service status');
|
|
1146
1398
|
if (r?.provisioning_detail === 'waiting_owner_invite')
|
|
@@ -1180,18 +1432,22 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1180
1432
|
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null, recovery_actions: actions }, null, 2));
|
|
1181
1433
|
return;
|
|
1182
1434
|
}
|
|
1183
|
-
console.log(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1435
|
+
console.log(renderMarkdownResult({
|
|
1436
|
+
icon: '🛟', title: 'Room recovery',
|
|
1437
|
+
fields: [
|
|
1438
|
+
{ label: 'Room', value: cowork.room_id, kind: 'code' },
|
|
1439
|
+
{ label: 'Status', value: roomStatus(cowork.state), kind: 'markdown' },
|
|
1440
|
+
...(r ? [{ label: 'Saga', value: r.saga.phase, kind: 'code' }] : []),
|
|
1441
|
+
],
|
|
1442
|
+
sections: actions.length
|
|
1443
|
+
? [{ heading: 'Next steps', items: actions }]
|
|
1444
|
+
: [{ heading: 'Result', items: ['No recovery action is needed.'] }],
|
|
1445
|
+
}));
|
|
1192
1446
|
}
|
|
1193
1447
|
catch (e) {
|
|
1194
|
-
|
|
1448
|
+
if (opts.json)
|
|
1449
|
+
die(e);
|
|
1450
|
+
dieTaskRoom(e);
|
|
1195
1451
|
}
|
|
1196
1452
|
});
|
|
1197
1453
|
const internalDeleteAction = async (id, opts) => {
|
|
@@ -1202,11 +1458,16 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1202
1458
|
console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
|
|
1203
1459
|
return;
|
|
1204
1460
|
}
|
|
1205
|
-
console.log(
|
|
1461
|
+
console.log(renderMarkdownResult({
|
|
1462
|
+
icon: '🗑️', title: 'Room deleted',
|
|
1463
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1464
|
+
}));
|
|
1206
1465
|
}
|
|
1207
1466
|
catch (e) {
|
|
1208
1467
|
await recordManagedRoomCloseError(id, errorText(e), `External delete worker failed. Retry room delete ${id} ${id}.`).catch(() => { });
|
|
1209
|
-
|
|
1468
|
+
if (opts.json)
|
|
1469
|
+
die(e);
|
|
1470
|
+
dieTaskRoom(e);
|
|
1210
1471
|
}
|
|
1211
1472
|
};
|
|
1212
1473
|
cOpt(roomCmd.command('_delete <id>', { hidden: true }))
|