@ours.network/fleet 1.1.0-nightly.9 → 1.1.1
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 +285 -46
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +7 -4
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +4 -4
- package/dist/application/role-creation-service.js +8 -1
- 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 +92 -39
- package/dist/application/task-room-service.js +562 -289
- package/dist/briefing.js +79 -22
- package/dist/build-info.json +5 -5
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +129 -47
- package/dist/config.d.ts +42 -3
- package/dist/config.js +202 -29
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +195 -41
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +22 -1
- package/dist/fleet-command-audit.js +355 -50
- package/dist/fleet-proxy.d.ts +5 -2
- package/dist/harness/claude-code-session.js +5 -1
- 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 +9 -2
- package/dist/harness/codex-session.js +21 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +104 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +1 -1
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +631 -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-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +19 -3
- package/dist/owner-channel/channel.js +301 -120
- package/dist/owner-channel/commands.d.ts +28 -6
- package/dist/owner-channel/commands.js +109 -27
- package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
- package/dist/owner-channel/lifecycle-outbox.js +87 -0
- package/dist/owner-channel/ours-client.d.ts +19 -0
- package/dist/owner-channel/ours-client.js +3 -0
- package/dist/permissions.js +1 -1
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +450 -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 +361 -285
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +23 -7
- package/dist/rooms-tasks/config.js +16 -25
- package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
- package/dist/rooms-tasks/cowork-adapter.js +13 -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 +8 -0
- package/dist/rooms-tasks/external-worker.js +38 -0
- 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/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +9 -2
- package/dist/rooms-tasks/provision.js +146 -83
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-room-name.d.ts +12 -0
- package/dist/rooms-tasks/task-room-name.js +61 -0
- 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 -1
- package/dist/rooms-tasks/templates.js +34 -3
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +10 -4
- package/dist/rooms-tasks/types.d.ts +92 -7
- package/dist/rooms-tasks/types.js +14 -1
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +97 -47
- package/dist/session/acp.d.ts +27 -1
- package/dist/session/acp.js +307 -17
- 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 +1 -1
- package/dist/session/control.js +17 -1
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/stall-watchdog.d.ts +60 -0
- package/dist/session/stall-watchdog.js +210 -0
- package/dist/session/types.d.ts +29 -8
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +9 -0
- package/dist/spawn.js +77 -9
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +25 -7
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
- package/dist/web-app/index.html +1 -1
- package/package.json +4 -4
- package/presets/brain-catalog.json +17 -0
- package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
- package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
- package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
- 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/brains/codex-gpt-6-astra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +37 -0
- package/presets/fleet/roles/Critic.yaml +23 -1
- package/presets/fleet/roles/Developer.yaml +27 -5
- package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
- package/presets/fleet/room_templates/pair.yaml +9 -7
- package/presets/fleet/room_templates/single.yaml +5 -4
- package/presets/fleet/room_templates/team.yaml +12 -8
- package/presets/manifest.json +2 -1
- package/presets/fleet/agents/Architect.yaml +0 -3
- package/presets/fleet/agents/Secretary.yaml +0 -3
- package/presets/fleet/agents/Tester.yaml +0 -3
- package/presets/fleet/roles/Agent.yaml +0 -6
- package/presets/fleet/roles/Architect.yaml +0 -6
- package/presets/fleet/roles/Secretary.yaml +0 -6
- package/presets/fleet/roles/Tester.yaml +0 -6
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
3
4
|
import { loadConfig, ConfigError } from '../config.js';
|
|
4
5
|
import { provisionMembers, getBinPath } from './provision.js';
|
|
5
|
-
import { launchFleetWorker } from './external-worker.js';
|
|
6
|
+
import { launchFleetWorker, presentFleetWorkerLifecycle } from './external-worker.js';
|
|
6
7
|
import { resolveTemplate, snapshotTemplate, } from './templates.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
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, TaskStateError, } from './task-state.js';
|
|
43
|
+
import { getRoomRecord, RoomStateError, } from './room-state.js';
|
|
44
|
+
import { createCoworkAdapter } from './cowork-adapter.js';
|
|
10
45
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
11
|
-
import { TaskRoomApplicationError, TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
46
|
+
import { recordTaskProvisioningOutcome, TaskRoomApplicationError, TaskRoomApplicationService, } from '../application/task-room-service.js';
|
|
12
47
|
import { TaskListError } from './task-lists.js';
|
|
13
|
-
import { recordFleetAuditFailure, recordFleetAuditPresentation, recordFleetAuditResource, } from '../fleet-command-audit.js';
|
|
48
|
+
import { beginFleetAuditCollection, consumeFleetAuditCollection, recordFleetAuditFailure, recordFleetAuditPresentation, recordFleetAuditResource, } from '../fleet-command-audit.js';
|
|
49
|
+
import { renderAgentConfiguration } from '../lifecycle-summary.js';
|
|
50
|
+
import { withFileLock } from '../atomic-file.js';
|
|
51
|
+
import { stateRoot } from '../paths.js';
|
|
14
52
|
class TaskRoomPublicError extends Error {
|
|
15
53
|
code;
|
|
16
54
|
fields;
|
|
@@ -29,6 +67,11 @@ function taskRoomPublicError(code, fields = {}) {
|
|
|
29
67
|
}));
|
|
30
68
|
return new TaskRoomPublicError(code, validated);
|
|
31
69
|
}
|
|
70
|
+
async function presentDetachedProvisioningOutcome(outcome) {
|
|
71
|
+
beginFleetAuditCollection();
|
|
72
|
+
recordTaskProvisioningOutcome(outcome);
|
|
73
|
+
await presentFleetWorkerLifecycle(consumeFleetAuditCollection().presentations ?? []);
|
|
74
|
+
}
|
|
32
75
|
function taskRoomPublicFailure(error) {
|
|
33
76
|
const f = error.fields;
|
|
34
77
|
switch (error.code) {
|
|
@@ -43,7 +86,7 @@ function taskRoomPublicFailure(error) {
|
|
|
43
86
|
case 'template_not_found': return {
|
|
44
87
|
legacy: `template not found: ${f.template ?? 'requested-template'}`, kind: 'not_found',
|
|
45
88
|
detail: `The requested template ${f.template ?? ''}`.trim() + ' was not found.',
|
|
46
|
-
action: 'Run ours-fleet init to
|
|
89
|
+
action: 'Run interactive ours-fleet init to review and create a complete default setup, then run ours-fleet template list.',
|
|
47
90
|
};
|
|
48
91
|
case 'template_mismatch': return {
|
|
49
92
|
legacy: `template ${f.requested ?? 'requested-template'} does not match room ${f.room ?? 'requested-room'}'s provisioned template ${f.provisioned ?? 'recorded-template'}`,
|
|
@@ -53,7 +96,7 @@ function taskRoomPublicFailure(error) {
|
|
|
53
96
|
case 'task_template_drift': return {
|
|
54
97
|
legacy: `task template snapshot no longer matches ${f.template ?? 'the recorded template'}`,
|
|
55
98
|
kind: 'state', detail: 'The task template snapshot no longer matches the recorded template.',
|
|
56
|
-
action: '
|
|
99
|
+
action: 'Inspect the matching Task and Room, then retry the originating start command.',
|
|
57
100
|
};
|
|
58
101
|
case 'task_terminal': return {
|
|
59
102
|
legacy: `task ${f.task ?? 'requested-task'} is in terminal state '${f.state ?? 'terminal'}'`,
|
|
@@ -66,9 +109,9 @@ function taskRoomPublicFailure(error) {
|
|
|
66
109
|
action: 'Run ours-fleet task show to inspect the completed task.',
|
|
67
110
|
};
|
|
68
111
|
case 'task_non_resumable': return {
|
|
69
|
-
legacy: `task ${f.task ?? 'requested-task'} has room ${f.room ?? 'requested-room'} in a non-resumable state
|
|
112
|
+
legacy: `task ${f.task ?? 'requested-task'} has room ${f.room ?? 'requested-room'} in a non-resumable state`,
|
|
70
113
|
kind: 'state', detail: 'The task’s room is in a non-resumable state.',
|
|
71
|
-
action: `
|
|
114
|
+
action: `Inspect the Task and Room, then retry ours-fleet task start ${f.task ?? '<id>'}.`,
|
|
72
115
|
};
|
|
73
116
|
case 'room_filter': return {
|
|
74
117
|
legacy: 'room state filter must be active, provisioning, or all', kind: 'usage',
|
|
@@ -127,7 +170,7 @@ function isKnownTaskStateMessage(message) {
|
|
|
127
170
|
new RegExp(`^task ${SAFE_ID_WORD} is not tied to room ${SAFE_ID_WORD}$`, 'u'),
|
|
128
171
|
new RegExp(`^task ${SAFE_ID_WORD} has no terminal intent$`, 'u'),
|
|
129
172
|
new RegExp(`^task ${SAFE_ID_WORD} reached terminal state '${TASK_STATE_WORD}' outside its intent$`, 'u'),
|
|
130
|
-
new RegExp(`^
|
|
173
|
+
new RegExp(`^task ${SAFE_ID_WORD} is pending deletion; run 'ours-fleet task delete ${SAFE_ID_WORD} ${SAFE_ID_WORD}' to retry cleanup$`, 'u'),
|
|
131
174
|
].some(pattern => pattern.test(message));
|
|
132
175
|
}
|
|
133
176
|
function isKnownRoomStateMessage(message) {
|
|
@@ -215,6 +258,42 @@ const taskActionMarkdown = (title, task, fields = []) => {
|
|
|
215
258
|
],
|
|
216
259
|
});
|
|
217
260
|
};
|
|
261
|
+
function provisioningMarkdown(outcome) {
|
|
262
|
+
const room = outcome.room;
|
|
263
|
+
if (outcome.kind === 'failed')
|
|
264
|
+
return renderMarkdownFailure({
|
|
265
|
+
kind: 'state', subject: `task start ${outcome.task.task_id}`,
|
|
266
|
+
detail: outcome.blocker ?? 'Provisioning reached a terminal failure.',
|
|
267
|
+
action: outcome.next_action ?? `Run ours-fleet task start ${outcome.task.task_id}.`,
|
|
268
|
+
});
|
|
269
|
+
return renderMarkdownResult({
|
|
270
|
+
icon: outcome.kind === 'ready' ? '✅' : outcome.next_action ? '⚠️' : '⏳',
|
|
271
|
+
title: outcome.kind === 'ready' ? 'Room provisioning complete'
|
|
272
|
+
: outcome.next_action ? 'Room provisioning needs attention' : 'Room provisioning continues',
|
|
273
|
+
fields: [
|
|
274
|
+
{ label: 'Task', value: `${outcome.task.task_id} — ${outcome.task.title}` },
|
|
275
|
+
...(room ? [{ label: 'Room', value: `${room.room_id} — ${room.room_name}` }] : []),
|
|
276
|
+
...(outcome.launch.template ? [{ label: 'Template', value: outcome.launch.template, kind: 'code' }] : []),
|
|
277
|
+
{ label: 'Launch', value: `anonymous=${outcome.launch.anonymous ? 'yes' : 'no'}, owner=${outcome.launch.owner_attached ? 'attached' : 'not-attached'}` },
|
|
278
|
+
{ label: 'Lifecycle', value: outcome.kind },
|
|
279
|
+
{ label: 'Members', value: `${outcome.members.active}/${outcome.members.expected} active; ${outcome.members.launched}/${outcome.members.expected} launched` },
|
|
280
|
+
...(outcome.blocker ? [{ label: 'Blocker', value: outcome.blocker }] : []),
|
|
281
|
+
...(outcome.next_action ? [{ label: 'Next action', value: outcome.next_action }] : []),
|
|
282
|
+
],
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async function continueProvisioningInBackground(taskId, configPath) {
|
|
286
|
+
await launchFleetWorker(['task', '_provision', taskId], `task-provision-${taskId}`, configPath);
|
|
287
|
+
}
|
|
288
|
+
const taskProvisioningWorkerLockPath = (taskId) => join(stateRoot(), 'locks', 'task-provisioning-worker', encodeURIComponent(taskId));
|
|
289
|
+
/**
|
|
290
|
+
* Shared launch-configuration line for CLI member listings. Pre-upgrade seats
|
|
291
|
+
* without a captured presentation keep the minimal listing rather than
|
|
292
|
+
* claiming resolved facts. Output is already escaped (markdownItems boundary).
|
|
293
|
+
*/
|
|
294
|
+
function seatConfigurationSuffix(presentation) {
|
|
295
|
+
return presentation ? ` — ${renderAgentConfiguration(presentation)}` : '';
|
|
296
|
+
}
|
|
218
297
|
function safeSelectionSummary(definition, kind) {
|
|
219
298
|
const selected = definition?.[kind];
|
|
220
299
|
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
@@ -246,6 +325,10 @@ function safePermissionsSummary(definition) {
|
|
|
246
325
|
return fields.length ? fields.join(',') : undefined;
|
|
247
326
|
}
|
|
248
327
|
function auditTask(operation, task, previousState, newState = task.state, revision) {
|
|
328
|
+
// Accepted terminal intent is internal progress. The Owner receives either
|
|
329
|
+
// the final outcome or one truthful failure/pending notice, never both.
|
|
330
|
+
if (operation === 'settling')
|
|
331
|
+
return;
|
|
249
332
|
const materialSameState = operation === 'block' || operation === 'unblock' || operation === 'settling';
|
|
250
333
|
if (previousState === newState && !materialSameState)
|
|
251
334
|
return;
|
|
@@ -254,6 +337,7 @@ function auditTask(operation, task, previousState, newState = task.state, revisi
|
|
|
254
337
|
recordFleetAuditResource('room', task.room_id);
|
|
255
338
|
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
256
339
|
const definitions = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.agent_definition]) ?? []);
|
|
340
|
+
const presentations = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.presentation]) ?? []);
|
|
257
341
|
const semanticOperation = operation === 'recover'
|
|
258
342
|
? newState === 'active' ? 'work' : newState === 'done' ? 'done'
|
|
259
343
|
: newState === 'cancelled' ? 'cancel' : undefined
|
|
@@ -265,23 +349,20 @@ function auditTask(operation, task, previousState, newState = task.state, revisi
|
|
|
265
349
|
revision: revision ?? task.blocked?.at ?? task.ended_at ?? task.started_at ?? task.created_at,
|
|
266
350
|
list: task.list_name ?? 'default',
|
|
267
351
|
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
268
|
-
roomId: task.room_id,
|
|
352
|
+
roomId: task.room_id, roomName: room?.room_name,
|
|
353
|
+
reason: operation === 'block' ? task.blocked?.reason : undefined,
|
|
354
|
+
agents: [...task.member_roles].sort((a, b) => a.name.localeCompare(b.name)).map(member => ({ name: member.name,
|
|
269
355
|
brain: safeSelectionSummary(definitions.get(member.name), 'brain'),
|
|
270
356
|
role: safeSelectionSummary(definitions.get(member.name), 'role') === 'unresolved'
|
|
271
357
|
? member.cowork_role : safeSelectionSummary(definitions.get(member.name), 'role'),
|
|
272
|
-
permissions: safePermissionsSummary(definitions.get(member.name))
|
|
358
|
+
permissions: safePermissionsSummary(definitions.get(member.name)),
|
|
359
|
+
configuration: presentations.get(member.name) })) });
|
|
273
360
|
}
|
|
274
361
|
function auditRoom(operation, room, previousState, newState = room.state) {
|
|
275
362
|
if (previousState === newState)
|
|
276
363
|
return;
|
|
277
364
|
recordFleetAuditResource('room', room.room_id);
|
|
278
|
-
|
|
279
|
-
? newState === 'active' ? 'activate' : newState === 'deleted' ? 'delete'
|
|
280
|
-
: newState === 'closing' ? 'close' : undefined
|
|
281
|
-
: operation;
|
|
282
|
-
if (!semanticOperation)
|
|
283
|
-
return;
|
|
284
|
-
recordFleetAuditPresentation({ kind: 'room', operation: semanticOperation, id: room.room_id, previousState, newState,
|
|
365
|
+
recordFleetAuditPresentation({ kind: 'room', operation, id: room.room_id, previousState, newState,
|
|
285
366
|
revision: room.closed_at ?? room.activated_at ?? room.close?.accepted_at ?? room.created_at,
|
|
286
367
|
name: room.room_name, taskId: room.task_id,
|
|
287
368
|
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
@@ -289,7 +370,8 @@ function auditRoom(operation, room, previousState, newState = room.state) {
|
|
|
289
370
|
brain: safeSelectionSummary(member.launch?.agent_definition, 'brain'),
|
|
290
371
|
role: safeSelectionSummary(member.launch?.agent_definition, 'role') === 'unresolved'
|
|
291
372
|
? member.cowork_role : safeSelectionSummary(member.launch?.agent_definition, 'role'),
|
|
292
|
-
permissions: safePermissionsSummary(member.launch?.agent_definition)
|
|
373
|
+
permissions: safePermissionsSummary(member.launch?.agent_definition),
|
|
374
|
+
configuration: member.launch?.presentation })) });
|
|
293
375
|
}
|
|
294
376
|
const roomActionMarkdown = (title, room, fields = []) => {
|
|
295
377
|
recordFleetAuditResource('room', room.room_id);
|
|
@@ -308,7 +390,7 @@ const sleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms); });
|
|
|
308
390
|
function errorText(error) {
|
|
309
391
|
return error instanceof Error ? error.message : String(error);
|
|
310
392
|
}
|
|
311
|
-
async function launchTaskSettleWorker(taskId, configPath) {
|
|
393
|
+
async function launchTaskSettleWorker(taskId, retryCommand, configPath) {
|
|
312
394
|
const previousErrorAt = getTask(taskId).terminal_intent?.error_at;
|
|
313
395
|
try {
|
|
314
396
|
await launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, configPath);
|
|
@@ -316,11 +398,11 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
316
398
|
catch (error) {
|
|
317
399
|
await taskRoomService(configPath).recordSettlementError({
|
|
318
400
|
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
319
|
-
recoveryHint: `External settle worker failed to start. Retry
|
|
401
|
+
recoveryHint: `External settle worker failed to start. Retry ${retryCommand}.`,
|
|
320
402
|
});
|
|
321
403
|
const task = getTask(taskId);
|
|
322
404
|
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
323
|
-
state: task.state, category: 'settlement_failed',
|
|
405
|
+
label: task.title, state: task.state, category: 'settlement_failed',
|
|
324
406
|
eventId: task.terminal_intent?.error_at ?? task.terminal_intent?.accepted_at ?? task.created_at });
|
|
325
407
|
throw error;
|
|
326
408
|
}
|
|
@@ -336,10 +418,55 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
336
418
|
}
|
|
337
419
|
const task = getTask(taskId);
|
|
338
420
|
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
339
|
-
state: task.state, category: 'settlement_pending',
|
|
421
|
+
label: task.title, state: task.state, category: 'settlement_pending',
|
|
340
422
|
eventId: task.terminal_intent?.accepted_at ?? task.created_at });
|
|
341
423
|
return { task, timedOut: true };
|
|
342
424
|
}
|
|
425
|
+
/** Launch the deletion settle worker and wait boundedly for physical absence. */
|
|
426
|
+
async function launchTaskDeleteWorker(taskId, configPath) {
|
|
427
|
+
const readDeletion = () => {
|
|
428
|
+
try {
|
|
429
|
+
const task = getDeletingTask(taskId);
|
|
430
|
+
return { present: true, title: task.title,
|
|
431
|
+
errorAt: task.deletion?.error_at, error: task.deletion?.error };
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
// Only proven physical absence counts as deleted; anything else propagates.
|
|
435
|
+
if (error instanceof TaskStateError && /task not found/.test(error.message))
|
|
436
|
+
return { present: false };
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const before = readDeletion();
|
|
441
|
+
if (!before.present)
|
|
442
|
+
return { deleted: true, timedOut: false };
|
|
443
|
+
try {
|
|
444
|
+
await launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, configPath);
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
await taskRoomService(configPath).recordDeletionError({
|
|
448
|
+
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
449
|
+
recoveryHint: `External delete worker failed to start. Retry task delete ${taskId} ${taskId}.`,
|
|
450
|
+
}).catch(() => { });
|
|
451
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
452
|
+
label: before.title, state: 'deleting', category: 'settlement_failed', eventId: new Date().toISOString() });
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
456
|
+
while (Date.now() < deadline) {
|
|
457
|
+
const current = readDeletion();
|
|
458
|
+
if (!current.present)
|
|
459
|
+
return { deleted: true, timedOut: false };
|
|
460
|
+
if (current.errorAt !== before.errorAt && current.error) {
|
|
461
|
+
throw new TaskStateError(current.error);
|
|
462
|
+
}
|
|
463
|
+
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
464
|
+
}
|
|
465
|
+
const remaining = readDeletion();
|
|
466
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
467
|
+
label: remaining.title ?? before.title, state: 'deleting', category: 'settlement_pending', eventId: new Date().toISOString() });
|
|
468
|
+
return { deleted: false, timedOut: true };
|
|
469
|
+
}
|
|
343
470
|
async function launchRoomDeleteWorker(roomId, configPath) {
|
|
344
471
|
const previousErrorAt = getRoomRecord(roomId)?.close?.error_at;
|
|
345
472
|
try {
|
|
@@ -353,7 +480,7 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
353
480
|
const room = getRoomRecord(roomId);
|
|
354
481
|
if (room)
|
|
355
482
|
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
356
|
-
state: room.state, category: 'cleanup_failed',
|
|
483
|
+
label: room.room_name, state: room.state, category: 'cleanup_failed',
|
|
357
484
|
eventId: room.close?.error_at ?? room.close?.accepted_at ?? room.created_at });
|
|
358
485
|
throw error;
|
|
359
486
|
}
|
|
@@ -370,7 +497,7 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
370
497
|
const remaining = getRoomRecord(roomId);
|
|
371
498
|
if (remaining)
|
|
372
499
|
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
373
|
-
state: remaining.state, category: 'cleanup_pending',
|
|
500
|
+
label: remaining.room_name, state: remaining.state, category: 'cleanup_pending',
|
|
374
501
|
eventId: remaining.close?.accepted_at ?? remaining.created_at });
|
|
375
502
|
return { deleted: remaining === undefined, timedOut: true };
|
|
376
503
|
}
|
|
@@ -399,7 +526,7 @@ function resolveRoomTemplate(cfg, name) {
|
|
|
399
526
|
const template = resolveTemplate(name, allTemplates(cfg));
|
|
400
527
|
if (!template)
|
|
401
528
|
throw new Error(`template not found: ${name}`);
|
|
402
|
-
return snapshotTemplate(template);
|
|
529
|
+
return snapshotTemplate(template, cfg.agentTemplates);
|
|
403
530
|
}
|
|
404
531
|
function durableTaskRoomTemplate(task, room) {
|
|
405
532
|
const snapshot = room.template_snapshot;
|
|
@@ -434,84 +561,6 @@ function roomStartupSections(room) {
|
|
|
434
561
|
}
|
|
435
562
|
return sections;
|
|
436
563
|
}
|
|
437
|
-
async function provisionRoom(cfg, input) {
|
|
438
|
-
const rooms = cfg.rooms;
|
|
439
|
-
if (!rooms)
|
|
440
|
-
throw new ConfigError('rooms: configuration is required');
|
|
441
|
-
const attachOwner = rooms.defaults?.attach_owner !== false;
|
|
442
|
-
if (attachOwner && !cfg.ownerInvite)
|
|
443
|
-
throw new ConfigError('rooms.owner: public_invite or public_invite_file is required when attach_owner is enabled');
|
|
444
|
-
const cowork = coworkFor(cfg);
|
|
445
|
-
const goal = input.goal?.trim() || input.name;
|
|
446
|
-
const briefing = input.brief?.trim() || input.template?.contract?.trim() || goal;
|
|
447
|
-
const created = await cowork.createRoom({
|
|
448
|
-
room_name: input.name,
|
|
449
|
-
goal,
|
|
450
|
-
briefing,
|
|
451
|
-
quiet_membership: input.template?.room?.quiet_membership,
|
|
452
|
-
anonymous: input.template?.room?.anonymous,
|
|
453
|
-
});
|
|
454
|
-
let record = createRoomRecord({
|
|
455
|
-
room_id: created.room_id,
|
|
456
|
-
room_name: input.name,
|
|
457
|
-
room_identity_cid: created.identity_cid,
|
|
458
|
-
task_id: input.taskId,
|
|
459
|
-
template_snapshot: input.template,
|
|
460
|
-
});
|
|
461
|
-
input.onCreated?.(record);
|
|
462
|
-
record = advanceSaga(record.room_id, 'create_room', 1);
|
|
463
|
-
if (attachOwner) {
|
|
464
|
-
try {
|
|
465
|
-
record = advanceSaga(record.room_id, 'attach_owner', 2);
|
|
466
|
-
const accepted = await cowork.acceptInvite(record.room_id, cfg.ownerInvite, {
|
|
467
|
-
role: rooms.owner.role,
|
|
468
|
-
expected_cid: rooms.owner.expected_cid,
|
|
469
|
-
});
|
|
470
|
-
record = setOwnerSeat(record.room_id, accepted.seat_cid, cfg.ownerInviteFingerprint ?? '');
|
|
471
|
-
}
|
|
472
|
-
catch (error) {
|
|
473
|
-
const mismatch = error instanceof CoworkProtocolError && /CID|expected/i.test(error.message);
|
|
474
|
-
setSagaError(record.room_id, error instanceof Error ? error.message : String(error), mismatch
|
|
475
|
-
? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
|
|
476
|
-
: 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
|
|
477
|
-
const failed = getRoomRecord(record.room_id);
|
|
478
|
-
if (failed)
|
|
479
|
-
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
480
|
-
id: failed.room_id, state: failed.state, category: 'provision_failed',
|
|
481
|
-
eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
|
|
482
|
-
throw error;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
record = advanceSaga(record.room_id, 'create_members', 3);
|
|
486
|
-
if (input.template && input.template.members.length > 0) {
|
|
487
|
-
try {
|
|
488
|
-
record = await provisionMembers({
|
|
489
|
-
cfg,
|
|
490
|
-
cowork,
|
|
491
|
-
roomId: record.room_id,
|
|
492
|
-
taskId: input.taskId,
|
|
493
|
-
template: input.template,
|
|
494
|
-
binPath: getBinPath(),
|
|
495
|
-
brief: input.brief,
|
|
496
|
-
goal: input.goal,
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
const failed = getRoomRecord(record.room_id);
|
|
501
|
-
if (failed)
|
|
502
|
-
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
503
|
-
id: failed.room_id, state: failed.state, category: 'provision_failed',
|
|
504
|
-
eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
|
|
505
|
-
throw error;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
record = activateRoom(record.room_id);
|
|
510
|
-
if (input.taskId)
|
|
511
|
-
activateTask(input.taskId);
|
|
512
|
-
}
|
|
513
|
-
return record;
|
|
514
|
-
}
|
|
515
564
|
export function registerTemplateCommands(parent, cOpt) {
|
|
516
565
|
const templateCmd = parent.command('template').description('room template operations');
|
|
517
566
|
cOpt(templateCmd.command('list'))
|
|
@@ -525,7 +574,7 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
525
574
|
return;
|
|
526
575
|
}
|
|
527
576
|
if (!templates.length) {
|
|
528
|
-
console.log('No templates configured. Run ours-fleet init to
|
|
577
|
+
console.log('No templates configured. Run interactive ours-fleet init to review and create a complete default setup.');
|
|
529
578
|
return;
|
|
530
579
|
}
|
|
531
580
|
for (const t of templates) {
|
|
@@ -553,7 +602,7 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
553
602
|
console.log(`\nMembers:`);
|
|
554
603
|
for (const m of t.members)
|
|
555
604
|
console.log(` ${m.slot}: ${m.count}× ${m.role} (`
|
|
556
|
-
+
|
|
605
|
+
+ `agent template: ${m.agent_template})`);
|
|
557
606
|
console.log(`\nContent hash: ${t.content_hash}`);
|
|
558
607
|
}
|
|
559
608
|
catch (e) {
|
|
@@ -596,20 +645,50 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
596
645
|
.option('--brief-file <path>', 'task brief from file')
|
|
597
646
|
.option('--backlog', 'create in backlog (do not start immediately)')
|
|
598
647
|
.option('--no-room', 'create task without a room')
|
|
648
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
649
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
599
650
|
.option('--idempotency-key <key>', 'idempotency key')
|
|
600
651
|
.option('--list <name>', 'task list (default: default)')
|
|
652
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
653
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
654
|
+
.option('--agent-template <id>', 'Agent Template for the current member block')
|
|
655
|
+
.option('--brain <id>', 'Brain preset for the current member block')
|
|
656
|
+
.option('--role <id>', 'Role preset for the current member block')
|
|
657
|
+
.option('--approval <mode>', 'approval for the current member block')
|
|
658
|
+
.option('--filesystem <mode>', 'filesystem for the current member block')
|
|
659
|
+
.option('--unattended <mode>', 'unattended policy for the current member block')
|
|
660
|
+
.option('--cwd <path>', 'working directory for the current member block')
|
|
661
|
+
.option('--model <id>', 'model for the current member block')
|
|
662
|
+
.option('--effort <level>', 'reasoning effort for the current member block')
|
|
663
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for the current temporary member')
|
|
664
|
+
.option('--no-loops', 'disable loops for the current temporary member')
|
|
601
665
|
.option('--json', 'JSON output')
|
|
602
|
-
.action(async (opts) => {
|
|
666
|
+
.action(async (opts, command) => {
|
|
603
667
|
try {
|
|
604
|
-
const
|
|
668
|
+
const members = cliMemberOverrides(opts.membersFile, commandArgv(command));
|
|
669
|
+
const service = taskRoomService(opts.configuration);
|
|
670
|
+
const record = await service.createTask({
|
|
605
671
|
actor: { kind: 'local_control', surface: 'cli' }, title: opts.title,
|
|
606
672
|
brief: opts.brief, briefFile: opts.briefFile, template: opts.template,
|
|
607
673
|
backlog: opts.backlog, noRoom: opts.room === false,
|
|
674
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
608
675
|
idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
|
|
609
676
|
list: opts.list,
|
|
677
|
+
members,
|
|
610
678
|
});
|
|
679
|
+
const provisioning = !opts.backlog && opts.room !== false && record.room_id
|
|
680
|
+
? service.taskProvisioningOutcome(record.task_id) : undefined;
|
|
681
|
+
if (provisioning?.kind === 'in_progress' && !provisioning.next_action)
|
|
682
|
+
await continueProvisioningInBackground(record.task_id, opts.configuration);
|
|
683
|
+
if (provisioning)
|
|
684
|
+
recordTaskProvisioningOutcome(provisioning);
|
|
611
685
|
if (opts.json) {
|
|
612
|
-
console.log(JSON.stringify({ schema_version: 1, task: record
|
|
686
|
+
console.log(JSON.stringify({ schema_version: 1, task: record,
|
|
687
|
+
...(provisioning ? { provisioning } : {}) }, null, 2));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (provisioning) {
|
|
691
|
+
console.log(provisioningMarkdown(provisioning));
|
|
613
692
|
return;
|
|
614
693
|
}
|
|
615
694
|
console.log(taskActionMarkdown('Task created', record, [
|
|
@@ -631,7 +710,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
631
710
|
.option('--list <name>', 'filter by task list')
|
|
632
711
|
.option('--group-by-list', 'group deterministic results by list (JSON)')
|
|
633
712
|
.option('--json', 'JSON output')
|
|
634
|
-
.action((opts) => {
|
|
713
|
+
.action(async (opts) => {
|
|
635
714
|
try {
|
|
636
715
|
let stateFilter;
|
|
637
716
|
if (opts.state && opts.state !== 'all') {
|
|
@@ -657,9 +736,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
657
736
|
taskCmd.command('lists')
|
|
658
737
|
.description('list named task lists')
|
|
659
738
|
.option('--json', 'JSON output')
|
|
660
|
-
.action((opts) => {
|
|
739
|
+
.action(async (opts) => {
|
|
661
740
|
try {
|
|
662
|
-
const
|
|
741
|
+
const service = taskRoomService(opts.configuration);
|
|
742
|
+
const lists = service.listTaskLists();
|
|
663
743
|
if (opts.json) {
|
|
664
744
|
console.log(JSON.stringify({ schema_version: 1, lists }, null, 2));
|
|
665
745
|
return;
|
|
@@ -750,9 +830,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
750
830
|
taskCmd.command('show <id>')
|
|
751
831
|
.description('show task details')
|
|
752
832
|
.option('--json', 'JSON output')
|
|
753
|
-
.action((id, opts) => {
|
|
833
|
+
.action(async (id, opts) => {
|
|
754
834
|
try {
|
|
755
|
-
const
|
|
835
|
+
const service = taskRoomService();
|
|
836
|
+
const { task: t, orchestration: room } = service.getTask(id);
|
|
756
837
|
if (opts.json) {
|
|
757
838
|
console.log(JSON.stringify({
|
|
758
839
|
schema_version: 1, task: t, orchestration: room ?? null,
|
|
@@ -777,7 +858,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
777
858
|
],
|
|
778
859
|
sections: [
|
|
779
860
|
...(t.member_roles.length ? [{
|
|
780
|
-
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`
|
|
861
|
+
heading: 'Members', markdownItems: t.member_roles.map(m => `${markdownCode(m.name)} — ${markdownProse(m.cowork_role)} — ${markdownCode(m.identity_cid)}`
|
|
862
|
+
+ seatConfigurationSuffix(room?.member_seats
|
|
863
|
+
.find(seat => seat.role_name === m.name)?.launch?.presentation)),
|
|
781
864
|
}] : []),
|
|
782
865
|
...(room ? roomStartupSections(room) : []),
|
|
783
866
|
],
|
|
@@ -790,18 +873,41 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
790
873
|
}
|
|
791
874
|
});
|
|
792
875
|
cOpt(taskCmd.command('start <id>'))
|
|
793
|
-
.description('start a
|
|
876
|
+
.description('idempotently select a plan, provision, and start a task')
|
|
877
|
+
.option('--template <name>', 'room template')
|
|
878
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
879
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
880
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
881
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
882
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
883
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
884
|
+
.option('--role <id>', 'Role preset for current member')
|
|
885
|
+
.option('--approval <mode>', 'approval for current member')
|
|
886
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
887
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
888
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
889
|
+
.option('--model <id>', 'model for current member')
|
|
890
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
891
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for current member')
|
|
892
|
+
.option('--no-loops', 'disable loops for current member')
|
|
794
893
|
.option('--json', 'JSON output')
|
|
795
|
-
.action(async (id, opts) => {
|
|
894
|
+
.action(async (id, opts, command) => {
|
|
796
895
|
try {
|
|
797
|
-
const
|
|
896
|
+
const service = taskRoomService(opts.configuration);
|
|
897
|
+
const t = await service.startTask({
|
|
798
898
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
899
|
+
template: opts.template, members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
900
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
799
901
|
});
|
|
902
|
+
const provisioning = service.taskProvisioningOutcome(t.task_id);
|
|
903
|
+
if (provisioning.kind === 'in_progress' && !provisioning.next_action)
|
|
904
|
+
await continueProvisioningInBackground(t.task_id, opts.configuration);
|
|
905
|
+
recordTaskProvisioningOutcome(provisioning);
|
|
800
906
|
if (opts.json) {
|
|
801
|
-
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
907
|
+
console.log(JSON.stringify({ schema_version: 1, task: t, provisioning }, null, 2));
|
|
802
908
|
return;
|
|
803
909
|
}
|
|
804
|
-
console.log(
|
|
910
|
+
console.log(provisioningMarkdown(provisioning));
|
|
805
911
|
}
|
|
806
912
|
catch (e) {
|
|
807
913
|
if (e instanceof TaskRoomApplicationError && e.code === 'task_template_drift')
|
|
@@ -900,7 +1006,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
900
1006
|
if (plan.task.terminal_intent?.status === 'pending')
|
|
901
1007
|
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
902
1008
|
const settled = plan.settlementRequired
|
|
903
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1009
|
+
? await launchTaskSettleWorker(id, `ours-fleet task done ${id}`, opts.configuration)
|
|
904
1010
|
: { task: plan.task, timedOut: false };
|
|
905
1011
|
const t = settled.task;
|
|
906
1012
|
auditTask('done', t, previous);
|
|
@@ -912,7 +1018,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
912
1018
|
console.log(renderMarkdownFailure({
|
|
913
1019
|
kind: 'pending', subject: `task done ${id}`,
|
|
914
1020
|
detail: 'The completion request was accepted and is still being settled.',
|
|
915
|
-
action: `
|
|
1021
|
+
action: `Re-run ours-fleet task done ${id}.`,
|
|
916
1022
|
}));
|
|
917
1023
|
return;
|
|
918
1024
|
}
|
|
@@ -938,7 +1044,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
938
1044
|
if (plan.task.terminal_intent?.status === 'pending')
|
|
939
1045
|
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
940
1046
|
const settled = plan.settlementRequired
|
|
941
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1047
|
+
? await launchTaskSettleWorker(id, `ours-fleet task cancel ${id} ${id}`, opts.configuration)
|
|
942
1048
|
: { task: plan.task, timedOut: false };
|
|
943
1049
|
const t = settled.task;
|
|
944
1050
|
auditTask('cancel', t, previous);
|
|
@@ -950,7 +1056,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
950
1056
|
console.log(renderMarkdownFailure({
|
|
951
1057
|
kind: 'pending', subject: `task cancel ${id} ${id}`,
|
|
952
1058
|
detail: 'The cancellation request was accepted and is still being settled.',
|
|
953
|
-
action: `
|
|
1059
|
+
action: `Re-run ours-fleet task cancel ${id} ${id}.`,
|
|
954
1060
|
}));
|
|
955
1061
|
return;
|
|
956
1062
|
}
|
|
@@ -963,91 +1069,47 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
963
1069
|
}
|
|
964
1070
|
});
|
|
965
1071
|
cOpt(taskCmd.command('delete <id> <confirm-id>'))
|
|
966
|
-
.description('delete a
|
|
1072
|
+
.description('permanently delete a task in any lifecycle state (requires ID twice for confirmation)')
|
|
967
1073
|
.option('--json', 'JSON output')
|
|
968
|
-
.action((id, confirmId, opts) => {
|
|
1074
|
+
.action(async (id, confirmId, opts) => {
|
|
969
1075
|
try {
|
|
970
1076
|
if (id !== confirmId)
|
|
971
1077
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
972
|
-
|
|
973
|
-
try {
|
|
974
|
-
prior = getTask(id);
|
|
975
|
-
}
|
|
976
|
-
catch { /* idempotent already-absent delete */ }
|
|
977
|
-
const deleted = taskRoomService().deleteTask({
|
|
1078
|
+
const accepted = await taskRoomService(opts.configuration).requestTaskDeletion({
|
|
978
1079
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
979
1080
|
});
|
|
980
|
-
if (
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1081
|
+
if (accepted.status === 'already_absent') {
|
|
1082
|
+
if (opts.json) {
|
|
1083
|
+
console.log(JSON.stringify({
|
|
1084
|
+
schema_version: 1, task_id: id, deleted: false, already_absent: true,
|
|
1085
|
+
}, null, 2));
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
console.log(renderMarkdownResult({
|
|
1089
|
+
icon: '🗑️', title: 'Task already absent',
|
|
1090
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1091
|
+
}));
|
|
984
1092
|
return;
|
|
985
1093
|
}
|
|
986
|
-
|
|
987
|
-
icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
|
|
988
|
-
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
989
|
-
}));
|
|
990
|
-
}
|
|
991
|
-
catch (e) {
|
|
992
|
-
if (opts.json)
|
|
993
|
-
die(e);
|
|
994
|
-
dieTaskRoom(e);
|
|
995
|
-
}
|
|
996
|
-
});
|
|
997
|
-
cOpt(taskCmd.command('recover <id>'))
|
|
998
|
-
.description('attempt to recover a stuck task')
|
|
999
|
-
.option('--json', 'JSON output')
|
|
1000
|
-
.action(async (id, opts) => {
|
|
1001
|
-
try {
|
|
1002
|
-
const previous = getTask(id).state;
|
|
1003
|
-
const app = taskRoomService(opts.configuration);
|
|
1004
|
-
const actor = { kind: 'local_control', surface: 'cli' };
|
|
1005
|
-
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
1006
|
-
const recovered = begin.kind === 'terminal_worker_required'
|
|
1007
|
-
? await (async () => {
|
|
1008
|
-
const settled = await launchTaskSettleWorker(id, opts.configuration);
|
|
1009
|
-
return app.continueTaskRecovery({ actor, taskId: id, terminalTimedOut: settled.timedOut });
|
|
1010
|
-
})()
|
|
1011
|
-
: begin.result;
|
|
1012
|
-
const t = recovered.task;
|
|
1013
|
-
auditTask('recover', t, previous);
|
|
1014
|
-
const room = recovered.room;
|
|
1015
|
-
const recoveryActions = recovered.issues.map(issue => {
|
|
1016
|
-
if (issue.code === 'terminal_pending')
|
|
1017
|
-
return `Terminal intent remains pending — retry task recover ${id}`;
|
|
1018
|
-
if (issue.code === 'waiting_cowork')
|
|
1019
|
-
return 'Cowork management socket unreachable — check ours-cowork service';
|
|
1020
|
-
if (issue.code === 'waiting_owner_invite')
|
|
1021
|
-
return 'Owner invite invalid or expired — rotate rooms.owner.public_invite in config';
|
|
1022
|
-
if (issue.code === 'owner_cid_mismatch')
|
|
1023
|
-
return 'Owner CID mismatch — verify rooms.owner.expected_cid matches Messenger identity';
|
|
1024
|
-
if (issue.code === 'member_failed')
|
|
1025
|
-
return `Member creation failed at saga step ${issue.stepIndex} — inspect and retry`;
|
|
1026
|
-
if (issue.code === 'waiting_seats')
|
|
1027
|
-
return 'Members have not accepted their one-time room invites yet — inspect role logs, then retry recovery';
|
|
1028
|
-
if (issue.code === 'resume_failed')
|
|
1029
|
-
return `Resume failed: ${issue.error}`;
|
|
1030
|
-
return 'Provisioning resumed successfully';
|
|
1031
|
-
});
|
|
1032
|
-
const result = { task: t, room: room ?? null, recovery_actions: recoveryActions };
|
|
1094
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
1033
1095
|
if (opts.json) {
|
|
1034
|
-
console.log(JSON.stringify({
|
|
1096
|
+
console.log(JSON.stringify({
|
|
1097
|
+
schema_version: 1, task_id: id, accepted: true,
|
|
1098
|
+
deleted: settled.deleted, pending: settled.timedOut,
|
|
1099
|
+
}, null, 2));
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (!settled.deleted) {
|
|
1103
|
+
console.log(renderMarkdownFailure({
|
|
1104
|
+
kind: 'pending', subject: `task delete ${id} ${id}`,
|
|
1105
|
+
detail: 'The deletion was accepted and cleanup is still settling.',
|
|
1106
|
+
action: `Re-run ours-fleet task delete ${id} ${id}.`,
|
|
1107
|
+
}));
|
|
1035
1108
|
return;
|
|
1036
1109
|
}
|
|
1037
1110
|
console.log(renderMarkdownResult({
|
|
1038
|
-
icon: '
|
|
1039
|
-
fields: [
|
|
1040
|
-
{ label: 'Task', value: t.task_id, kind: 'code' },
|
|
1041
|
-
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
1042
|
-
...(room ? [
|
|
1043
|
-
{ label: 'Room', value: room.room_id, kind: 'code' },
|
|
1044
|
-
{ label: 'Room status', value: roomStatus(room.state), kind: 'markdown' },
|
|
1045
|
-
{ label: 'Saga', value: room.saga.phase, kind: 'code' },
|
|
1046
|
-
] : []),
|
|
1047
|
-
],
|
|
1048
|
-
sections: recoveryActions.length
|
|
1049
|
-
? [{ heading: 'Next steps', items: recoveryActions }]
|
|
1050
|
-
: [{ heading: 'Result', items: ['No automated recovery action is available.'] }],
|
|
1111
|
+
icon: '🗑️', title: 'Task deleted',
|
|
1112
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1051
1113
|
}));
|
|
1052
1114
|
}
|
|
1053
1115
|
catch (e) {
|
|
@@ -1074,54 +1136,105 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1074
1136
|
catch (e) {
|
|
1075
1137
|
await taskRoomService(opts.configuration).recordSettlementError({
|
|
1076
1138
|
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1077
|
-
error: errorText(e), recoveryHint: `External settle worker failed. Retry task
|
|
1139
|
+
error: errorText(e), recoveryHint: `External settle worker failed. Retry the originating task terminal command.`,
|
|
1078
1140
|
}).catch(() => { });
|
|
1079
1141
|
if (opts.json)
|
|
1080
1142
|
die(e);
|
|
1081
1143
|
dieTaskRoom(e);
|
|
1082
1144
|
}
|
|
1083
1145
|
});
|
|
1084
|
-
cOpt(taskCmd.command('
|
|
1085
|
-
.description('internal:
|
|
1146
|
+
cOpt(taskCmd.command('_provision <id>', { hidden: true }))
|
|
1147
|
+
.description('internal: continue an accepted room-provisioning operation')
|
|
1086
1148
|
.option('--json', 'JSON output')
|
|
1087
1149
|
.action(async (id, opts) => {
|
|
1088
|
-
const app = taskRoomService(opts.configuration);
|
|
1089
|
-
const actor = { kind: 'internal_worker', surface: 'cli' };
|
|
1090
1150
|
try {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1151
|
+
await withFileLock(taskProvisioningWorkerLockPath(id), async () => {
|
|
1152
|
+
const app = taskRoomService(opts.configuration);
|
|
1153
|
+
for (;;) {
|
|
1154
|
+
const before = app.taskProvisioningOutcome(id);
|
|
1155
|
+
if (before.kind !== 'in_progress') {
|
|
1156
|
+
await presentDetachedProvisioningOutcome(before);
|
|
1157
|
+
return;
|
|
1096
1158
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1159
|
+
await app.continueTaskProvisioning({
|
|
1160
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1161
|
+
});
|
|
1162
|
+
const outcome = app.taskProvisioningOutcome(id);
|
|
1163
|
+
if (outcome.kind !== 'in_progress') {
|
|
1164
|
+
await presentDetachedProvisioningOutcome(outcome);
|
|
1165
|
+
return;
|
|
1103
1166
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1167
|
+
if (outcome.next_action)
|
|
1168
|
+
return;
|
|
1169
|
+
await sleep(1_000);
|
|
1170
|
+
}
|
|
1171
|
+
}, {}, 10 * 60_000);
|
|
1172
|
+
}
|
|
1173
|
+
catch (e) {
|
|
1174
|
+
if (opts.json)
|
|
1175
|
+
die(e);
|
|
1176
|
+
dieTaskRoom(e);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
cOpt(taskCmd.command('_settle_delete <id>', { hidden: true }))
|
|
1180
|
+
.description('internal: settle a previously accepted task deletion')
|
|
1181
|
+
.option('--json', 'JSON output')
|
|
1182
|
+
.action(async (id, opts) => {
|
|
1183
|
+
try {
|
|
1184
|
+
const app = taskRoomService(opts.configuration);
|
|
1185
|
+
const result = await app.settleTaskDeletion({
|
|
1186
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1187
|
+
});
|
|
1188
|
+
if (opts.json) {
|
|
1189
|
+
console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
console.log(renderMarkdownResult({
|
|
1193
|
+
icon: '🗑️', title: result.deleted ? 'Task deletion settled' : 'Task already absent',
|
|
1194
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1195
|
+
}));
|
|
1108
1196
|
}
|
|
1109
1197
|
catch (e) {
|
|
1198
|
+
await taskRoomService(opts.configuration).recordDeletionError({
|
|
1199
|
+
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
1200
|
+
error: errorText(e),
|
|
1201
|
+
recoveryHint: `External delete worker failed. Retry task delete ${id} ${id}.`,
|
|
1202
|
+
}).catch(() => { });
|
|
1110
1203
|
if (opts.json)
|
|
1111
1204
|
die(e);
|
|
1112
1205
|
dieTaskRoom(e);
|
|
1113
1206
|
}
|
|
1114
1207
|
});
|
|
1115
1208
|
cOpt(taskCmd.command('work <id>'))
|
|
1116
|
-
.description('
|
|
1209
|
+
.description('deprecated alias for task start')
|
|
1117
1210
|
.option('--template <name>', 'room template')
|
|
1211
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1212
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1213
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
1214
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
1215
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
1216
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
1217
|
+
.option('--role <id>', 'Role preset for current member')
|
|
1218
|
+
.option('--approval <mode>', 'approval for current member')
|
|
1219
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
1220
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
1221
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
1222
|
+
.option('--model <id>', 'model for current member')
|
|
1223
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
1224
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for current member')
|
|
1225
|
+
.option('--no-loops', 'disable loops for current member')
|
|
1118
1226
|
.option('--json', 'JSON output')
|
|
1119
|
-
.action(async (id, opts) => {
|
|
1227
|
+
.action(async (id, opts, command) => {
|
|
1120
1228
|
try {
|
|
1229
|
+
const previous = getTask(id).state;
|
|
1230
|
+
console.error('warning: `fleet task work` is deprecated; use `fleet task start`');
|
|
1121
1231
|
const result = await taskRoomService(opts.configuration).ensureTaskWork({
|
|
1122
1232
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, template: opts.template,
|
|
1233
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1234
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
1123
1235
|
});
|
|
1124
1236
|
const t = result.task;
|
|
1237
|
+
auditTask('work', t, previous);
|
|
1125
1238
|
if (result.status === 'already_active') {
|
|
1126
1239
|
if (opts.json) {
|
|
1127
1240
|
console.log(JSON.stringify({ schema_version: 1, task: t, status: 'already_active' }, null, 2));
|
|
@@ -1171,7 +1284,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1171
1284
|
if (plan.task.terminal_intent?.status === 'pending')
|
|
1172
1285
|
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
1173
1286
|
const settled = plan.settlementRequired
|
|
1174
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1287
|
+
? await launchTaskSettleWorker(id, `ours-fleet task finish ${id}`, opts.configuration)
|
|
1175
1288
|
: { task: plan.task, timedOut: false };
|
|
1176
1289
|
const t = settled.task;
|
|
1177
1290
|
auditTask('finish', t, previous);
|
|
@@ -1183,7 +1296,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1183
1296
|
console.log(renderMarkdownFailure({
|
|
1184
1297
|
kind: 'pending', subject: `task finish ${id}`,
|
|
1185
1298
|
detail: 'The finish request was accepted and is still being settled.',
|
|
1186
|
-
action: `
|
|
1299
|
+
action: `Re-run ours-fleet task finish ${id}.`,
|
|
1187
1300
|
}));
|
|
1188
1301
|
return;
|
|
1189
1302
|
}
|
|
@@ -1204,15 +1317,32 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1204
1317
|
.description('create a standalone room')
|
|
1205
1318
|
.requiredOption('--name <name>', 'room name')
|
|
1206
1319
|
.option('--template <name>', 'room template')
|
|
1320
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1321
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1207
1322
|
.option('--goal <text>', 'room goal')
|
|
1208
1323
|
.option('--brief <text>', 'room briefing')
|
|
1209
1324
|
.option('--brief-file <path>', 'room briefing from file')
|
|
1325
|
+
.option('--members-file <path>', 'typed YAML member overrides')
|
|
1326
|
+
.option('--member <slot>', 'begin a typed member override block')
|
|
1327
|
+
.option('--agent-template <id>', 'Agent Template for current member')
|
|
1328
|
+
.option('--brain <id>', 'Brain preset for current member')
|
|
1329
|
+
.option('--role <id>', 'Role preset for current member')
|
|
1330
|
+
.option('--approval <mode>', 'approval for current member')
|
|
1331
|
+
.option('--filesystem <mode>', 'filesystem for current member')
|
|
1332
|
+
.option('--unattended <mode>', 'unattended policy for current member')
|
|
1333
|
+
.option('--cwd <path>', 'working directory for current member')
|
|
1334
|
+
.option('--model <id>', 'model for current member')
|
|
1335
|
+
.option('--effort <level>', 'reasoning effort for current member')
|
|
1336
|
+
.option('--loops-file <path>', 'owner-only loops: YAML for current member')
|
|
1337
|
+
.option('--no-loops', 'disable loops for current member')
|
|
1210
1338
|
.option('--json', 'JSON output')
|
|
1211
|
-
.action(async (opts) => {
|
|
1339
|
+
.action(async (opts, command) => {
|
|
1212
1340
|
try {
|
|
1213
1341
|
const record = await taskRoomService(opts.configuration).createRoom({
|
|
1214
1342
|
actor: { kind: 'local_control', surface: 'cli' }, name: opts.name,
|
|
1215
1343
|
template: opts.template, goal: opts.goal, brief: opts.brief, briefFile: opts.briefFile,
|
|
1344
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1345
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
1216
1346
|
});
|
|
1217
1347
|
if (opts.json) {
|
|
1218
1348
|
console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
|
|
@@ -1281,7 +1411,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1281
1411
|
],
|
|
1282
1412
|
sections: [
|
|
1283
1413
|
...(cowork.seats.length ? [{
|
|
1284
|
-
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1414
|
+
heading: 'Members', markdownItems: cowork.seats.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1415
|
+
+ seatConfigurationSuffix(r?.member_seats
|
|
1416
|
+
.find(seat => seat.identity_cid === s.identity_cid)?.launch?.presentation)),
|
|
1285
1417
|
}] : []),
|
|
1286
1418
|
...(r ? roomStartupSections(r) : []),
|
|
1287
1419
|
],
|
|
@@ -1343,7 +1475,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1343
1475
|
],
|
|
1344
1476
|
sections: [{
|
|
1345
1477
|
heading: 'Members',
|
|
1346
|
-
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1478
|
+
...(members.length ? { markdownItems: members.map(s => `${markdownCode(s.identity_cid)} — ${markdownProse(s.role)} — ${markdownProse(s.seat_state)}`
|
|
1479
|
+
+ seatConfigurationSuffix(r?.member_seats
|
|
1480
|
+
.find(seat => seat.identity_cid === s.identity_cid)?.launch?.presentation)) }
|
|
1347
1481
|
: { items: ['No members found.'] }),
|
|
1348
1482
|
}],
|
|
1349
1483
|
}));
|
|
@@ -1363,8 +1497,6 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1363
1497
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1364
1498
|
});
|
|
1365
1499
|
const closing = getRoomRecord(id);
|
|
1366
|
-
if (previous && closing && previous.state !== closing.state)
|
|
1367
|
-
auditRoom('close', closing, previous.state, closing.state);
|
|
1368
1500
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1369
1501
|
if (closing)
|
|
1370
1502
|
auditRoom('delete', closing, closing.state, settled.deleted ? 'deleted' : 'closing');
|
|
@@ -1376,7 +1508,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1376
1508
|
console.log(renderMarkdownFailure({
|
|
1377
1509
|
kind: 'pending', subject: `room delete ${id} ${id}`,
|
|
1378
1510
|
detail: 'The deletion request was accepted and is still being settled.',
|
|
1379
|
-
action: `Run ours-fleet room delete ${id} ${id}
|
|
1511
|
+
action: `Run ours-fleet room delete ${id} ${id} again.`,
|
|
1380
1512
|
}));
|
|
1381
1513
|
return;
|
|
1382
1514
|
}
|
|
@@ -1399,62 +1531,6 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1399
1531
|
.description('deprecated alias for room delete (requires ID twice for confirmation)')
|
|
1400
1532
|
.option('--json', 'JSON output')
|
|
1401
1533
|
.action(deleteAction);
|
|
1402
|
-
cOpt(roomCmd.command('recover <id>'))
|
|
1403
|
-
.description('attempt to recover a stuck room')
|
|
1404
|
-
.option('--json', 'JSON output')
|
|
1405
|
-
.action(async (id, opts) => {
|
|
1406
|
-
try {
|
|
1407
|
-
const previous = getRoomRecord(id);
|
|
1408
|
-
const recovered = await taskRoomService(opts.configuration).recoverRoom({
|
|
1409
|
-
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1410
|
-
});
|
|
1411
|
-
if (recovered.kind === 'deletion_worker_required') {
|
|
1412
|
-
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1413
|
-
if (previous)
|
|
1414
|
-
auditRoom('recover', previous, previous.state, settled.deleted ? 'deleted' : 'closing');
|
|
1415
|
-
if (opts.json) {
|
|
1416
|
-
console.log(JSON.stringify({
|
|
1417
|
-
schema_version: 1, room: null, orchestration: null,
|
|
1418
|
-
recovery_actions: [settled.timedOut
|
|
1419
|
-
? `Deletion remains pending — retry room delete ${id} ${id}`
|
|
1420
|
-
: 'Deletion completed successfully'],
|
|
1421
|
-
}, null, 2));
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
console.log(renderMarkdownResult({
|
|
1425
|
-
icon: settled.timedOut ? '⏳' : '🗑️',
|
|
1426
|
-
title: settled.timedOut ? 'Room deletion pending' : 'Room deleted',
|
|
1427
|
-
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1428
|
-
sections: [{ heading: 'Next step', items: [settled.timedOut
|
|
1429
|
-
? `Run ours-fleet room delete ${id} ${id} or room recover ${id} again.`
|
|
1430
|
-
: 'No recovery action is needed.'] }],
|
|
1431
|
-
}));
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
const { room: cowork, orchestration: r, issues: actions } = recovered;
|
|
1435
|
-
if (r)
|
|
1436
|
-
auditRoom('recover', r, previous?.state ?? 'unresolved', r.state);
|
|
1437
|
-
if (opts.json) {
|
|
1438
|
-
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null, recovery_actions: actions }, null, 2));
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
console.log(renderMarkdownResult({
|
|
1442
|
-
icon: '🛟', title: 'Room recovery',
|
|
1443
|
-
fields: [
|
|
1444
|
-
{ label: 'Room', value: cowork.room_id, kind: 'code' },
|
|
1445
|
-
{ label: 'Status', value: roomStatus(cowork.state), kind: 'markdown' },
|
|
1446
|
-
...(r ? [{ label: 'Saga', value: r.saga.phase, kind: 'code' }] : []),
|
|
1447
|
-
],
|
|
1448
|
-
sections: actions.length ? [{ heading: 'Next steps', items: actions }]
|
|
1449
|
-
: [{ heading: 'Result', items: ['No recovery action is needed.'] }],
|
|
1450
|
-
}));
|
|
1451
|
-
}
|
|
1452
|
-
catch (e) {
|
|
1453
|
-
if (opts.json)
|
|
1454
|
-
die(e);
|
|
1455
|
-
dieTaskRoom(e);
|
|
1456
|
-
}
|
|
1457
|
-
});
|
|
1458
1534
|
const internalDeleteAction = async (id, opts) => {
|
|
1459
1535
|
try {
|
|
1460
1536
|
const app = taskRoomService(opts.configuration);
|