@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.21
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 +204 -55
- 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 +435 -79
- 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 +241 -76
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +75 -3
- package/dist/config.js +274 -34
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +3 -2
- 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 +192 -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 +555 -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 +134 -0
- package/dist/lifecycle-summary.js +179 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +38 -5
- 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 +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +514 -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 +181 -0
- package/dist/rooms-tasks/member-overrides.d.ts +30 -0
- package/dist/rooms-tasks/member-overrides.js +204 -0
- package/dist/rooms-tasks/provision.js +184 -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 +96 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +12 -2
- package/dist/runner.js +124 -43
- 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 +15 -31
- package/dist/spawn.js +117 -237
- 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,52 @@
|
|
|
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', '--loops-file', '--no-loops',
|
|
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]);
|
|
18
|
+
if (argv[i] !== '--no-loops') {
|
|
19
|
+
grouped.push(argv[i + 1]);
|
|
20
|
+
i += 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (membersFile && grouped.length)
|
|
24
|
+
throw new Error('--members-file cannot be combined with grouped --member options');
|
|
25
|
+
if (membersFile)
|
|
26
|
+
return readMembersFile(membersFile);
|
|
27
|
+
return grouped.length ? parseGroupedMemberArgs(grouped) : undefined;
|
|
28
|
+
}
|
|
29
|
+
function commandArgv(command) {
|
|
30
|
+
let root = command;
|
|
31
|
+
while (root.parent)
|
|
32
|
+
root = root.parent;
|
|
33
|
+
return root.rawArgs ?? process.argv.slice(2);
|
|
34
|
+
}
|
|
35
|
+
function cliAnonymousOverride(argv) {
|
|
36
|
+
const enabled = argv.includes('--anonymous');
|
|
37
|
+
const disabled = argv.includes('--no-anonymous');
|
|
38
|
+
if (enabled && disabled)
|
|
39
|
+
throw new Error('--anonymous and --no-anonymous are mutually exclusive');
|
|
40
|
+
return enabled ? true : disabled ? false : undefined;
|
|
41
|
+
}
|
|
42
|
+
import { getTask, getDeletingTask, activateTask, TaskStateError, } from './task-state.js';
|
|
7
43
|
import { createRoomRecord, getRoomRecord, advanceSaga, setOwnerSeat, setSagaError, activateRoom, RoomStateError, } from './room-state.js';
|
|
8
44
|
import { createCoworkAdapter, CoworkProtocolError } from './cowork-adapter.js';
|
|
9
45
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
10
46
|
import { TaskRoomApplicationError, TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
11
47
|
import { TaskListError } from './task-lists.js';
|
|
48
|
+
import { recordFleetAuditFailure, recordFleetAuditPresentation, recordFleetAuditResource, } from '../fleet-command-audit.js';
|
|
49
|
+
import { renderAgentConfiguration } from '../lifecycle-summary.js';
|
|
12
50
|
class TaskRoomPublicError extends Error {
|
|
13
51
|
code;
|
|
14
52
|
fields;
|
|
@@ -41,7 +79,7 @@ function taskRoomPublicFailure(error) {
|
|
|
41
79
|
case 'template_not_found': return {
|
|
42
80
|
legacy: `template not found: ${f.template ?? 'requested-template'}`, kind: 'not_found',
|
|
43
81
|
detail: `The requested template ${f.template ?? ''}`.trim() + ' was not found.',
|
|
44
|
-
action: 'Run ours-fleet
|
|
82
|
+
action: 'Run interactive ours-fleet init to review and create a complete default setup, then run ours-fleet template list.',
|
|
45
83
|
};
|
|
46
84
|
case 'template_mismatch': return {
|
|
47
85
|
legacy: `template ${f.requested ?? 'requested-template'} does not match room ${f.room ?? 'requested-room'}'s provisioned template ${f.provisioned ?? 'recorded-template'}`,
|
|
@@ -89,8 +127,29 @@ function die(e) {
|
|
|
89
127
|
const msg = e instanceof TaskRoomPublicError
|
|
90
128
|
? taskRoomPublicFailure(e).legacy : e instanceof Error ? e.message : String(e);
|
|
91
129
|
process.stderr.write(`error: ${msg}\n`);
|
|
130
|
+
recordFleetAuditFailure(classifyTaskRoomFailure(e));
|
|
92
131
|
process.exit(1);
|
|
93
132
|
}
|
|
133
|
+
function classifyTaskRoomFailure(e) {
|
|
134
|
+
if (e instanceof TaskRoomApplicationError)
|
|
135
|
+
e = taskRoomPublicError(e.code, e.fields);
|
|
136
|
+
if (e instanceof TaskRoomPublicError || e instanceof TaskListError || e instanceof ConfigError)
|
|
137
|
+
return { class: 'validation', effect: 'not_started' };
|
|
138
|
+
if (e instanceof TaskStateError) {
|
|
139
|
+
const known = /^task not found: [A-Za-z0-9_-]{1,128}$/u.test(e.message)
|
|
140
|
+
|| e.message === 'invalid task ID: expected the canonical 17-character lowercase ID'
|
|
141
|
+
|| isKnownTaskStateMessage(e.message);
|
|
142
|
+
return known ? { class: 'validation', effect: 'not_started' }
|
|
143
|
+
: { class: 'runtime', effect: 'unknown' };
|
|
144
|
+
}
|
|
145
|
+
if (e instanceof RoomStateError) {
|
|
146
|
+
const known = /^room not found: [A-Za-z0-9_-]{1,128}$/u.test(e.message)
|
|
147
|
+
|| isKnownRoomStateMessage(e.message);
|
|
148
|
+
return known ? { class: 'validation', effect: 'not_started' }
|
|
149
|
+
: { class: 'runtime', effect: 'unknown' };
|
|
150
|
+
}
|
|
151
|
+
return { class: 'runtime', effect: 'unknown' };
|
|
152
|
+
}
|
|
94
153
|
const TASK_STATE_WORD = '(?:backlog|provisioning|active|review|done|cancelled|failed)';
|
|
95
154
|
const SAFE_ID_WORD = '[A-Za-z0-9_-]{1,128}';
|
|
96
155
|
function isKnownTaskStateMessage(message) {
|
|
@@ -104,7 +163,7 @@ function isKnownTaskStateMessage(message) {
|
|
|
104
163
|
new RegExp(`^task ${SAFE_ID_WORD} is not tied to room ${SAFE_ID_WORD}$`, 'u'),
|
|
105
164
|
new RegExp(`^task ${SAFE_ID_WORD} has no terminal intent$`, 'u'),
|
|
106
165
|
new RegExp(`^task ${SAFE_ID_WORD} reached terminal state '${TASK_STATE_WORD}' outside its intent$`, 'u'),
|
|
107
|
-
new RegExp(`^
|
|
166
|
+
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
167
|
].some(pattern => pattern.test(message));
|
|
109
168
|
}
|
|
110
169
|
function isKnownRoomStateMessage(message) {
|
|
@@ -127,6 +186,7 @@ function dieTaskRoom(e) {
|
|
|
127
186
|
action: failure.action,
|
|
128
187
|
});
|
|
129
188
|
process.stderr.write(`${output}\n`);
|
|
189
|
+
recordFleetAuditFailure({ class: 'validation', effect: 'not_started' });
|
|
130
190
|
process.exit(1);
|
|
131
191
|
}
|
|
132
192
|
if (e instanceof TaskListError) {
|
|
@@ -142,6 +202,7 @@ function dieTaskRoom(e) {
|
|
|
142
202
|
: 'Use a valid NFC-normalized name of at most 64 Unicode code points.',
|
|
143
203
|
});
|
|
144
204
|
process.stderr.write(`${output}\n`);
|
|
205
|
+
recordFleetAuditFailure({ class: 'validation', effect: 'not_started' });
|
|
145
206
|
process.exit(1);
|
|
146
207
|
}
|
|
147
208
|
const taskMissing = e instanceof TaskStateError
|
|
@@ -167,6 +228,7 @@ function dieTaskRoom(e) {
|
|
|
167
228
|
: 'Retry once; if it repeats, run ours-fleet doctor and inspect the role logs.',
|
|
168
229
|
});
|
|
169
230
|
process.stderr.write(`${output}\n`);
|
|
231
|
+
recordFleetAuditFailure(classifyTaskRoomFailure(e));
|
|
170
232
|
process.exit(1);
|
|
171
233
|
}
|
|
172
234
|
const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
@@ -175,23 +237,118 @@ const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
|
175
237
|
+ ` — List ${markdownCode(t.list_name ?? 'default')}`
|
|
176
238
|
+ (t.blocked ? ` — 🚧 Blocked: ${markdownProse(t.blocked.reason)}` : '')),
|
|
177
239
|
});
|
|
178
|
-
const taskActionMarkdown = (title, task, fields = []) =>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
240
|
+
const taskActionMarkdown = (title, task, fields = []) => {
|
|
241
|
+
recordFleetAuditResource('task', task.task_id);
|
|
242
|
+
if (task.room_id)
|
|
243
|
+
recordFleetAuditResource('room', task.room_id);
|
|
244
|
+
return renderMarkdownResult({
|
|
245
|
+
icon: '📋', title,
|
|
246
|
+
fields: [
|
|
247
|
+
{ label: 'ID', value: task.task_id, kind: 'code' },
|
|
248
|
+
{ label: 'Status', value: taskStatus(task.state), kind: 'markdown' },
|
|
249
|
+
{ label: 'List', value: task.list_name ?? 'default', kind: 'code' },
|
|
250
|
+
...fields,
|
|
251
|
+
],
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Shared launch-configuration line for CLI member listings. Pre-upgrade seats
|
|
256
|
+
* without a captured presentation keep the minimal listing rather than
|
|
257
|
+
* claiming resolved facts. Output is already escaped (markdownItems boundary).
|
|
258
|
+
*/
|
|
259
|
+
function seatConfigurationSuffix(presentation) {
|
|
260
|
+
return presentation ? ` — ${renderAgentConfiguration(presentation)}` : '';
|
|
261
|
+
}
|
|
262
|
+
function safeSelectionSummary(definition, kind) {
|
|
263
|
+
const selected = definition?.[kind];
|
|
264
|
+
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
265
|
+
return 'unresolved';
|
|
266
|
+
const selection = selected;
|
|
267
|
+
if (selection.kind === 'ref' && typeof selection.id === 'string')
|
|
268
|
+
return `ref:${selection.id} (reference)`;
|
|
269
|
+
if (selection.kind === 'inline' && typeof selection.fingerprint === 'string')
|
|
270
|
+
return `inline:${selection.fingerprint} (inline)`;
|
|
271
|
+
if (typeof selection.ref === 'string')
|
|
272
|
+
return `ref:${selection.ref}`;
|
|
273
|
+
if (!Object.hasOwn(selection, 'inline'))
|
|
274
|
+
return 'unresolved';
|
|
275
|
+
const canonical = (value) => Array.isArray(value)
|
|
276
|
+
? `[${value.map(canonical).join(',')}]`
|
|
277
|
+
: value && typeof value === 'object'
|
|
278
|
+
? `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b))
|
|
279
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(',')}}`
|
|
280
|
+
: JSON.stringify(value);
|
|
281
|
+
const fingerprint = createHash('sha256').update(canonical(selection.inline)).digest('hex').slice(0, 16);
|
|
282
|
+
return `inline:sha256:${fingerprint}`;
|
|
283
|
+
}
|
|
284
|
+
function safePermissionsSummary(definition) {
|
|
285
|
+
const permissions = definition?.permissions;
|
|
286
|
+
if (!permissions || typeof permissions !== 'object' || Array.isArray(permissions))
|
|
287
|
+
return undefined;
|
|
288
|
+
const policy = permissions;
|
|
289
|
+
const fields = ['approval', 'filesystem', 'unattended'].flatMap(key => typeof policy[key] === 'string' ? [`${key}=${policy[key]}`] : []);
|
|
290
|
+
return fields.length ? fields.join(',') : undefined;
|
|
291
|
+
}
|
|
292
|
+
function auditTask(operation, task, previousState, newState = task.state, revision) {
|
|
293
|
+
const materialSameState = operation === 'block' || operation === 'unblock' || operation === 'settling';
|
|
294
|
+
if (previousState === newState && !materialSameState)
|
|
295
|
+
return;
|
|
296
|
+
recordFleetAuditResource('task', task.task_id);
|
|
297
|
+
if (task.room_id)
|
|
298
|
+
recordFleetAuditResource('room', task.room_id);
|
|
299
|
+
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
300
|
+
const definitions = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.agent_definition]) ?? []);
|
|
301
|
+
const presentations = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.presentation]) ?? []);
|
|
302
|
+
const semanticOperation = operation === 'recover'
|
|
303
|
+
? newState === 'active' ? 'work' : newState === 'done' ? 'done'
|
|
304
|
+
: newState === 'cancelled' ? 'cancel' : undefined
|
|
305
|
+
: operation;
|
|
306
|
+
if (!semanticOperation)
|
|
307
|
+
return;
|
|
308
|
+
recordFleetAuditPresentation({ kind: 'task', operation: semanticOperation, id: task.task_id, title: task.title,
|
|
309
|
+
previousState, newState,
|
|
310
|
+
revision: revision ?? task.blocked?.at ?? task.ended_at ?? task.started_at ?? task.created_at,
|
|
311
|
+
list: task.list_name ?? 'default',
|
|
312
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
313
|
+
roomId: task.room_id, agents: [...task.member_roles].sort((a, b) => a.name.localeCompare(b.name)).map(member => ({ name: member.name,
|
|
314
|
+
brain: safeSelectionSummary(definitions.get(member.name), 'brain'),
|
|
315
|
+
role: safeSelectionSummary(definitions.get(member.name), 'role') === 'unresolved'
|
|
316
|
+
? member.cowork_role : safeSelectionSummary(definitions.get(member.name), 'role'),
|
|
317
|
+
permissions: safePermissionsSummary(definitions.get(member.name)),
|
|
318
|
+
configuration: presentations.get(member.name) })) });
|
|
319
|
+
}
|
|
320
|
+
function auditRoom(operation, room, previousState, newState = room.state) {
|
|
321
|
+
if (previousState === newState)
|
|
322
|
+
return;
|
|
323
|
+
recordFleetAuditResource('room', room.room_id);
|
|
324
|
+
const semanticOperation = operation === 'recover'
|
|
325
|
+
? newState === 'active' ? 'activate' : newState === 'deleted' ? 'delete'
|
|
326
|
+
: newState === 'closing' ? 'close' : undefined
|
|
327
|
+
: operation;
|
|
328
|
+
if (!semanticOperation)
|
|
329
|
+
return;
|
|
330
|
+
recordFleetAuditPresentation({ kind: 'room', operation: semanticOperation, id: room.room_id, previousState, newState,
|
|
331
|
+
revision: room.closed_at ?? room.activated_at ?? room.close?.accepted_at ?? room.created_at,
|
|
332
|
+
name: room.room_name, taskId: room.task_id,
|
|
333
|
+
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
334
|
+
participants: [...room.member_seats].sort((a, b) => a.role_name.localeCompare(b.role_name)).map(member => ({ name: member.role_name, id: member.identity_cid,
|
|
335
|
+
brain: safeSelectionSummary(member.launch?.agent_definition, 'brain'),
|
|
336
|
+
role: safeSelectionSummary(member.launch?.agent_definition, 'role') === 'unresolved'
|
|
337
|
+
? member.cowork_role : safeSelectionSummary(member.launch?.agent_definition, 'role'),
|
|
338
|
+
permissions: safePermissionsSummary(member.launch?.agent_definition),
|
|
339
|
+
configuration: member.launch?.presentation })) });
|
|
340
|
+
}
|
|
341
|
+
const roomActionMarkdown = (title, room, fields = []) => {
|
|
342
|
+
recordFleetAuditResource('room', room.room_id);
|
|
343
|
+
return renderMarkdownResult({
|
|
344
|
+
icon: '🏠', title,
|
|
345
|
+
fields: [
|
|
346
|
+
{ label: 'ID', value: room.room_id, kind: 'code' },
|
|
347
|
+
{ label: 'Status', value: roomStatus(room.state), kind: 'markdown' },
|
|
348
|
+
...fields,
|
|
349
|
+
],
|
|
350
|
+
});
|
|
351
|
+
};
|
|
195
352
|
const PUBLIC_SETTLE_WAIT_MS = 60_000;
|
|
196
353
|
const PUBLIC_SETTLE_POLL_MS = 100;
|
|
197
354
|
const sleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms); });
|
|
@@ -208,6 +365,10 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
208
365
|
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
209
366
|
recoveryHint: `External settle worker failed to start. Retry task recover ${taskId}.`,
|
|
210
367
|
});
|
|
368
|
+
const task = getTask(taskId);
|
|
369
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
370
|
+
state: task.state, category: 'settlement_failed',
|
|
371
|
+
eventId: task.terminal_intent?.error_at ?? task.terminal_intent?.accepted_at ?? task.created_at });
|
|
211
372
|
throw error;
|
|
212
373
|
}
|
|
213
374
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -220,7 +381,54 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
220
381
|
}
|
|
221
382
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
222
383
|
}
|
|
223
|
-
|
|
384
|
+
const task = getTask(taskId);
|
|
385
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
386
|
+
state: task.state, category: 'settlement_pending',
|
|
387
|
+
eventId: task.terminal_intent?.accepted_at ?? task.created_at });
|
|
388
|
+
return { task, timedOut: true };
|
|
389
|
+
}
|
|
390
|
+
/** Launch the deletion settle worker and wait boundedly for physical absence. */
|
|
391
|
+
async function launchTaskDeleteWorker(taskId, configPath) {
|
|
392
|
+
const readDeletion = () => {
|
|
393
|
+
try {
|
|
394
|
+
const task = getDeletingTask(taskId);
|
|
395
|
+
return { present: true, errorAt: task.deletion?.error_at, error: task.deletion?.error };
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
// Only proven physical absence counts as deleted; anything else propagates.
|
|
399
|
+
if (error instanceof TaskStateError && /task not found/.test(error.message))
|
|
400
|
+
return { present: false };
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const before = readDeletion();
|
|
405
|
+
if (!before.present)
|
|
406
|
+
return { deleted: true, timedOut: false };
|
|
407
|
+
try {
|
|
408
|
+
await launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, configPath);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
await taskRoomService(configPath).recordDeletionError({
|
|
412
|
+
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
413
|
+
recoveryHint: `External delete worker failed to start. Retry task delete ${taskId} ${taskId}.`,
|
|
414
|
+
}).catch(() => { });
|
|
415
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
416
|
+
state: 'deleting', category: 'settlement_failed', eventId: new Date().toISOString() });
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
420
|
+
while (Date.now() < deadline) {
|
|
421
|
+
const current = readDeletion();
|
|
422
|
+
if (!current.present)
|
|
423
|
+
return { deleted: true, timedOut: false };
|
|
424
|
+
if (current.errorAt !== before.errorAt && current.error) {
|
|
425
|
+
throw new TaskStateError(current.error);
|
|
426
|
+
}
|
|
427
|
+
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
428
|
+
}
|
|
429
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
430
|
+
state: 'deleting', category: 'settlement_pending', eventId: new Date().toISOString() });
|
|
431
|
+
return { deleted: false, timedOut: true };
|
|
224
432
|
}
|
|
225
433
|
async function launchRoomDeleteWorker(roomId, configPath) {
|
|
226
434
|
const previousErrorAt = getRoomRecord(roomId)?.close?.error_at;
|
|
@@ -232,6 +440,11 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
232
440
|
actor: { kind: 'local_control', surface: 'cli' }, roomId, error: errorText(error),
|
|
233
441
|
recoveryHint: `External delete worker failed to start. Retry room delete ${roomId} ${roomId}.`,
|
|
234
442
|
});
|
|
443
|
+
const room = getRoomRecord(roomId);
|
|
444
|
+
if (room)
|
|
445
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
446
|
+
state: room.state, category: 'cleanup_failed',
|
|
447
|
+
eventId: room.close?.error_at ?? room.close?.accepted_at ?? room.created_at });
|
|
235
448
|
throw error;
|
|
236
449
|
}
|
|
237
450
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -244,7 +457,12 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
244
457
|
}
|
|
245
458
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
246
459
|
}
|
|
247
|
-
|
|
460
|
+
const remaining = getRoomRecord(roomId);
|
|
461
|
+
if (remaining)
|
|
462
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
463
|
+
state: remaining.state, category: 'cleanup_pending',
|
|
464
|
+
eventId: remaining.close?.accepted_at ?? remaining.created_at });
|
|
465
|
+
return { deleted: remaining === undefined, timedOut: true };
|
|
248
466
|
}
|
|
249
467
|
function loadCfg(opts) {
|
|
250
468
|
return loadConfig(opts.configuration);
|
|
@@ -271,7 +489,7 @@ function resolveRoomTemplate(cfg, name) {
|
|
|
271
489
|
const template = resolveTemplate(name, allTemplates(cfg));
|
|
272
490
|
if (!template)
|
|
273
491
|
throw new Error(`template not found: ${name}`);
|
|
274
|
-
return snapshotTemplate(template);
|
|
492
|
+
return snapshotTemplate(template, cfg.agentTemplates);
|
|
275
493
|
}
|
|
276
494
|
function durableTaskRoomTemplate(task, room) {
|
|
277
495
|
const snapshot = room.template_snapshot;
|
|
@@ -315,22 +533,36 @@ async function provisionRoom(cfg, input) {
|
|
|
315
533
|
throw new ConfigError('rooms.owner: public_invite or public_invite_file is required when attach_owner is enabled');
|
|
316
534
|
const cowork = coworkFor(cfg);
|
|
317
535
|
const goal = input.goal?.trim() || input.name;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
536
|
+
const unlockSnapshot = input.template ? acquireLaunchSnapshotLock() : undefined;
|
|
537
|
+
let launchTemplate;
|
|
538
|
+
let record;
|
|
539
|
+
try {
|
|
540
|
+
launchTemplate = input.template
|
|
541
|
+
? sealTemplateSnapshot(input.template, cfg.agentTemplates ?? {}) : undefined;
|
|
542
|
+
const briefing = input.brief?.trim() || launchTemplate?.contract?.trim() || goal;
|
|
543
|
+
const created = await cowork.createRoom({
|
|
544
|
+
room_name: input.name,
|
|
545
|
+
goal,
|
|
546
|
+
briefing,
|
|
547
|
+
quiet_membership: launchTemplate?.room?.quiet_membership,
|
|
548
|
+
anonymous: launchTemplate?.room?.anonymous,
|
|
549
|
+
});
|
|
550
|
+
record = createRoomRecord({
|
|
551
|
+
room_id: created.room_id,
|
|
552
|
+
room_name: input.name,
|
|
553
|
+
room_identity_cid: created.identity_cid,
|
|
554
|
+
task_id: input.taskId,
|
|
555
|
+
template_snapshot: launchTemplate,
|
|
556
|
+
});
|
|
557
|
+
input.onCreated?.(record);
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
unlockSnapshot?.();
|
|
561
|
+
if (launchTemplate?.launch_snapshot_hash)
|
|
562
|
+
releaseLaunchSnapshot(launchTemplate.launch_snapshot_hash);
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
unlockSnapshot?.();
|
|
334
566
|
record = advanceSaga(record.room_id, 'create_room', 1);
|
|
335
567
|
if (attachOwner) {
|
|
336
568
|
try {
|
|
@@ -346,21 +578,36 @@ async function provisionRoom(cfg, input) {
|
|
|
346
578
|
setSagaError(record.room_id, error instanceof Error ? error.message : String(error), mismatch
|
|
347
579
|
? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
|
|
348
580
|
: 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
|
|
581
|
+
const failed = getRoomRecord(record.room_id);
|
|
582
|
+
if (failed)
|
|
583
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
584
|
+
id: failed.room_id, state: failed.state, category: 'provision_failed',
|
|
585
|
+
eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
|
|
349
586
|
throw error;
|
|
350
587
|
}
|
|
351
588
|
}
|
|
352
589
|
record = advanceSaga(record.room_id, 'create_members', 3);
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
590
|
+
if (launchTemplate && launchTemplate.members.length > 0) {
|
|
591
|
+
try {
|
|
592
|
+
record = await provisionMembers({
|
|
593
|
+
cfg,
|
|
594
|
+
cowork,
|
|
595
|
+
roomId: record.room_id,
|
|
596
|
+
taskId: input.taskId,
|
|
597
|
+
template: launchTemplate,
|
|
598
|
+
binPath: getBinPath(),
|
|
599
|
+
brief: input.brief,
|
|
600
|
+
goal: input.goal,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
catch (error) {
|
|
604
|
+
const failed = getRoomRecord(record.room_id);
|
|
605
|
+
if (failed)
|
|
606
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
607
|
+
id: failed.room_id, state: failed.state, category: 'provision_failed',
|
|
608
|
+
eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
364
611
|
}
|
|
365
612
|
else {
|
|
366
613
|
record = activateRoom(record.room_id);
|
|
@@ -382,12 +629,11 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
382
629
|
return;
|
|
383
630
|
}
|
|
384
631
|
if (!templates.length) {
|
|
385
|
-
console.log('No templates configured.');
|
|
632
|
+
console.log('No templates configured. Run interactive ours-fleet init to review and create a complete default setup.');
|
|
386
633
|
return;
|
|
387
634
|
}
|
|
388
635
|
for (const t of templates) {
|
|
389
|
-
|
|
390
|
-
console.log(`${t.name}@${t.version}${tag} ${t.description}`);
|
|
636
|
+
console.log(`${t.name}@${t.version} ${t.description} [file: ${t.sourceFile ?? 'manifest'}]`);
|
|
391
637
|
}
|
|
392
638
|
}
|
|
393
639
|
catch (e) {
|
|
@@ -405,11 +651,13 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
405
651
|
return;
|
|
406
652
|
}
|
|
407
653
|
console.log(`${t.name}@${t.version} ${t.description}`);
|
|
654
|
+
console.log(`Source: ${t.sourceFile ?? 'manifest'}`);
|
|
408
655
|
if (t.contract)
|
|
409
656
|
console.log(`\nContract:\n${t.contract}`);
|
|
410
657
|
console.log(`\nMembers:`);
|
|
411
658
|
for (const m of t.members)
|
|
412
|
-
console.log(` ${m.slot}: ${m.count}× ${m.role} (
|
|
659
|
+
console.log(` ${m.slot}: ${m.count}× ${m.role} (`
|
|
660
|
+
+ `agent template: ${m.agent_template})`);
|
|
413
661
|
console.log(`\nContent hash: ${t.content_hash}`);
|
|
414
662
|
}
|
|
415
663
|
catch (e) {
|
|
@@ -452,17 +700,35 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
452
700
|
.option('--brief-file <path>', 'task brief from file')
|
|
453
701
|
.option('--backlog', 'create in backlog (do not start immediately)')
|
|
454
702
|
.option('--no-room', 'create task without a room')
|
|
703
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
704
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
455
705
|
.option('--idempotency-key <key>', 'idempotency key')
|
|
456
706
|
.option('--list <name>', 'task list (default: default)')
|
|
707
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
708
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
709
|
+
.option('--agent-template <id>', 'Agent Template for the current member block')
|
|
710
|
+
.option('--brain <id>', 'Brain preset for the current member block')
|
|
711
|
+
.option('--role <id>', 'Role preset for the current member block')
|
|
712
|
+
.option('--approval <mode>', 'approval for the current member block')
|
|
713
|
+
.option('--filesystem <mode>', 'filesystem for the current member block')
|
|
714
|
+
.option('--unattended <mode>', 'unattended policy for the current member block')
|
|
715
|
+
.option('--cwd <path>', 'working directory for the current member block')
|
|
716
|
+
.option('--model <id>', 'model for the current member block')
|
|
717
|
+
.option('--effort <level>', 'reasoning effort for the current member block')
|
|
718
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for the current temporary member')
|
|
719
|
+
.option('--no-loops', 'disable loops for the current temporary member')
|
|
457
720
|
.option('--json', 'JSON output')
|
|
458
|
-
.action(async (opts) => {
|
|
721
|
+
.action(async (opts, command) => {
|
|
459
722
|
try {
|
|
723
|
+
const members = cliMemberOverrides(opts.membersFile, commandArgv(command));
|
|
460
724
|
const record = await taskRoomService(opts.configuration).createTask({
|
|
461
725
|
actor: { kind: 'local_control', surface: 'cli' }, title: opts.title,
|
|
462
726
|
brief: opts.brief, briefFile: opts.briefFile, template: opts.template,
|
|
463
727
|
backlog: opts.backlog, noRoom: opts.room === false,
|
|
728
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
464
729
|
idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
|
|
465
730
|
list: opts.list,
|
|
731
|
+
members,
|
|
466
732
|
});
|
|
467
733
|
if (opts.json) {
|
|
468
734
|
console.log(JSON.stringify({ schema_version: 1, task: record }, null, 2));
|
|
@@ -487,7 +753,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
487
753
|
.option('--list <name>', 'filter by task list')
|
|
488
754
|
.option('--group-by-list', 'group deterministic results by list (JSON)')
|
|
489
755
|
.option('--json', 'JSON output')
|
|
490
|
-
.action((opts) => {
|
|
756
|
+
.action(async (opts) => {
|
|
491
757
|
try {
|
|
492
758
|
let stateFilter;
|
|
493
759
|
if (opts.state && opts.state !== 'all') {
|
|
@@ -513,9 +779,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
513
779
|
taskCmd.command('lists')
|
|
514
780
|
.description('list named task lists')
|
|
515
781
|
.option('--json', 'JSON output')
|
|
516
|
-
.action((opts) => {
|
|
782
|
+
.action(async (opts) => {
|
|
517
783
|
try {
|
|
518
|
-
const
|
|
784
|
+
const service = taskRoomService(opts.configuration);
|
|
785
|
+
const lists = service.listTaskLists();
|
|
519
786
|
if (opts.json) {
|
|
520
787
|
console.log(JSON.stringify({ schema_version: 1, lists }, null, 2));
|
|
521
788
|
return;
|
|
@@ -606,9 +873,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
606
873
|
taskCmd.command('show <id>')
|
|
607
874
|
.description('show task details')
|
|
608
875
|
.option('--json', 'JSON output')
|
|
609
|
-
.action((id, opts) => {
|
|
876
|
+
.action(async (id, opts) => {
|
|
610
877
|
try {
|
|
611
|
-
const
|
|
878
|
+
const service = taskRoomService();
|
|
879
|
+
const { task: t, orchestration: room } = service.getTask(id);
|
|
612
880
|
if (opts.json) {
|
|
613
881
|
console.log(JSON.stringify({
|
|
614
882
|
schema_version: 1, task: t, orchestration: room ?? null,
|
|
@@ -633,7 +901,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
633
901
|
],
|
|
634
902
|
sections: [
|
|
635
903
|
...(t.member_roles.length ? [{
|
|
636
|
-
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`
|
|
904
|
+
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`
|
|
905
|
+
+ seatConfigurationSuffix(room?.member_seats
|
|
906
|
+
.find(seat => seat.role_name === m.name)?.launch?.presentation)),
|
|
637
907
|
}] : []),
|
|
638
908
|
...(room ? roomStartupSections(room) : []),
|
|
639
909
|
],
|
|
@@ -646,12 +916,30 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
646
916
|
}
|
|
647
917
|
});
|
|
648
918
|
cOpt(taskCmd.command('start <id>'))
|
|
649
|
-
.description('start a
|
|
919
|
+
.description('idempotently select a plan, provision, and start a task')
|
|
920
|
+
.option('--template <name>', 'room template')
|
|
921
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
922
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
923
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
924
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
925
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
926
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
927
|
+
.option('--role <id>', 'Role preset for current member')
|
|
928
|
+
.option('--approval <mode>', 'approval for current member')
|
|
929
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
930
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
931
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
932
|
+
.option('--model <id>', 'model for current member')
|
|
933
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
934
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for current member')
|
|
935
|
+
.option('--no-loops', 'disable loops for current member')
|
|
650
936
|
.option('--json', 'JSON output')
|
|
651
|
-
.action(async (id, opts) => {
|
|
937
|
+
.action(async (id, opts, command) => {
|
|
652
938
|
try {
|
|
653
939
|
const t = await taskRoomService(opts.configuration).startTask({
|
|
654
940
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
941
|
+
template: opts.template, members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
942
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
655
943
|
});
|
|
656
944
|
if (opts.json) {
|
|
657
945
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
@@ -676,9 +964,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
676
964
|
.option('--json', 'JSON output')
|
|
677
965
|
.action((id, opts) => {
|
|
678
966
|
try {
|
|
967
|
+
const previous = getTask(id).state;
|
|
679
968
|
const t = taskRoomService().blockTask({
|
|
680
969
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, reason: opts.reason,
|
|
681
970
|
});
|
|
971
|
+
auditTask('block', t, previous);
|
|
682
972
|
if (opts.json) {
|
|
683
973
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
684
974
|
return;
|
|
@@ -696,9 +986,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
696
986
|
.option('--json', 'JSON output')
|
|
697
987
|
.action((id, opts) => {
|
|
698
988
|
try {
|
|
989
|
+
const prior = getTask(id);
|
|
990
|
+
const previous = prior.state;
|
|
699
991
|
const t = taskRoomService().unblockTask({
|
|
700
992
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
701
993
|
});
|
|
994
|
+
auditTask('unblock', t, previous, t.state, prior.blocked?.at);
|
|
702
995
|
if (opts.json) {
|
|
703
996
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
704
997
|
return;
|
|
@@ -716,9 +1009,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
716
1009
|
.option('--json', 'JSON output')
|
|
717
1010
|
.action((id, opts) => {
|
|
718
1011
|
try {
|
|
1012
|
+
const previous = getTask(id).state;
|
|
719
1013
|
const t = taskRoomService().reviewTask({
|
|
720
1014
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
721
1015
|
});
|
|
1016
|
+
auditTask('review', t, previous);
|
|
722
1017
|
if (opts.json) {
|
|
723
1018
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
724
1019
|
return;
|
|
@@ -738,6 +1033,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
738
1033
|
.option('--json', 'JSON output')
|
|
739
1034
|
.action(async (id, opts) => {
|
|
740
1035
|
try {
|
|
1036
|
+
const previous = getTask(id).state;
|
|
741
1037
|
let summary = opts.summary;
|
|
742
1038
|
if (opts.summaryFile)
|
|
743
1039
|
summary = readFileSync(opts.summaryFile, 'utf8');
|
|
@@ -745,10 +1041,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
745
1041
|
const plan = await taskRoomService(opts.configuration).completeTask({
|
|
746
1042
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
747
1043
|
});
|
|
1044
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1045
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
748
1046
|
const settled = plan.settlementRequired
|
|
749
1047
|
? await launchTaskSettleWorker(id, opts.configuration)
|
|
750
1048
|
: { task: plan.task, timedOut: false };
|
|
751
1049
|
const t = settled.task;
|
|
1050
|
+
auditTask('done', t, previous);
|
|
752
1051
|
if (opts.json) {
|
|
753
1052
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
754
1053
|
return;
|
|
@@ -776,13 +1075,17 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
776
1075
|
try {
|
|
777
1076
|
if (id !== confirmId)
|
|
778
1077
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
1078
|
+
const previous = getTask(id).state;
|
|
779
1079
|
const plan = await taskRoomService(opts.configuration).cancelTask({
|
|
780
1080
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
781
1081
|
});
|
|
1082
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1083
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
782
1084
|
const settled = plan.settlementRequired
|
|
783
1085
|
? await launchTaskSettleWorker(id, opts.configuration)
|
|
784
1086
|
: { task: plan.task, timedOut: false };
|
|
785
1087
|
const t = settled.task;
|
|
1088
|
+
auditTask('cancel', t, previous);
|
|
786
1089
|
if (opts.json) {
|
|
787
1090
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
788
1091
|
return;
|
|
@@ -804,21 +1107,46 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
804
1107
|
}
|
|
805
1108
|
});
|
|
806
1109
|
cOpt(taskCmd.command('delete <id> <confirm-id>'))
|
|
807
|
-
.description('delete a
|
|
1110
|
+
.description('permanently delete a task in any lifecycle state (requires ID twice for confirmation)')
|
|
808
1111
|
.option('--json', 'JSON output')
|
|
809
|
-
.action((id, confirmId, opts) => {
|
|
1112
|
+
.action(async (id, confirmId, opts) => {
|
|
810
1113
|
try {
|
|
811
1114
|
if (id !== confirmId)
|
|
812
1115
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
813
|
-
const
|
|
1116
|
+
const accepted = await taskRoomService(opts.configuration).requestTaskDeletion({
|
|
814
1117
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
815
1118
|
});
|
|
1119
|
+
if (accepted.status === 'already_absent') {
|
|
1120
|
+
if (opts.json) {
|
|
1121
|
+
console.log(JSON.stringify({
|
|
1122
|
+
schema_version: 1, task_id: id, deleted: false, already_absent: true,
|
|
1123
|
+
}, null, 2));
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
console.log(renderMarkdownResult({
|
|
1127
|
+
icon: '🗑️', title: 'Task already absent',
|
|
1128
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1129
|
+
}));
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
816
1133
|
if (opts.json) {
|
|
817
|
-
console.log(JSON.stringify({
|
|
1134
|
+
console.log(JSON.stringify({
|
|
1135
|
+
schema_version: 1, task_id: id, accepted: true,
|
|
1136
|
+
deleted: settled.deleted, pending: settled.timedOut,
|
|
1137
|
+
}, null, 2));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (!settled.deleted) {
|
|
1141
|
+
console.log(renderMarkdownFailure({
|
|
1142
|
+
kind: 'pending', subject: `task delete ${id} ${id}`,
|
|
1143
|
+
detail: 'The deletion was accepted and cleanup is still settling.',
|
|
1144
|
+
action: `Re-run ours-fleet task delete ${id} ${id} or ours-fleet task recover ${id}.`,
|
|
1145
|
+
}));
|
|
818
1146
|
return;
|
|
819
1147
|
}
|
|
820
1148
|
console.log(renderMarkdownResult({
|
|
821
|
-
icon: '🗑️', title:
|
|
1149
|
+
icon: '🗑️', title: 'Task deleted',
|
|
822
1150
|
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
823
1151
|
}));
|
|
824
1152
|
}
|
|
@@ -833,9 +1161,29 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
833
1161
|
.option('--json', 'JSON output')
|
|
834
1162
|
.action(async (id, opts) => {
|
|
835
1163
|
try {
|
|
1164
|
+
const previous = getTask(id).state;
|
|
836
1165
|
const app = taskRoomService(opts.configuration);
|
|
837
1166
|
const actor = { kind: 'local_control', surface: 'cli' };
|
|
838
1167
|
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
1168
|
+
if (begin.kind === 'deletion_worker_required') {
|
|
1169
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
1170
|
+
const payload = { schema_version: 1, task_id: id, deleted: settled.deleted };
|
|
1171
|
+
if (opts.json) {
|
|
1172
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
console.log(settled.deleted
|
|
1176
|
+
? renderMarkdownResult({
|
|
1177
|
+
icon: '🗑️', title: 'Task deletion completed by recovery',
|
|
1178
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1179
|
+
})
|
|
1180
|
+
: renderMarkdownFailure({
|
|
1181
|
+
kind: 'pending', subject: `task recover ${id}`,
|
|
1182
|
+
detail: 'The task is pending deletion and cleanup is still settling.',
|
|
1183
|
+
action: `Run ours-fleet task delete ${id} ${id} to retry.`,
|
|
1184
|
+
}));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
839
1187
|
const recovered = begin.kind === 'terminal_worker_required'
|
|
840
1188
|
? await (async () => {
|
|
841
1189
|
const settled = await launchTaskSettleWorker(id, opts.configuration);
|
|
@@ -843,6 +1191,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
843
1191
|
})()
|
|
844
1192
|
: begin.result;
|
|
845
1193
|
const t = recovered.task;
|
|
1194
|
+
auditTask('recover', t, previous);
|
|
846
1195
|
const room = recovered.room;
|
|
847
1196
|
const recoveryActions = recovered.issues.map(issue => {
|
|
848
1197
|
if (issue.code === 'terminal_pending')
|
|
@@ -913,6 +1262,35 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
913
1262
|
dieTaskRoom(e);
|
|
914
1263
|
}
|
|
915
1264
|
});
|
|
1265
|
+
cOpt(taskCmd.command('_settle_delete <id>', { hidden: true }))
|
|
1266
|
+
.description('internal: settle a previously accepted task deletion')
|
|
1267
|
+
.option('--json', 'JSON output')
|
|
1268
|
+
.action(async (id, opts) => {
|
|
1269
|
+
try {
|
|
1270
|
+
const app = taskRoomService(opts.configuration);
|
|
1271
|
+
const result = await app.settleTaskDeletion({
|
|
1272
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1273
|
+
});
|
|
1274
|
+
if (opts.json) {
|
|
1275
|
+
console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
console.log(renderMarkdownResult({
|
|
1279
|
+
icon: '🗑️', title: result.deleted ? 'Task deletion settled' : 'Task already absent',
|
|
1280
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1281
|
+
}));
|
|
1282
|
+
}
|
|
1283
|
+
catch (e) {
|
|
1284
|
+
await taskRoomService(opts.configuration).recordDeletionError({
|
|
1285
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1286
|
+
error: errorText(e),
|
|
1287
|
+
recoveryHint: `External delete worker failed. Retry task delete ${id} ${id}.`,
|
|
1288
|
+
}).catch(() => { });
|
|
1289
|
+
if (opts.json)
|
|
1290
|
+
die(e);
|
|
1291
|
+
dieTaskRoom(e);
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
916
1294
|
cOpt(taskCmd.command('_recover <id>', { hidden: true }))
|
|
917
1295
|
.description('internal: settle and continue task recovery')
|
|
918
1296
|
.option('--json', 'JSON output')
|
|
@@ -921,6 +1299,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
921
1299
|
const actor = { kind: 'internal_worker', surface: 'cli' };
|
|
922
1300
|
try {
|
|
923
1301
|
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
1302
|
+
if (begin.kind === 'deletion_worker_required') {
|
|
1303
|
+
const settled = await app.settleTaskDeletion({ actor, taskId: id });
|
|
1304
|
+
console.log(JSON.stringify({ schema_version: 1, deletion: settled }, null, 2));
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
924
1307
|
const result = begin.kind === 'terminal_worker_required'
|
|
925
1308
|
? await (async () => {
|
|
926
1309
|
try {
|
|
@@ -945,15 +1328,35 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
945
1328
|
}
|
|
946
1329
|
});
|
|
947
1330
|
cOpt(taskCmd.command('work <id>'))
|
|
948
|
-
.description('
|
|
1331
|
+
.description('deprecated alias for task start')
|
|
949
1332
|
.option('--template <name>', 'room template')
|
|
1333
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1334
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1335
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
1336
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
1337
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
1338
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
1339
|
+
.option('--role <id>', 'Role preset for current member')
|
|
1340
|
+
.option('--approval <mode>', 'approval for current member')
|
|
1341
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
1342
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
1343
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
1344
|
+
.option('--model <id>', 'model for current member')
|
|
1345
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
1346
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for current member')
|
|
1347
|
+
.option('--no-loops', 'disable loops for current member')
|
|
950
1348
|
.option('--json', 'JSON output')
|
|
951
|
-
.action(async (id, opts) => {
|
|
1349
|
+
.action(async (id, opts, command) => {
|
|
952
1350
|
try {
|
|
1351
|
+
const previous = getTask(id).state;
|
|
1352
|
+
console.error('warning: `fleet task work` is deprecated; use `fleet task start`');
|
|
953
1353
|
const result = await taskRoomService(opts.configuration).ensureTaskWork({
|
|
954
1354
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, template: opts.template,
|
|
1355
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1356
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
955
1357
|
});
|
|
956
1358
|
const t = result.task;
|
|
1359
|
+
auditTask('work', t, previous);
|
|
957
1360
|
if (result.status === 'already_active') {
|
|
958
1361
|
if (opts.json) {
|
|
959
1362
|
console.log(JSON.stringify({ schema_version: 1, task: t, status: 'already_active' }, null, 2));
|
|
@@ -992,6 +1395,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
992
1395
|
.option('--json', 'JSON output')
|
|
993
1396
|
.action(async (id, opts) => {
|
|
994
1397
|
try {
|
|
1398
|
+
const previous = getTask(id).state;
|
|
995
1399
|
let summary = opts.summary;
|
|
996
1400
|
if (opts.summaryFile)
|
|
997
1401
|
summary = readFileSync(opts.summaryFile, 'utf8');
|
|
@@ -999,10 +1403,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
999
1403
|
const plan = await taskRoomService(opts.configuration).finishTask({
|
|
1000
1404
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
1001
1405
|
});
|
|
1406
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1407
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
1002
1408
|
const settled = plan.settlementRequired
|
|
1003
1409
|
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1004
1410
|
: { task: plan.task, timedOut: false };
|
|
1005
1411
|
const t = settled.task;
|
|
1412
|
+
auditTask('finish', t, previous);
|
|
1006
1413
|
if (opts.json) {
|
|
1007
1414
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
1008
1415
|
return;
|
|
@@ -1032,15 +1439,32 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1032
1439
|
.description('create a standalone room')
|
|
1033
1440
|
.requiredOption('--name <name>', 'room name')
|
|
1034
1441
|
.option('--template <name>', 'room template')
|
|
1442
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1443
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1035
1444
|
.option('--goal <text>', 'room goal')
|
|
1036
1445
|
.option('--brief <text>', 'room briefing')
|
|
1037
1446
|
.option('--brief-file <path>', 'room briefing from file')
|
|
1447
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
1448
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
1449
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
1450
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
1451
|
+
.option('--role <id>', 'Role preset for current member')
|
|
1452
|
+
.option('--approval <mode>', 'approval for current member')
|
|
1453
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
1454
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
1455
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
1456
|
+
.option('--model <id>', 'model for current member')
|
|
1457
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
1458
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for current member')
|
|
1459
|
+
.option('--no-loops', 'disable loops for current member')
|
|
1038
1460
|
.option('--json', 'JSON output')
|
|
1039
|
-
.action(async (opts) => {
|
|
1461
|
+
.action(async (opts, command) => {
|
|
1040
1462
|
try {
|
|
1041
1463
|
const record = await taskRoomService(opts.configuration).createRoom({
|
|
1042
1464
|
actor: { kind: 'local_control', surface: 'cli' }, name: opts.name,
|
|
1043
1465
|
template: opts.template, goal: opts.goal, brief: opts.brief, briefFile: opts.briefFile,
|
|
1466
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1467
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
1044
1468
|
});
|
|
1045
1469
|
if (opts.json) {
|
|
1046
1470
|
console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
|
|
@@ -1109,7 +1533,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1109
1533
|
],
|
|
1110
1534
|
sections: [
|
|
1111
1535
|
...(cowork.seats.length ? [{
|
|
1112
|
-
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1536
|
+
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1537
|
+
+ seatConfigurationSuffix(r?.member_seats
|
|
1538
|
+
.find(seat => seat.identity_cid === s.identity_cid)?.launch?.presentation)),
|
|
1113
1539
|
}] : []),
|
|
1114
1540
|
...(r ? roomStartupSections(r) : []),
|
|
1115
1541
|
],
|
|
@@ -1126,7 +1552,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1126
1552
|
.option('--json', 'JSON output')
|
|
1127
1553
|
.action(async (id, opts) => {
|
|
1128
1554
|
try {
|
|
1129
|
-
const { room } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1555
|
+
const { room, orchestration } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1130
1556
|
const url = `http://localhost:4460/room/${id}`;
|
|
1131
1557
|
if (opts.json) {
|
|
1132
1558
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, url, room_name: room.room_name }, null, 2));
|
|
@@ -1171,7 +1597,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1171
1597
|
],
|
|
1172
1598
|
sections: [{
|
|
1173
1599
|
heading: 'Members',
|
|
1174
|
-
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1600
|
+
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1601
|
+
+ seatConfigurationSuffix(r?.member_seats
|
|
1602
|
+
.find(seat => seat.identity_cid === s.identity_cid)?.launch?.presentation)) }
|
|
1175
1603
|
: { items: ['No members found.'] }),
|
|
1176
1604
|
}],
|
|
1177
1605
|
}));
|
|
@@ -1186,10 +1614,16 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1186
1614
|
try {
|
|
1187
1615
|
if (id !== confirmId)
|
|
1188
1616
|
throw taskRoomPublicError('room_confirmation_mismatch');
|
|
1617
|
+
const previous = getRoomRecord(id);
|
|
1189
1618
|
await taskRoomService(opts.configuration).requestRoomDeletion({
|
|
1190
1619
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1191
1620
|
});
|
|
1621
|
+
const closing = getRoomRecord(id);
|
|
1622
|
+
if (previous && closing && previous.state !== closing.state)
|
|
1623
|
+
auditRoom('close', closing, previous.state, closing.state);
|
|
1192
1624
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1625
|
+
if (closing)
|
|
1626
|
+
auditRoom('delete', closing, closing.state, settled.deleted ? 'deleted' : 'closing');
|
|
1193
1627
|
if (opts.json) {
|
|
1194
1628
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, deleted: settled.deleted }, null, 2));
|
|
1195
1629
|
return;
|
|
@@ -1226,11 +1660,14 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1226
1660
|
.option('--json', 'JSON output')
|
|
1227
1661
|
.action(async (id, opts) => {
|
|
1228
1662
|
try {
|
|
1663
|
+
const previous = getRoomRecord(id);
|
|
1229
1664
|
const recovered = await taskRoomService(opts.configuration).recoverRoom({
|
|
1230
1665
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1231
1666
|
});
|
|
1232
1667
|
if (recovered.kind === 'deletion_worker_required') {
|
|
1233
1668
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1669
|
+
if (previous)
|
|
1670
|
+
auditRoom('recover', previous, previous.state, settled.deleted ? 'deleted' : 'closing');
|
|
1234
1671
|
if (opts.json) {
|
|
1235
1672
|
console.log(JSON.stringify({
|
|
1236
1673
|
schema_version: 1, room: null, orchestration: null,
|
|
@@ -1251,6 +1688,8 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1251
1688
|
return;
|
|
1252
1689
|
}
|
|
1253
1690
|
const { room: cowork, orchestration: r, issues: actions } = recovered;
|
|
1691
|
+
if (r)
|
|
1692
|
+
auditRoom('recover', r, previous?.state ?? 'unresolved', r.state);
|
|
1254
1693
|
if (opts.json) {
|
|
1255
1694
|
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null, recovery_actions: actions }, null, 2));
|
|
1256
1695
|
return;
|