@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.20
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 +169 -54
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +6 -25
- package/dist/application/role-creation-service.js +34 -72
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +58 -6
- package/dist/application/task-room-service.js +426 -78
- package/dist/briefing.js +74 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +227 -73
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +67 -2
- package/dist/config.js +260 -30
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +171 -66
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +165 -0
- package/dist/fleet-command-audit.js +543 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/fleet-proxy.js +30 -8
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +629 -0
- package/dist/lifecycle-summary.d.ts +118 -0
- package/dist/lifecycle-summary.js +161 -0
- package/dist/loops/config.js +3 -2
- package/dist/model-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +58 -0
- package/dist/owner-channel/channel.js +321 -42
- package/dist/owner-channel/commands.d.ts +6 -1
- package/dist/owner-channel/commands.js +34 -19
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +414 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +4 -0
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +503 -75
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +20 -4
- package/dist/rooms-tasks/config.js +16 -14
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +176 -0
- package/dist/rooms-tasks/member-overrides.d.ts +27 -0
- package/dist/rooms-tasks/member-overrides.js +170 -0
- package/dist/rooms-tasks/provision.js +179 -49
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +94 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +9 -2
- package/dist/runner.js +107 -40
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +38 -0
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +6 -31
- package/dist/spawn.js +57 -234
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web/topology-promote.js +8 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +3 -2
- package/presets/brain-catalog.json +16 -0
- package/presets/fleet/agent_templates/Critic.yaml +4 -0
- package/presets/fleet/agent_templates/Developer.yaml +4 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +19 -0
- package/presets/fleet/roles/Developer.yaml +19 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
- package/presets/fleet/room_templates/pair.yaml +10 -0
- package/presets/fleet/room_templates/single.yaml +7 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { loadConfig, ConfigError } from '../config.js';
|
|
3
4
|
import { provisionMembers, getBinPath } from './provision.js';
|
|
4
5
|
import { launchFleetWorker } from './external-worker.js';
|
|
5
|
-
import { resolveTemplate, snapshotTemplate, } from './templates.js';
|
|
6
|
-
import {
|
|
6
|
+
import { resolveTemplate, sealTemplateSnapshot, snapshotTemplate, } from './templates.js';
|
|
7
|
+
import { acquireLaunchSnapshotLock, releaseLaunchSnapshot } from './launch-snapshot.js';
|
|
8
|
+
import { parseGroupedMemberArgs, readMembersFile } from './member-overrides.js';
|
|
9
|
+
const MEMBER_ARG_FLAGS = new Set([
|
|
10
|
+
'--member', '--agent-template', '--brain', '--role', '--approval', '--filesystem', '--unattended',
|
|
11
|
+
'--cwd', '--model', '--effort',
|
|
12
|
+
]);
|
|
13
|
+
export function cliMemberOverrides(membersFile, argv = process.argv.slice(2)) {
|
|
14
|
+
const grouped = [];
|
|
15
|
+
for (let i = 0; i < argv.length; i++)
|
|
16
|
+
if (MEMBER_ARG_FLAGS.has(argv[i])) {
|
|
17
|
+
grouped.push(argv[i], argv[i + 1]);
|
|
18
|
+
i += 1;
|
|
19
|
+
}
|
|
20
|
+
if (membersFile && grouped.length)
|
|
21
|
+
throw new Error('--members-file cannot be combined with grouped --member options');
|
|
22
|
+
if (membersFile)
|
|
23
|
+
return readMembersFile(membersFile);
|
|
24
|
+
return grouped.length ? parseGroupedMemberArgs(grouped) : undefined;
|
|
25
|
+
}
|
|
26
|
+
function commandArgv(command) {
|
|
27
|
+
let root = command;
|
|
28
|
+
while (root.parent)
|
|
29
|
+
root = root.parent;
|
|
30
|
+
return root.rawArgs ?? process.argv.slice(2);
|
|
31
|
+
}
|
|
32
|
+
function cliAnonymousOverride(argv) {
|
|
33
|
+
const enabled = argv.includes('--anonymous');
|
|
34
|
+
const disabled = argv.includes('--no-anonymous');
|
|
35
|
+
if (enabled && disabled)
|
|
36
|
+
throw new Error('--anonymous and --no-anonymous are mutually exclusive');
|
|
37
|
+
return enabled ? true : disabled ? false : undefined;
|
|
38
|
+
}
|
|
39
|
+
import { getTask, getDeletingTask, activateTask, TaskStateError, } from './task-state.js';
|
|
7
40
|
import { createRoomRecord, getRoomRecord, advanceSaga, setOwnerSeat, setSagaError, activateRoom, RoomStateError, } from './room-state.js';
|
|
8
41
|
import { createCoworkAdapter, CoworkProtocolError } from './cowork-adapter.js';
|
|
9
42
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
10
43
|
import { TaskRoomApplicationError, TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
11
44
|
import { TaskListError } from './task-lists.js';
|
|
45
|
+
import { recordFleetAuditFailure, recordFleetAuditPresentation, recordFleetAuditResource, } from '../fleet-command-audit.js';
|
|
46
|
+
import { renderAgentConfiguration } from '../lifecycle-summary.js';
|
|
12
47
|
class TaskRoomPublicError extends Error {
|
|
13
48
|
code;
|
|
14
49
|
fields;
|
|
@@ -41,7 +76,7 @@ function taskRoomPublicFailure(error) {
|
|
|
41
76
|
case 'template_not_found': return {
|
|
42
77
|
legacy: `template not found: ${f.template ?? 'requested-template'}`, kind: 'not_found',
|
|
43
78
|
detail: `The requested template ${f.template ?? ''}`.trim() + ' was not found.',
|
|
44
|
-
action: 'Run ours-fleet
|
|
79
|
+
action: 'Run interactive ours-fleet init to review and create a complete default setup, then run ours-fleet template list.',
|
|
45
80
|
};
|
|
46
81
|
case 'template_mismatch': return {
|
|
47
82
|
legacy: `template ${f.requested ?? 'requested-template'} does not match room ${f.room ?? 'requested-room'}'s provisioned template ${f.provisioned ?? 'recorded-template'}`,
|
|
@@ -89,8 +124,29 @@ function die(e) {
|
|
|
89
124
|
const msg = e instanceof TaskRoomPublicError
|
|
90
125
|
? taskRoomPublicFailure(e).legacy : e instanceof Error ? e.message : String(e);
|
|
91
126
|
process.stderr.write(`error: ${msg}\n`);
|
|
127
|
+
recordFleetAuditFailure(classifyTaskRoomFailure(e));
|
|
92
128
|
process.exit(1);
|
|
93
129
|
}
|
|
130
|
+
function classifyTaskRoomFailure(e) {
|
|
131
|
+
if (e instanceof TaskRoomApplicationError)
|
|
132
|
+
e = taskRoomPublicError(e.code, e.fields);
|
|
133
|
+
if (e instanceof TaskRoomPublicError || e instanceof TaskListError || e instanceof ConfigError)
|
|
134
|
+
return { class: 'validation', effect: 'not_started' };
|
|
135
|
+
if (e instanceof TaskStateError) {
|
|
136
|
+
const known = /^task not found: [A-Za-z0-9_-]{1,128}$/u.test(e.message)
|
|
137
|
+
|| e.message === 'invalid task ID: expected the canonical 17-character lowercase ID'
|
|
138
|
+
|| isKnownTaskStateMessage(e.message);
|
|
139
|
+
return known ? { class: 'validation', effect: 'not_started' }
|
|
140
|
+
: { class: 'runtime', effect: 'unknown' };
|
|
141
|
+
}
|
|
142
|
+
if (e instanceof RoomStateError) {
|
|
143
|
+
const known = /^room not found: [A-Za-z0-9_-]{1,128}$/u.test(e.message)
|
|
144
|
+
|| isKnownRoomStateMessage(e.message);
|
|
145
|
+
return known ? { class: 'validation', effect: 'not_started' }
|
|
146
|
+
: { class: 'runtime', effect: 'unknown' };
|
|
147
|
+
}
|
|
148
|
+
return { class: 'runtime', effect: 'unknown' };
|
|
149
|
+
}
|
|
94
150
|
const TASK_STATE_WORD = '(?:backlog|provisioning|active|review|done|cancelled|failed)';
|
|
95
151
|
const SAFE_ID_WORD = '[A-Za-z0-9_-]{1,128}';
|
|
96
152
|
function isKnownTaskStateMessage(message) {
|
|
@@ -104,7 +160,7 @@ function isKnownTaskStateMessage(message) {
|
|
|
104
160
|
new RegExp(`^task ${SAFE_ID_WORD} is not tied to room ${SAFE_ID_WORD}$`, 'u'),
|
|
105
161
|
new RegExp(`^task ${SAFE_ID_WORD} has no terminal intent$`, 'u'),
|
|
106
162
|
new RegExp(`^task ${SAFE_ID_WORD} reached terminal state '${TASK_STATE_WORD}' outside its intent$`, 'u'),
|
|
107
|
-
new RegExp(`^
|
|
163
|
+
new RegExp(`^task ${SAFE_ID_WORD} is pending deletion; run 'ours-fleet task delete ${SAFE_ID_WORD} ${SAFE_ID_WORD}' to retry cleanup$`, 'u'),
|
|
108
164
|
].some(pattern => pattern.test(message));
|
|
109
165
|
}
|
|
110
166
|
function isKnownRoomStateMessage(message) {
|
|
@@ -127,6 +183,7 @@ function dieTaskRoom(e) {
|
|
|
127
183
|
action: failure.action,
|
|
128
184
|
});
|
|
129
185
|
process.stderr.write(`${output}\n`);
|
|
186
|
+
recordFleetAuditFailure({ class: 'validation', effect: 'not_started' });
|
|
130
187
|
process.exit(1);
|
|
131
188
|
}
|
|
132
189
|
if (e instanceof TaskListError) {
|
|
@@ -142,6 +199,7 @@ function dieTaskRoom(e) {
|
|
|
142
199
|
: 'Use a valid NFC-normalized name of at most 64 Unicode code points.',
|
|
143
200
|
});
|
|
144
201
|
process.stderr.write(`${output}\n`);
|
|
202
|
+
recordFleetAuditFailure({ class: 'validation', effect: 'not_started' });
|
|
145
203
|
process.exit(1);
|
|
146
204
|
}
|
|
147
205
|
const taskMissing = e instanceof TaskStateError
|
|
@@ -167,6 +225,7 @@ function dieTaskRoom(e) {
|
|
|
167
225
|
: 'Retry once; if it repeats, run ours-fleet doctor and inspect the role logs.',
|
|
168
226
|
});
|
|
169
227
|
process.stderr.write(`${output}\n`);
|
|
228
|
+
recordFleetAuditFailure(classifyTaskRoomFailure(e));
|
|
170
229
|
process.exit(1);
|
|
171
230
|
}
|
|
172
231
|
const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
@@ -175,23 +234,118 @@ const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
|
175
234
|
+ ` — List ${markdownCode(t.list_name ?? 'default')}`
|
|
176
235
|
+ (t.blocked ? ` — 🚧 Blocked: ${markdownProse(t.blocked.reason)}` : '')),
|
|
177
236
|
});
|
|
178
|
-
const taskActionMarkdown = (title, task, fields = []) =>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
237
|
+
const taskActionMarkdown = (title, task, fields = []) => {
|
|
238
|
+
recordFleetAuditResource('task', task.task_id);
|
|
239
|
+
if (task.room_id)
|
|
240
|
+
recordFleetAuditResource('room', task.room_id);
|
|
241
|
+
return renderMarkdownResult({
|
|
242
|
+
icon: '📋', title,
|
|
243
|
+
fields: [
|
|
244
|
+
{ label: 'ID', value: task.task_id, kind: 'code' },
|
|
245
|
+
{ label: 'Status', value: taskStatus(task.state), kind: 'markdown' },
|
|
246
|
+
{ label: 'List', value: task.list_name ?? 'default', kind: 'code' },
|
|
247
|
+
...fields,
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Shared launch-configuration line for CLI member listings. Pre-upgrade seats
|
|
253
|
+
* without a captured presentation keep the minimal listing rather than
|
|
254
|
+
* claiming resolved facts. Output is already escaped (markdownItems boundary).
|
|
255
|
+
*/
|
|
256
|
+
function seatConfigurationSuffix(presentation) {
|
|
257
|
+
return presentation ? ` — ${renderAgentConfiguration(presentation)}` : '';
|
|
258
|
+
}
|
|
259
|
+
function safeSelectionSummary(definition, kind) {
|
|
260
|
+
const selected = definition?.[kind];
|
|
261
|
+
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
262
|
+
return 'unresolved';
|
|
263
|
+
const selection = selected;
|
|
264
|
+
if (selection.kind === 'ref' && typeof selection.id === 'string')
|
|
265
|
+
return `ref:${selection.id} (reference)`;
|
|
266
|
+
if (selection.kind === 'inline' && typeof selection.fingerprint === 'string')
|
|
267
|
+
return `inline:${selection.fingerprint} (inline)`;
|
|
268
|
+
if (typeof selection.ref === 'string')
|
|
269
|
+
return `ref:${selection.ref}`;
|
|
270
|
+
if (!Object.hasOwn(selection, 'inline'))
|
|
271
|
+
return 'unresolved';
|
|
272
|
+
const canonical = (value) => Array.isArray(value)
|
|
273
|
+
? `[${value.map(canonical).join(',')}]`
|
|
274
|
+
: value && typeof value === 'object'
|
|
275
|
+
? `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b))
|
|
276
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(',')}}`
|
|
277
|
+
: JSON.stringify(value);
|
|
278
|
+
const fingerprint = createHash('sha256').update(canonical(selection.inline)).digest('hex').slice(0, 16);
|
|
279
|
+
return `inline:sha256:${fingerprint}`;
|
|
280
|
+
}
|
|
281
|
+
function safePermissionsSummary(definition) {
|
|
282
|
+
const permissions = definition?.permissions;
|
|
283
|
+
if (!permissions || typeof permissions !== 'object' || Array.isArray(permissions))
|
|
284
|
+
return undefined;
|
|
285
|
+
const policy = permissions;
|
|
286
|
+
const fields = ['approval', 'filesystem', 'unattended'].flatMap(key => typeof policy[key] === 'string' ? [`${key}=${policy[key]}`] : []);
|
|
287
|
+
return fields.length ? fields.join(',') : undefined;
|
|
288
|
+
}
|
|
289
|
+
function auditTask(operation, task, previousState, newState = task.state, revision) {
|
|
290
|
+
const materialSameState = operation === 'block' || operation === 'unblock' || operation === 'settling';
|
|
291
|
+
if (previousState === newState && !materialSameState)
|
|
292
|
+
return;
|
|
293
|
+
recordFleetAuditResource('task', task.task_id);
|
|
294
|
+
if (task.room_id)
|
|
295
|
+
recordFleetAuditResource('room', task.room_id);
|
|
296
|
+
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
297
|
+
const definitions = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.agent_definition]) ?? []);
|
|
298
|
+
const presentations = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.presentation]) ?? []);
|
|
299
|
+
const semanticOperation = operation === 'recover'
|
|
300
|
+
? newState === 'active' ? 'work' : newState === 'done' ? 'done'
|
|
301
|
+
: newState === 'cancelled' ? 'cancel' : undefined
|
|
302
|
+
: operation;
|
|
303
|
+
if (!semanticOperation)
|
|
304
|
+
return;
|
|
305
|
+
recordFleetAuditPresentation({ kind: 'task', operation: semanticOperation, id: task.task_id, title: task.title,
|
|
306
|
+
previousState, newState,
|
|
307
|
+
revision: revision ?? task.blocked?.at ?? task.ended_at ?? task.started_at ?? task.created_at,
|
|
308
|
+
list: task.list_name ?? 'default',
|
|
309
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
310
|
+
roomId: task.room_id, agents: [...task.member_roles].sort((a, b) => a.name.localeCompare(b.name)).map(member => ({ name: member.name,
|
|
311
|
+
brain: safeSelectionSummary(definitions.get(member.name), 'brain'),
|
|
312
|
+
role: safeSelectionSummary(definitions.get(member.name), 'role') === 'unresolved'
|
|
313
|
+
? member.cowork_role : safeSelectionSummary(definitions.get(member.name), 'role'),
|
|
314
|
+
permissions: safePermissionsSummary(definitions.get(member.name)),
|
|
315
|
+
configuration: presentations.get(member.name) })) });
|
|
316
|
+
}
|
|
317
|
+
function auditRoom(operation, room, previousState, newState = room.state) {
|
|
318
|
+
if (previousState === newState)
|
|
319
|
+
return;
|
|
320
|
+
recordFleetAuditResource('room', room.room_id);
|
|
321
|
+
const semanticOperation = operation === 'recover'
|
|
322
|
+
? newState === 'active' ? 'activate' : newState === 'deleted' ? 'delete'
|
|
323
|
+
: newState === 'closing' ? 'close' : undefined
|
|
324
|
+
: operation;
|
|
325
|
+
if (!semanticOperation)
|
|
326
|
+
return;
|
|
327
|
+
recordFleetAuditPresentation({ kind: 'room', operation: semanticOperation, id: room.room_id, previousState, newState,
|
|
328
|
+
revision: room.closed_at ?? room.activated_at ?? room.close?.accepted_at ?? room.created_at,
|
|
329
|
+
name: room.room_name, taskId: room.task_id,
|
|
330
|
+
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
331
|
+
participants: [...room.member_seats].sort((a, b) => a.role_name.localeCompare(b.role_name)).map(member => ({ name: member.role_name, id: member.identity_cid,
|
|
332
|
+
brain: safeSelectionSummary(member.launch?.agent_definition, 'brain'),
|
|
333
|
+
role: safeSelectionSummary(member.launch?.agent_definition, 'role') === 'unresolved'
|
|
334
|
+
? member.cowork_role : safeSelectionSummary(member.launch?.agent_definition, 'role'),
|
|
335
|
+
permissions: safePermissionsSummary(member.launch?.agent_definition),
|
|
336
|
+
configuration: member.launch?.presentation })) });
|
|
337
|
+
}
|
|
338
|
+
const roomActionMarkdown = (title, room, fields = []) => {
|
|
339
|
+
recordFleetAuditResource('room', room.room_id);
|
|
340
|
+
return renderMarkdownResult({
|
|
341
|
+
icon: '🏠', title,
|
|
342
|
+
fields: [
|
|
343
|
+
{ label: 'ID', value: room.room_id, kind: 'code' },
|
|
344
|
+
{ label: 'Status', value: roomStatus(room.state), kind: 'markdown' },
|
|
345
|
+
...fields,
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
};
|
|
195
349
|
const PUBLIC_SETTLE_WAIT_MS = 60_000;
|
|
196
350
|
const PUBLIC_SETTLE_POLL_MS = 100;
|
|
197
351
|
const sleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms); });
|
|
@@ -208,6 +362,10 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
208
362
|
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
209
363
|
recoveryHint: `External settle worker failed to start. Retry task recover ${taskId}.`,
|
|
210
364
|
});
|
|
365
|
+
const task = getTask(taskId);
|
|
366
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
367
|
+
state: task.state, category: 'settlement_failed',
|
|
368
|
+
eventId: task.terminal_intent?.error_at ?? task.terminal_intent?.accepted_at ?? task.created_at });
|
|
211
369
|
throw error;
|
|
212
370
|
}
|
|
213
371
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -220,7 +378,54 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
220
378
|
}
|
|
221
379
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
222
380
|
}
|
|
223
|
-
|
|
381
|
+
const task = getTask(taskId);
|
|
382
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
383
|
+
state: task.state, category: 'settlement_pending',
|
|
384
|
+
eventId: task.terminal_intent?.accepted_at ?? task.created_at });
|
|
385
|
+
return { task, timedOut: true };
|
|
386
|
+
}
|
|
387
|
+
/** Launch the deletion settle worker and wait boundedly for physical absence. */
|
|
388
|
+
async function launchTaskDeleteWorker(taskId, configPath) {
|
|
389
|
+
const readDeletion = () => {
|
|
390
|
+
try {
|
|
391
|
+
const task = getDeletingTask(taskId);
|
|
392
|
+
return { present: true, errorAt: task.deletion?.error_at, error: task.deletion?.error };
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
// Only proven physical absence counts as deleted; anything else propagates.
|
|
396
|
+
if (error instanceof TaskStateError && /task not found/.test(error.message))
|
|
397
|
+
return { present: false };
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
const before = readDeletion();
|
|
402
|
+
if (!before.present)
|
|
403
|
+
return { deleted: true, timedOut: false };
|
|
404
|
+
try {
|
|
405
|
+
await launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, configPath);
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
await taskRoomService(configPath).recordDeletionError({
|
|
409
|
+
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
410
|
+
recoveryHint: `External delete worker failed to start. Retry task delete ${taskId} ${taskId}.`,
|
|
411
|
+
}).catch(() => { });
|
|
412
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
413
|
+
state: 'deleting', category: 'settlement_failed', eventId: new Date().toISOString() });
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
417
|
+
while (Date.now() < deadline) {
|
|
418
|
+
const current = readDeletion();
|
|
419
|
+
if (!current.present)
|
|
420
|
+
return { deleted: true, timedOut: false };
|
|
421
|
+
if (current.errorAt !== before.errorAt && current.error) {
|
|
422
|
+
throw new TaskStateError(current.error);
|
|
423
|
+
}
|
|
424
|
+
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
425
|
+
}
|
|
426
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
427
|
+
state: 'deleting', category: 'settlement_pending', eventId: new Date().toISOString() });
|
|
428
|
+
return { deleted: false, timedOut: true };
|
|
224
429
|
}
|
|
225
430
|
async function launchRoomDeleteWorker(roomId, configPath) {
|
|
226
431
|
const previousErrorAt = getRoomRecord(roomId)?.close?.error_at;
|
|
@@ -232,6 +437,11 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
232
437
|
actor: { kind: 'local_control', surface: 'cli' }, roomId, error: errorText(error),
|
|
233
438
|
recoveryHint: `External delete worker failed to start. Retry room delete ${roomId} ${roomId}.`,
|
|
234
439
|
});
|
|
440
|
+
const room = getRoomRecord(roomId);
|
|
441
|
+
if (room)
|
|
442
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
443
|
+
state: room.state, category: 'cleanup_failed',
|
|
444
|
+
eventId: room.close?.error_at ?? room.close?.accepted_at ?? room.created_at });
|
|
235
445
|
throw error;
|
|
236
446
|
}
|
|
237
447
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -244,7 +454,12 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
244
454
|
}
|
|
245
455
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
246
456
|
}
|
|
247
|
-
|
|
457
|
+
const remaining = getRoomRecord(roomId);
|
|
458
|
+
if (remaining)
|
|
459
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
460
|
+
state: remaining.state, category: 'cleanup_pending',
|
|
461
|
+
eventId: remaining.close?.accepted_at ?? remaining.created_at });
|
|
462
|
+
return { deleted: remaining === undefined, timedOut: true };
|
|
248
463
|
}
|
|
249
464
|
function loadCfg(opts) {
|
|
250
465
|
return loadConfig(opts.configuration);
|
|
@@ -271,7 +486,7 @@ function resolveRoomTemplate(cfg, name) {
|
|
|
271
486
|
const template = resolveTemplate(name, allTemplates(cfg));
|
|
272
487
|
if (!template)
|
|
273
488
|
throw new Error(`template not found: ${name}`);
|
|
274
|
-
return snapshotTemplate(template);
|
|
489
|
+
return snapshotTemplate(template, cfg.agentTemplates);
|
|
275
490
|
}
|
|
276
491
|
function durableTaskRoomTemplate(task, room) {
|
|
277
492
|
const snapshot = room.template_snapshot;
|
|
@@ -315,22 +530,36 @@ async function provisionRoom(cfg, input) {
|
|
|
315
530
|
throw new ConfigError('rooms.owner: public_invite or public_invite_file is required when attach_owner is enabled');
|
|
316
531
|
const cowork = coworkFor(cfg);
|
|
317
532
|
const goal = input.goal?.trim() || input.name;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
533
|
+
const unlockSnapshot = input.template ? acquireLaunchSnapshotLock() : undefined;
|
|
534
|
+
let launchTemplate;
|
|
535
|
+
let record;
|
|
536
|
+
try {
|
|
537
|
+
launchTemplate = input.template
|
|
538
|
+
? sealTemplateSnapshot(input.template, cfg.agentTemplates ?? {}) : undefined;
|
|
539
|
+
const briefing = input.brief?.trim() || launchTemplate?.contract?.trim() || goal;
|
|
540
|
+
const created = await cowork.createRoom({
|
|
541
|
+
room_name: input.name,
|
|
542
|
+
goal,
|
|
543
|
+
briefing,
|
|
544
|
+
quiet_membership: launchTemplate?.room?.quiet_membership,
|
|
545
|
+
anonymous: launchTemplate?.room?.anonymous,
|
|
546
|
+
});
|
|
547
|
+
record = createRoomRecord({
|
|
548
|
+
room_id: created.room_id,
|
|
549
|
+
room_name: input.name,
|
|
550
|
+
room_identity_cid: created.identity_cid,
|
|
551
|
+
task_id: input.taskId,
|
|
552
|
+
template_snapshot: launchTemplate,
|
|
553
|
+
});
|
|
554
|
+
input.onCreated?.(record);
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
unlockSnapshot?.();
|
|
558
|
+
if (launchTemplate?.launch_snapshot_hash)
|
|
559
|
+
releaseLaunchSnapshot(launchTemplate.launch_snapshot_hash);
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
unlockSnapshot?.();
|
|
334
563
|
record = advanceSaga(record.room_id, 'create_room', 1);
|
|
335
564
|
if (attachOwner) {
|
|
336
565
|
try {
|
|
@@ -346,21 +575,36 @@ async function provisionRoom(cfg, input) {
|
|
|
346
575
|
setSagaError(record.room_id, error instanceof Error ? error.message : String(error), mismatch
|
|
347
576
|
? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
|
|
348
577
|
: 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
|
|
578
|
+
const failed = getRoomRecord(record.room_id);
|
|
579
|
+
if (failed)
|
|
580
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
581
|
+
id: failed.room_id, state: failed.state, category: 'provision_failed',
|
|
582
|
+
eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
|
|
349
583
|
throw error;
|
|
350
584
|
}
|
|
351
585
|
}
|
|
352
586
|
record = advanceSaga(record.room_id, 'create_members', 3);
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
587
|
+
if (launchTemplate && launchTemplate.members.length > 0) {
|
|
588
|
+
try {
|
|
589
|
+
record = await provisionMembers({
|
|
590
|
+
cfg,
|
|
591
|
+
cowork,
|
|
592
|
+
roomId: record.room_id,
|
|
593
|
+
taskId: input.taskId,
|
|
594
|
+
template: launchTemplate,
|
|
595
|
+
binPath: getBinPath(),
|
|
596
|
+
brief: input.brief,
|
|
597
|
+
goal: input.goal,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
const failed = getRoomRecord(record.room_id);
|
|
602
|
+
if (failed)
|
|
603
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
604
|
+
id: failed.room_id, state: failed.state, category: 'provision_failed',
|
|
605
|
+
eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
364
608
|
}
|
|
365
609
|
else {
|
|
366
610
|
record = activateRoom(record.room_id);
|
|
@@ -382,12 +626,11 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
382
626
|
return;
|
|
383
627
|
}
|
|
384
628
|
if (!templates.length) {
|
|
385
|
-
console.log('No templates configured.');
|
|
629
|
+
console.log('No templates configured. Run interactive ours-fleet init to review and create a complete default setup.');
|
|
386
630
|
return;
|
|
387
631
|
}
|
|
388
632
|
for (const t of templates) {
|
|
389
|
-
|
|
390
|
-
console.log(`${t.name}@${t.version}${tag} ${t.description}`);
|
|
633
|
+
console.log(`${t.name}@${t.version} ${t.description} [file: ${t.sourceFile ?? 'manifest'}]`);
|
|
391
634
|
}
|
|
392
635
|
}
|
|
393
636
|
catch (e) {
|
|
@@ -405,11 +648,13 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
405
648
|
return;
|
|
406
649
|
}
|
|
407
650
|
console.log(`${t.name}@${t.version} ${t.description}`);
|
|
651
|
+
console.log(`Source: ${t.sourceFile ?? 'manifest'}`);
|
|
408
652
|
if (t.contract)
|
|
409
653
|
console.log(`\nContract:\n${t.contract}`);
|
|
410
654
|
console.log(`\nMembers:`);
|
|
411
655
|
for (const m of t.members)
|
|
412
|
-
console.log(` ${m.slot}: ${m.count}× ${m.role} (
|
|
656
|
+
console.log(` ${m.slot}: ${m.count}× ${m.role} (`
|
|
657
|
+
+ `agent template: ${m.agent_template})`);
|
|
413
658
|
console.log(`\nContent hash: ${t.content_hash}`);
|
|
414
659
|
}
|
|
415
660
|
catch (e) {
|
|
@@ -452,17 +697,33 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
452
697
|
.option('--brief-file <path>', 'task brief from file')
|
|
453
698
|
.option('--backlog', 'create in backlog (do not start immediately)')
|
|
454
699
|
.option('--no-room', 'create task without a room')
|
|
700
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
701
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
455
702
|
.option('--idempotency-key <key>', 'idempotency key')
|
|
456
703
|
.option('--list <name>', 'task list (default: default)')
|
|
704
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
705
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
706
|
+
.option('--agent-template <id>', 'Agent Template for the current member block')
|
|
707
|
+
.option('--brain <id>', 'Brain preset for the current member block')
|
|
708
|
+
.option('--role <id>', 'Role preset for the current member block')
|
|
709
|
+
.option('--approval <mode>', 'approval for the current member block')
|
|
710
|
+
.option('--filesystem <mode>', 'filesystem for the current member block')
|
|
711
|
+
.option('--unattended <mode>', 'unattended policy for the current member block')
|
|
712
|
+
.option('--cwd <path>', 'working directory for the current member block')
|
|
713
|
+
.option('--model <id>', 'model for the current member block')
|
|
714
|
+
.option('--effort <level>', 'reasoning effort for the current member block')
|
|
457
715
|
.option('--json', 'JSON output')
|
|
458
|
-
.action(async (opts) => {
|
|
716
|
+
.action(async (opts, command) => {
|
|
459
717
|
try {
|
|
718
|
+
const members = cliMemberOverrides(opts.membersFile, commandArgv(command));
|
|
460
719
|
const record = await taskRoomService(opts.configuration).createTask({
|
|
461
720
|
actor: { kind: 'local_control', surface: 'cli' }, title: opts.title,
|
|
462
721
|
brief: opts.brief, briefFile: opts.briefFile, template: opts.template,
|
|
463
722
|
backlog: opts.backlog, noRoom: opts.room === false,
|
|
723
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
464
724
|
idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
|
|
465
725
|
list: opts.list,
|
|
726
|
+
members,
|
|
466
727
|
});
|
|
467
728
|
if (opts.json) {
|
|
468
729
|
console.log(JSON.stringify({ schema_version: 1, task: record }, null, 2));
|
|
@@ -487,7 +748,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
487
748
|
.option('--list <name>', 'filter by task list')
|
|
488
749
|
.option('--group-by-list', 'group deterministic results by list (JSON)')
|
|
489
750
|
.option('--json', 'JSON output')
|
|
490
|
-
.action((opts) => {
|
|
751
|
+
.action(async (opts) => {
|
|
491
752
|
try {
|
|
492
753
|
let stateFilter;
|
|
493
754
|
if (opts.state && opts.state !== 'all') {
|
|
@@ -513,9 +774,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
513
774
|
taskCmd.command('lists')
|
|
514
775
|
.description('list named task lists')
|
|
515
776
|
.option('--json', 'JSON output')
|
|
516
|
-
.action((opts) => {
|
|
777
|
+
.action(async (opts) => {
|
|
517
778
|
try {
|
|
518
|
-
const
|
|
779
|
+
const service = taskRoomService(opts.configuration);
|
|
780
|
+
const lists = service.listTaskLists();
|
|
519
781
|
if (opts.json) {
|
|
520
782
|
console.log(JSON.stringify({ schema_version: 1, lists }, null, 2));
|
|
521
783
|
return;
|
|
@@ -606,9 +868,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
606
868
|
taskCmd.command('show <id>')
|
|
607
869
|
.description('show task details')
|
|
608
870
|
.option('--json', 'JSON output')
|
|
609
|
-
.action((id, opts) => {
|
|
871
|
+
.action(async (id, opts) => {
|
|
610
872
|
try {
|
|
611
|
-
const
|
|
873
|
+
const service = taskRoomService();
|
|
874
|
+
const { task: t, orchestration: room } = service.getTask(id);
|
|
612
875
|
if (opts.json) {
|
|
613
876
|
console.log(JSON.stringify({
|
|
614
877
|
schema_version: 1, task: t, orchestration: room ?? null,
|
|
@@ -633,7 +896,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
633
896
|
],
|
|
634
897
|
sections: [
|
|
635
898
|
...(t.member_roles.length ? [{
|
|
636
|
-
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`
|
|
899
|
+
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`
|
|
900
|
+
+ seatConfigurationSuffix(room?.member_seats
|
|
901
|
+
.find(seat => seat.role_name === m.name)?.launch?.presentation)),
|
|
637
902
|
}] : []),
|
|
638
903
|
...(room ? roomStartupSections(room) : []),
|
|
639
904
|
],
|
|
@@ -646,12 +911,28 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
646
911
|
}
|
|
647
912
|
});
|
|
648
913
|
cOpt(taskCmd.command('start <id>'))
|
|
649
|
-
.description('start a
|
|
914
|
+
.description('idempotently select a plan, provision, and start a task')
|
|
915
|
+
.option('--template <name>', 'room template')
|
|
916
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
917
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
918
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
919
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
920
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
921
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
922
|
+
.option('--role <id>', 'Role preset for current member')
|
|
923
|
+
.option('--approval <mode>', 'approval for current member')
|
|
924
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
925
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
926
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
927
|
+
.option('--model <id>', 'model for current member')
|
|
928
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
650
929
|
.option('--json', 'JSON output')
|
|
651
|
-
.action(async (id, opts) => {
|
|
930
|
+
.action(async (id, opts, command) => {
|
|
652
931
|
try {
|
|
653
932
|
const t = await taskRoomService(opts.configuration).startTask({
|
|
654
933
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
934
|
+
template: opts.template, members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
935
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
655
936
|
});
|
|
656
937
|
if (opts.json) {
|
|
657
938
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
@@ -676,9 +957,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
676
957
|
.option('--json', 'JSON output')
|
|
677
958
|
.action((id, opts) => {
|
|
678
959
|
try {
|
|
960
|
+
const previous = getTask(id).state;
|
|
679
961
|
const t = taskRoomService().blockTask({
|
|
680
962
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, reason: opts.reason,
|
|
681
963
|
});
|
|
964
|
+
auditTask('block', t, previous);
|
|
682
965
|
if (opts.json) {
|
|
683
966
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
684
967
|
return;
|
|
@@ -696,9 +979,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
696
979
|
.option('--json', 'JSON output')
|
|
697
980
|
.action((id, opts) => {
|
|
698
981
|
try {
|
|
982
|
+
const prior = getTask(id);
|
|
983
|
+
const previous = prior.state;
|
|
699
984
|
const t = taskRoomService().unblockTask({
|
|
700
985
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
701
986
|
});
|
|
987
|
+
auditTask('unblock', t, previous, t.state, prior.blocked?.at);
|
|
702
988
|
if (opts.json) {
|
|
703
989
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
704
990
|
return;
|
|
@@ -716,9 +1002,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
716
1002
|
.option('--json', 'JSON output')
|
|
717
1003
|
.action((id, opts) => {
|
|
718
1004
|
try {
|
|
1005
|
+
const previous = getTask(id).state;
|
|
719
1006
|
const t = taskRoomService().reviewTask({
|
|
720
1007
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
721
1008
|
});
|
|
1009
|
+
auditTask('review', t, previous);
|
|
722
1010
|
if (opts.json) {
|
|
723
1011
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
724
1012
|
return;
|
|
@@ -738,6 +1026,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
738
1026
|
.option('--json', 'JSON output')
|
|
739
1027
|
.action(async (id, opts) => {
|
|
740
1028
|
try {
|
|
1029
|
+
const previous = getTask(id).state;
|
|
741
1030
|
let summary = opts.summary;
|
|
742
1031
|
if (opts.summaryFile)
|
|
743
1032
|
summary = readFileSync(opts.summaryFile, 'utf8');
|
|
@@ -745,10 +1034,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
745
1034
|
const plan = await taskRoomService(opts.configuration).completeTask({
|
|
746
1035
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
747
1036
|
});
|
|
1037
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1038
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
748
1039
|
const settled = plan.settlementRequired
|
|
749
1040
|
? await launchTaskSettleWorker(id, opts.configuration)
|
|
750
1041
|
: { task: plan.task, timedOut: false };
|
|
751
1042
|
const t = settled.task;
|
|
1043
|
+
auditTask('done', t, previous);
|
|
752
1044
|
if (opts.json) {
|
|
753
1045
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
754
1046
|
return;
|
|
@@ -776,13 +1068,17 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
776
1068
|
try {
|
|
777
1069
|
if (id !== confirmId)
|
|
778
1070
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
1071
|
+
const previous = getTask(id).state;
|
|
779
1072
|
const plan = await taskRoomService(opts.configuration).cancelTask({
|
|
780
1073
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
781
1074
|
});
|
|
1075
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1076
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
782
1077
|
const settled = plan.settlementRequired
|
|
783
1078
|
? await launchTaskSettleWorker(id, opts.configuration)
|
|
784
1079
|
: { task: plan.task, timedOut: false };
|
|
785
1080
|
const t = settled.task;
|
|
1081
|
+
auditTask('cancel', t, previous);
|
|
786
1082
|
if (opts.json) {
|
|
787
1083
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
788
1084
|
return;
|
|
@@ -804,21 +1100,46 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
804
1100
|
}
|
|
805
1101
|
});
|
|
806
1102
|
cOpt(taskCmd.command('delete <id> <confirm-id>'))
|
|
807
|
-
.description('delete a
|
|
1103
|
+
.description('permanently delete a task in any lifecycle state (requires ID twice for confirmation)')
|
|
808
1104
|
.option('--json', 'JSON output')
|
|
809
|
-
.action((id, confirmId, opts) => {
|
|
1105
|
+
.action(async (id, confirmId, opts) => {
|
|
810
1106
|
try {
|
|
811
1107
|
if (id !== confirmId)
|
|
812
1108
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
813
|
-
const
|
|
1109
|
+
const accepted = await taskRoomService(opts.configuration).requestTaskDeletion({
|
|
814
1110
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
815
1111
|
});
|
|
1112
|
+
if (accepted.status === 'already_absent') {
|
|
1113
|
+
if (opts.json) {
|
|
1114
|
+
console.log(JSON.stringify({
|
|
1115
|
+
schema_version: 1, task_id: id, deleted: false, already_absent: true,
|
|
1116
|
+
}, null, 2));
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
console.log(renderMarkdownResult({
|
|
1120
|
+
icon: '🗑️', title: 'Task already absent',
|
|
1121
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1122
|
+
}));
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
816
1126
|
if (opts.json) {
|
|
817
|
-
console.log(JSON.stringify({
|
|
1127
|
+
console.log(JSON.stringify({
|
|
1128
|
+
schema_version: 1, task_id: id, accepted: true,
|
|
1129
|
+
deleted: settled.deleted, pending: settled.timedOut,
|
|
1130
|
+
}, null, 2));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (!settled.deleted) {
|
|
1134
|
+
console.log(renderMarkdownFailure({
|
|
1135
|
+
kind: 'pending', subject: `task delete ${id} ${id}`,
|
|
1136
|
+
detail: 'The deletion was accepted and cleanup is still settling.',
|
|
1137
|
+
action: `Re-run ours-fleet task delete ${id} ${id} or ours-fleet task recover ${id}.`,
|
|
1138
|
+
}));
|
|
818
1139
|
return;
|
|
819
1140
|
}
|
|
820
1141
|
console.log(renderMarkdownResult({
|
|
821
|
-
icon: '🗑️', title:
|
|
1142
|
+
icon: '🗑️', title: 'Task deleted',
|
|
822
1143
|
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
823
1144
|
}));
|
|
824
1145
|
}
|
|
@@ -833,9 +1154,29 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
833
1154
|
.option('--json', 'JSON output')
|
|
834
1155
|
.action(async (id, opts) => {
|
|
835
1156
|
try {
|
|
1157
|
+
const previous = getTask(id).state;
|
|
836
1158
|
const app = taskRoomService(opts.configuration);
|
|
837
1159
|
const actor = { kind: 'local_control', surface: 'cli' };
|
|
838
1160
|
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
1161
|
+
if (begin.kind === 'deletion_worker_required') {
|
|
1162
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
1163
|
+
const payload = { schema_version: 1, task_id: id, deleted: settled.deleted };
|
|
1164
|
+
if (opts.json) {
|
|
1165
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
console.log(settled.deleted
|
|
1169
|
+
? renderMarkdownResult({
|
|
1170
|
+
icon: '🗑️', title: 'Task deletion completed by recovery',
|
|
1171
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1172
|
+
})
|
|
1173
|
+
: renderMarkdownFailure({
|
|
1174
|
+
kind: 'pending', subject: `task recover ${id}`,
|
|
1175
|
+
detail: 'The task is pending deletion and cleanup is still settling.',
|
|
1176
|
+
action: `Run ours-fleet task delete ${id} ${id} to retry.`,
|
|
1177
|
+
}));
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
839
1180
|
const recovered = begin.kind === 'terminal_worker_required'
|
|
840
1181
|
? await (async () => {
|
|
841
1182
|
const settled = await launchTaskSettleWorker(id, opts.configuration);
|
|
@@ -843,6 +1184,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
843
1184
|
})()
|
|
844
1185
|
: begin.result;
|
|
845
1186
|
const t = recovered.task;
|
|
1187
|
+
auditTask('recover', t, previous);
|
|
846
1188
|
const room = recovered.room;
|
|
847
1189
|
const recoveryActions = recovered.issues.map(issue => {
|
|
848
1190
|
if (issue.code === 'terminal_pending')
|
|
@@ -913,6 +1255,35 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
913
1255
|
dieTaskRoom(e);
|
|
914
1256
|
}
|
|
915
1257
|
});
|
|
1258
|
+
cOpt(taskCmd.command('_settle_delete <id>', { hidden: true }))
|
|
1259
|
+
.description('internal: settle a previously accepted task deletion')
|
|
1260
|
+
.option('--json', 'JSON output')
|
|
1261
|
+
.action(async (id, opts) => {
|
|
1262
|
+
try {
|
|
1263
|
+
const app = taskRoomService(opts.configuration);
|
|
1264
|
+
const result = await app.settleTaskDeletion({
|
|
1265
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1266
|
+
});
|
|
1267
|
+
if (opts.json) {
|
|
1268
|
+
console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
console.log(renderMarkdownResult({
|
|
1272
|
+
icon: '🗑️', title: result.deleted ? 'Task deletion settled' : 'Task already absent',
|
|
1273
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1274
|
+
}));
|
|
1275
|
+
}
|
|
1276
|
+
catch (e) {
|
|
1277
|
+
await taskRoomService(opts.configuration).recordDeletionError({
|
|
1278
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1279
|
+
error: errorText(e),
|
|
1280
|
+
recoveryHint: `External delete worker failed. Retry task delete ${id} ${id}.`,
|
|
1281
|
+
}).catch(() => { });
|
|
1282
|
+
if (opts.json)
|
|
1283
|
+
die(e);
|
|
1284
|
+
dieTaskRoom(e);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
916
1287
|
cOpt(taskCmd.command('_recover <id>', { hidden: true }))
|
|
917
1288
|
.description('internal: settle and continue task recovery')
|
|
918
1289
|
.option('--json', 'JSON output')
|
|
@@ -921,6 +1292,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
921
1292
|
const actor = { kind: 'internal_worker', surface: 'cli' };
|
|
922
1293
|
try {
|
|
923
1294
|
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
1295
|
+
if (begin.kind === 'deletion_worker_required') {
|
|
1296
|
+
const settled = await app.settleTaskDeletion({ actor, taskId: id });
|
|
1297
|
+
console.log(JSON.stringify({ schema_version: 1, deletion: settled }, null, 2));
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
924
1300
|
const result = begin.kind === 'terminal_worker_required'
|
|
925
1301
|
? await (async () => {
|
|
926
1302
|
try {
|
|
@@ -945,15 +1321,33 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
945
1321
|
}
|
|
946
1322
|
});
|
|
947
1323
|
cOpt(taskCmd.command('work <id>'))
|
|
948
|
-
.description('
|
|
1324
|
+
.description('deprecated alias for task start')
|
|
949
1325
|
.option('--template <name>', 'room template')
|
|
1326
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1327
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1328
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
1329
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
1330
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
1331
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
1332
|
+
.option('--role <id>', 'Role preset for current member')
|
|
1333
|
+
.option('--approval <mode>', 'approval for current member')
|
|
1334
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
1335
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
1336
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
1337
|
+
.option('--model <id>', 'model for current member')
|
|
1338
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
950
1339
|
.option('--json', 'JSON output')
|
|
951
|
-
.action(async (id, opts) => {
|
|
1340
|
+
.action(async (id, opts, command) => {
|
|
952
1341
|
try {
|
|
1342
|
+
const previous = getTask(id).state;
|
|
1343
|
+
console.error('warning: `fleet task work` is deprecated; use `fleet task start`');
|
|
953
1344
|
const result = await taskRoomService(opts.configuration).ensureTaskWork({
|
|
954
1345
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, template: opts.template,
|
|
1346
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1347
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
955
1348
|
});
|
|
956
1349
|
const t = result.task;
|
|
1350
|
+
auditTask('work', t, previous);
|
|
957
1351
|
if (result.status === 'already_active') {
|
|
958
1352
|
if (opts.json) {
|
|
959
1353
|
console.log(JSON.stringify({ schema_version: 1, task: t, status: 'already_active' }, null, 2));
|
|
@@ -992,6 +1386,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
992
1386
|
.option('--json', 'JSON output')
|
|
993
1387
|
.action(async (id, opts) => {
|
|
994
1388
|
try {
|
|
1389
|
+
const previous = getTask(id).state;
|
|
995
1390
|
let summary = opts.summary;
|
|
996
1391
|
if (opts.summaryFile)
|
|
997
1392
|
summary = readFileSync(opts.summaryFile, 'utf8');
|
|
@@ -999,10 +1394,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
999
1394
|
const plan = await taskRoomService(opts.configuration).finishTask({
|
|
1000
1395
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
1001
1396
|
});
|
|
1397
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1398
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
1002
1399
|
const settled = plan.settlementRequired
|
|
1003
1400
|
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1004
1401
|
: { task: plan.task, timedOut: false };
|
|
1005
1402
|
const t = settled.task;
|
|
1403
|
+
auditTask('finish', t, previous);
|
|
1006
1404
|
if (opts.json) {
|
|
1007
1405
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
1008
1406
|
return;
|
|
@@ -1032,15 +1430,30 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1032
1430
|
.description('create a standalone room')
|
|
1033
1431
|
.requiredOption('--name <name>', 'room name')
|
|
1034
1432
|
.option('--template <name>', 'room template')
|
|
1433
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1434
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1035
1435
|
.option('--goal <text>', 'room goal')
|
|
1036
1436
|
.option('--brief <text>', 'room briefing')
|
|
1037
1437
|
.option('--brief-file <path>', 'room briefing from file')
|
|
1438
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
1439
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
1440
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
1441
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
1442
|
+
.option('--role <id>', 'Role preset for current member')
|
|
1443
|
+
.option('--approval <mode>', 'approval for current member')
|
|
1444
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
1445
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
1446
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
1447
|
+
.option('--model <id>', 'model for current member')
|
|
1448
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
1038
1449
|
.option('--json', 'JSON output')
|
|
1039
|
-
.action(async (opts) => {
|
|
1450
|
+
.action(async (opts, command) => {
|
|
1040
1451
|
try {
|
|
1041
1452
|
const record = await taskRoomService(opts.configuration).createRoom({
|
|
1042
1453
|
actor: { kind: 'local_control', surface: 'cli' }, name: opts.name,
|
|
1043
1454
|
template: opts.template, goal: opts.goal, brief: opts.brief, briefFile: opts.briefFile,
|
|
1455
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1456
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
1044
1457
|
});
|
|
1045
1458
|
if (opts.json) {
|
|
1046
1459
|
console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
|
|
@@ -1109,7 +1522,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1109
1522
|
],
|
|
1110
1523
|
sections: [
|
|
1111
1524
|
...(cowork.seats.length ? [{
|
|
1112
|
-
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1525
|
+
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1526
|
+
+ seatConfigurationSuffix(r?.member_seats
|
|
1527
|
+
.find(seat => seat.identity_cid === s.identity_cid)?.launch?.presentation)),
|
|
1113
1528
|
}] : []),
|
|
1114
1529
|
...(r ? roomStartupSections(r) : []),
|
|
1115
1530
|
],
|
|
@@ -1126,7 +1541,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1126
1541
|
.option('--json', 'JSON output')
|
|
1127
1542
|
.action(async (id, opts) => {
|
|
1128
1543
|
try {
|
|
1129
|
-
const { room } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1544
|
+
const { room, orchestration } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1130
1545
|
const url = `http://localhost:4460/room/${id}`;
|
|
1131
1546
|
if (opts.json) {
|
|
1132
1547
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, url, room_name: room.room_name }, null, 2));
|
|
@@ -1171,7 +1586,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1171
1586
|
],
|
|
1172
1587
|
sections: [{
|
|
1173
1588
|
heading: 'Members',
|
|
1174
|
-
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1589
|
+
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1590
|
+
+ seatConfigurationSuffix(r?.member_seats
|
|
1591
|
+
.find(seat => seat.identity_cid === s.identity_cid)?.launch?.presentation)) }
|
|
1175
1592
|
: { items: ['No members found.'] }),
|
|
1176
1593
|
}],
|
|
1177
1594
|
}));
|
|
@@ -1186,10 +1603,16 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1186
1603
|
try {
|
|
1187
1604
|
if (id !== confirmId)
|
|
1188
1605
|
throw taskRoomPublicError('room_confirmation_mismatch');
|
|
1606
|
+
const previous = getRoomRecord(id);
|
|
1189
1607
|
await taskRoomService(opts.configuration).requestRoomDeletion({
|
|
1190
1608
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1191
1609
|
});
|
|
1610
|
+
const closing = getRoomRecord(id);
|
|
1611
|
+
if (previous && closing && previous.state !== closing.state)
|
|
1612
|
+
auditRoom('close', closing, previous.state, closing.state);
|
|
1192
1613
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1614
|
+
if (closing)
|
|
1615
|
+
auditRoom('delete', closing, closing.state, settled.deleted ? 'deleted' : 'closing');
|
|
1193
1616
|
if (opts.json) {
|
|
1194
1617
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, deleted: settled.deleted }, null, 2));
|
|
1195
1618
|
return;
|
|
@@ -1226,11 +1649,14 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1226
1649
|
.option('--json', 'JSON output')
|
|
1227
1650
|
.action(async (id, opts) => {
|
|
1228
1651
|
try {
|
|
1652
|
+
const previous = getRoomRecord(id);
|
|
1229
1653
|
const recovered = await taskRoomService(opts.configuration).recoverRoom({
|
|
1230
1654
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1231
1655
|
});
|
|
1232
1656
|
if (recovered.kind === 'deletion_worker_required') {
|
|
1233
1657
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1658
|
+
if (previous)
|
|
1659
|
+
auditRoom('recover', previous, previous.state, settled.deleted ? 'deleted' : 'closing');
|
|
1234
1660
|
if (opts.json) {
|
|
1235
1661
|
console.log(JSON.stringify({
|
|
1236
1662
|
schema_version: 1, room: null, orchestration: null,
|
|
@@ -1251,6 +1677,8 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1251
1677
|
return;
|
|
1252
1678
|
}
|
|
1253
1679
|
const { room: cowork, orchestration: r, issues: actions } = recovered;
|
|
1680
|
+
if (r)
|
|
1681
|
+
auditRoom('recover', r, previous?.state ?? 'unresolved', r.state);
|
|
1254
1682
|
if (opts.json) {
|
|
1255
1683
|
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null, recovery_actions: actions }, null, 2));
|
|
1256
1684
|
return;
|