@ours.network/fleet 1.1.0-nightly.3 → 1.1.0-nightly.30
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 +244 -40
- 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 +4 -4
- package/dist/application/role-creation-service.js +19 -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 +93 -40
- package/dist/application/task-room-service.js +579 -173
- package/dist/briefing.js +85 -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 +217 -51
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +38 -3
- package/dist/config.js +240 -28
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- 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 +213 -42
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +181 -0
- package/dist/fleet-command-audit.js +738 -0
- package/dist/fleet-proxy.d.ts +7 -2
- 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 +4 -3
- package/dist/index.js +3 -2
- 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 +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 +75 -3
- package/dist/owner-channel/channel.js +531 -123
- package/dist/owner-channel/commands.d.ts +28 -6
- package/dist/owner-channel/commands.js +112 -36
- 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/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 +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 +519 -261
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +23 -7
- package/dist/rooms-tasks/config.js +16 -29
- 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 +9 -0
- package/dist/rooms-tasks/external-worker.js +52 -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/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +9 -2
- package/dist/rooms-tasks/provision.js +146 -56
- 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 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +10 -4
- package/dist/rooms-tasks/types.d.ts +94 -8
- package/dist/rooms-tasks/types.js +14 -1
- package/dist/runner.d.ts +12 -2
- package/dist/runner.js +167 -56
- 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 +51 -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 +9 -0
- package/dist/spawn.js +77 -9
- package/dist/temp-lifecycle.d.ts +1 -1
- 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-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
- package/dist/web-app/index.html +1 -1
- package/package.json +5 -4
- package/presets/brain-catalog.json +17 -0
- package/presets/fleet/agent_templates/Critic.yaml +5 -0
- package/presets/fleet/agent_templates/Developer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -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/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 +30 -0
- package/presets/fleet/roles/Critic.yaml +28 -0
- package/presets/fleet/roles/Developer.yaml +28 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
- package/presets/fleet/room_templates/pair.yaml +13 -0
- package/presets/fleet/room_templates/single.yaml +9 -0
- package/presets/fleet/room_templates/team.yaml +16 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
2
4
|
import { loadConfig, ConfigError } from '../config.js';
|
|
3
5
|
import { provisionMembers, getBinPath } from './provision.js';
|
|
4
|
-
import { launchFleetWorker } from './external-worker.js';
|
|
6
|
+
import { launchFleetWorker, presentFleetWorkerLifecycle } from './external-worker.js';
|
|
5
7
|
import { resolveTemplate, snapshotTemplate, } from './templates.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
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';
|
|
9
45
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
10
|
-
import { TaskRoomApplicationError, TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
46
|
+
import { recordTaskProvisioningOutcome, TaskRoomApplicationError, TaskRoomApplicationService, } from '../application/task-room-service.js';
|
|
11
47
|
import { TaskListError } from './task-lists.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';
|
|
12
52
|
class TaskRoomPublicError extends Error {
|
|
13
53
|
code;
|
|
14
54
|
fields;
|
|
@@ -27,6 +67,11 @@ function taskRoomPublicError(code, fields = {}) {
|
|
|
27
67
|
}));
|
|
28
68
|
return new TaskRoomPublicError(code, validated);
|
|
29
69
|
}
|
|
70
|
+
async function presentDetachedProvisioningOutcome(outcome) {
|
|
71
|
+
beginFleetAuditCollection();
|
|
72
|
+
recordTaskProvisioningOutcome(outcome);
|
|
73
|
+
await presentFleetWorkerLifecycle(consumeFleetAuditCollection().presentations ?? []);
|
|
74
|
+
}
|
|
30
75
|
function taskRoomPublicFailure(error) {
|
|
31
76
|
const f = error.fields;
|
|
32
77
|
switch (error.code) {
|
|
@@ -41,7 +86,7 @@ function taskRoomPublicFailure(error) {
|
|
|
41
86
|
case 'template_not_found': return {
|
|
42
87
|
legacy: `template not found: ${f.template ?? 'requested-template'}`, kind: 'not_found',
|
|
43
88
|
detail: `The requested template ${f.template ?? ''}`.trim() + ' was not found.',
|
|
44
|
-
action: 'Run ours-fleet
|
|
89
|
+
action: 'Run interactive ours-fleet init to review and create a complete default setup, then run ours-fleet template list.',
|
|
45
90
|
};
|
|
46
91
|
case 'template_mismatch': return {
|
|
47
92
|
legacy: `template ${f.requested ?? 'requested-template'} does not match room ${f.room ?? 'requested-room'}'s provisioned template ${f.provisioned ?? 'recorded-template'}`,
|
|
@@ -51,7 +96,7 @@ function taskRoomPublicFailure(error) {
|
|
|
51
96
|
case 'task_template_drift': return {
|
|
52
97
|
legacy: `task template snapshot no longer matches ${f.template ?? 'the recorded template'}`,
|
|
53
98
|
kind: 'state', detail: 'The task template snapshot no longer matches the recorded template.',
|
|
54
|
-
action: '
|
|
99
|
+
action: 'Inspect the matching Task and Room, then retry the originating start command.',
|
|
55
100
|
};
|
|
56
101
|
case 'task_terminal': return {
|
|
57
102
|
legacy: `task ${f.task ?? 'requested-task'} is in terminal state '${f.state ?? 'terminal'}'`,
|
|
@@ -64,9 +109,9 @@ function taskRoomPublicFailure(error) {
|
|
|
64
109
|
action: 'Run ours-fleet task show to inspect the completed task.',
|
|
65
110
|
};
|
|
66
111
|
case 'task_non_resumable': return {
|
|
67
|
-
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`,
|
|
68
113
|
kind: 'state', detail: 'The task’s room is in a non-resumable state.',
|
|
69
|
-
action: `
|
|
114
|
+
action: `Inspect the Task and Room, then retry ours-fleet task start ${f.task ?? '<id>'}.`,
|
|
70
115
|
};
|
|
71
116
|
case 'room_filter': return {
|
|
72
117
|
legacy: 'room state filter must be active, provisioning, or all', kind: 'usage',
|
|
@@ -89,8 +134,29 @@ function die(e) {
|
|
|
89
134
|
const msg = e instanceof TaskRoomPublicError
|
|
90
135
|
? taskRoomPublicFailure(e).legacy : e instanceof Error ? e.message : String(e);
|
|
91
136
|
process.stderr.write(`error: ${msg}\n`);
|
|
137
|
+
recordFleetAuditFailure(classifyTaskRoomFailure(e));
|
|
92
138
|
process.exit(1);
|
|
93
139
|
}
|
|
140
|
+
function classifyTaskRoomFailure(e) {
|
|
141
|
+
if (e instanceof TaskRoomApplicationError)
|
|
142
|
+
e = taskRoomPublicError(e.code, e.fields);
|
|
143
|
+
if (e instanceof TaskRoomPublicError || e instanceof TaskListError || e instanceof ConfigError)
|
|
144
|
+
return { class: 'validation', effect: 'not_started' };
|
|
145
|
+
if (e instanceof TaskStateError) {
|
|
146
|
+
const known = /^task not found: [A-Za-z0-9_-]{1,128}$/u.test(e.message)
|
|
147
|
+
|| e.message === 'invalid task ID: expected the canonical 17-character lowercase ID'
|
|
148
|
+
|| isKnownTaskStateMessage(e.message);
|
|
149
|
+
return known ? { class: 'validation', effect: 'not_started' }
|
|
150
|
+
: { class: 'runtime', effect: 'unknown' };
|
|
151
|
+
}
|
|
152
|
+
if (e instanceof RoomStateError) {
|
|
153
|
+
const known = /^room not found: [A-Za-z0-9_-]{1,128}$/u.test(e.message)
|
|
154
|
+
|| isKnownRoomStateMessage(e.message);
|
|
155
|
+
return known ? { class: 'validation', effect: 'not_started' }
|
|
156
|
+
: { class: 'runtime', effect: 'unknown' };
|
|
157
|
+
}
|
|
158
|
+
return { class: 'runtime', effect: 'unknown' };
|
|
159
|
+
}
|
|
94
160
|
const TASK_STATE_WORD = '(?:backlog|provisioning|active|review|done|cancelled|failed)';
|
|
95
161
|
const SAFE_ID_WORD = '[A-Za-z0-9_-]{1,128}';
|
|
96
162
|
function isKnownTaskStateMessage(message) {
|
|
@@ -104,7 +170,7 @@ function isKnownTaskStateMessage(message) {
|
|
|
104
170
|
new RegExp(`^task ${SAFE_ID_WORD} is not tied to room ${SAFE_ID_WORD}$`, 'u'),
|
|
105
171
|
new RegExp(`^task ${SAFE_ID_WORD} has no terminal intent$`, 'u'),
|
|
106
172
|
new RegExp(`^task ${SAFE_ID_WORD} reached terminal state '${TASK_STATE_WORD}' outside its intent$`, 'u'),
|
|
107
|
-
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'),
|
|
108
174
|
].some(pattern => pattern.test(message));
|
|
109
175
|
}
|
|
110
176
|
function isKnownRoomStateMessage(message) {
|
|
@@ -127,6 +193,7 @@ function dieTaskRoom(e) {
|
|
|
127
193
|
action: failure.action,
|
|
128
194
|
});
|
|
129
195
|
process.stderr.write(`${output}\n`);
|
|
196
|
+
recordFleetAuditFailure({ class: 'validation', effect: 'not_started' });
|
|
130
197
|
process.exit(1);
|
|
131
198
|
}
|
|
132
199
|
if (e instanceof TaskListError) {
|
|
@@ -142,6 +209,7 @@ function dieTaskRoom(e) {
|
|
|
142
209
|
: 'Use a valid NFC-normalized name of at most 64 Unicode code points.',
|
|
143
210
|
});
|
|
144
211
|
process.stderr.write(`${output}\n`);
|
|
212
|
+
recordFleetAuditFailure({ class: 'validation', effect: 'not_started' });
|
|
145
213
|
process.exit(1);
|
|
146
214
|
}
|
|
147
215
|
const taskMissing = e instanceof TaskStateError
|
|
@@ -167,6 +235,7 @@ function dieTaskRoom(e) {
|
|
|
167
235
|
: 'Retry once; if it repeats, run ours-fleet doctor and inspect the role logs.',
|
|
168
236
|
});
|
|
169
237
|
process.stderr.write(`${output}\n`);
|
|
238
|
+
recordFleetAuditFailure(classifyTaskRoomFailure(e));
|
|
170
239
|
process.exit(1);
|
|
171
240
|
}
|
|
172
241
|
const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
@@ -175,30 +244,153 @@ const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
|
175
244
|
+ ` — List ${markdownCode(t.list_name ?? 'default')}`
|
|
176
245
|
+ (t.blocked ? ` — 🚧 Blocked: ${markdownProse(t.blocked.reason)}` : '')),
|
|
177
246
|
});
|
|
178
|
-
const taskActionMarkdown = (title, task, fields = []) =>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
247
|
+
const taskActionMarkdown = (title, task, fields = []) => {
|
|
248
|
+
recordFleetAuditResource('task', task.task_id);
|
|
249
|
+
if (task.room_id)
|
|
250
|
+
recordFleetAuditResource('room', task.room_id);
|
|
251
|
+
return renderMarkdownResult({
|
|
252
|
+
icon: '📋', title,
|
|
253
|
+
fields: [
|
|
254
|
+
{ label: 'ID', value: task.task_id, kind: 'code' },
|
|
255
|
+
{ label: 'Status', value: taskStatus(task.state), kind: 'markdown' },
|
|
256
|
+
{ label: 'List', value: task.list_name ?? 'default', kind: 'code' },
|
|
257
|
+
...fields,
|
|
258
|
+
],
|
|
259
|
+
});
|
|
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
|
+
}
|
|
297
|
+
function safeSelectionSummary(definition, kind) {
|
|
298
|
+
const selected = definition?.[kind];
|
|
299
|
+
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
300
|
+
return 'unresolved';
|
|
301
|
+
const selection = selected;
|
|
302
|
+
if (selection.kind === 'ref' && typeof selection.id === 'string')
|
|
303
|
+
return `ref:${selection.id} (reference)`;
|
|
304
|
+
if (selection.kind === 'inline' && typeof selection.fingerprint === 'string')
|
|
305
|
+
return `inline:${selection.fingerprint} (inline)`;
|
|
306
|
+
if (typeof selection.ref === 'string')
|
|
307
|
+
return `ref:${selection.ref}`;
|
|
308
|
+
if (!Object.hasOwn(selection, 'inline'))
|
|
309
|
+
return 'unresolved';
|
|
310
|
+
const canonical = (value) => Array.isArray(value)
|
|
311
|
+
? `[${value.map(canonical).join(',')}]`
|
|
312
|
+
: value && typeof value === 'object'
|
|
313
|
+
? `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b))
|
|
314
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(',')}}`
|
|
315
|
+
: JSON.stringify(value);
|
|
316
|
+
const fingerprint = createHash('sha256').update(canonical(selection.inline)).digest('hex').slice(0, 16);
|
|
317
|
+
return `inline:sha256:${fingerprint}`;
|
|
318
|
+
}
|
|
319
|
+
function safePermissionsSummary(definition) {
|
|
320
|
+
const permissions = definition?.permissions;
|
|
321
|
+
if (!permissions || typeof permissions !== 'object' || Array.isArray(permissions))
|
|
322
|
+
return undefined;
|
|
323
|
+
const policy = permissions;
|
|
324
|
+
const fields = ['approval', 'filesystem', 'unattended'].flatMap(key => typeof policy[key] === 'string' ? [`${key}=${policy[key]}`] : []);
|
|
325
|
+
return fields.length ? fields.join(',') : undefined;
|
|
326
|
+
}
|
|
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;
|
|
332
|
+
const materialSameState = operation === 'block' || operation === 'unblock' || operation === 'settling';
|
|
333
|
+
if (previousState === newState && !materialSameState)
|
|
334
|
+
return;
|
|
335
|
+
recordFleetAuditResource('task', task.task_id);
|
|
336
|
+
if (task.room_id)
|
|
337
|
+
recordFleetAuditResource('room', task.room_id);
|
|
338
|
+
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
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]) ?? []);
|
|
341
|
+
const semanticOperation = operation === 'recover'
|
|
342
|
+
? newState === 'active' ? 'work' : newState === 'done' ? 'done'
|
|
343
|
+
: newState === 'cancelled' ? 'cancel' : undefined
|
|
344
|
+
: operation;
|
|
345
|
+
if (!semanticOperation)
|
|
346
|
+
return;
|
|
347
|
+
recordFleetAuditPresentation({ kind: 'task', operation: semanticOperation, id: task.task_id, title: task.title,
|
|
348
|
+
previousState, newState,
|
|
349
|
+
revision: revision ?? task.blocked?.at ?? task.ended_at ?? task.started_at ?? task.created_at,
|
|
350
|
+
list: task.list_name ?? 'default',
|
|
351
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
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,
|
|
355
|
+
brain: safeSelectionSummary(definitions.get(member.name), 'brain'),
|
|
356
|
+
role: safeSelectionSummary(definitions.get(member.name), 'role') === 'unresolved'
|
|
357
|
+
? member.cowork_role : safeSelectionSummary(definitions.get(member.name), 'role'),
|
|
358
|
+
permissions: safePermissionsSummary(definitions.get(member.name)),
|
|
359
|
+
configuration: presentations.get(member.name) })) });
|
|
360
|
+
}
|
|
361
|
+
function auditRoom(operation, room, previousState, newState = room.state) {
|
|
362
|
+
if (previousState === newState)
|
|
363
|
+
return;
|
|
364
|
+
recordFleetAuditResource('room', room.room_id);
|
|
365
|
+
recordFleetAuditPresentation({ kind: 'room', operation, id: room.room_id, previousState, newState,
|
|
366
|
+
revision: room.closed_at ?? room.activated_at ?? room.close?.accepted_at ?? room.created_at,
|
|
367
|
+
name: room.room_name, taskId: room.task_id,
|
|
368
|
+
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
369
|
+
participants: [...room.member_seats].sort((a, b) => a.role_name.localeCompare(b.role_name)).map(member => ({ name: member.role_name, id: member.identity_cid,
|
|
370
|
+
brain: safeSelectionSummary(member.launch?.agent_definition, 'brain'),
|
|
371
|
+
role: safeSelectionSummary(member.launch?.agent_definition, 'role') === 'unresolved'
|
|
372
|
+
? member.cowork_role : safeSelectionSummary(member.launch?.agent_definition, 'role'),
|
|
373
|
+
permissions: safePermissionsSummary(member.launch?.agent_definition),
|
|
374
|
+
configuration: member.launch?.presentation })) });
|
|
375
|
+
}
|
|
376
|
+
const roomActionMarkdown = (title, room, fields = []) => {
|
|
377
|
+
recordFleetAuditResource('room', room.room_id);
|
|
378
|
+
return renderMarkdownResult({
|
|
379
|
+
icon: '🏠', title,
|
|
380
|
+
fields: [
|
|
381
|
+
{ label: 'ID', value: room.room_id, kind: 'code' },
|
|
382
|
+
{ label: 'Status', value: roomStatus(room.state), kind: 'markdown' },
|
|
383
|
+
...fields,
|
|
384
|
+
],
|
|
385
|
+
});
|
|
386
|
+
};
|
|
195
387
|
const PUBLIC_SETTLE_WAIT_MS = 60_000;
|
|
196
388
|
const PUBLIC_SETTLE_POLL_MS = 100;
|
|
197
389
|
const sleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms); });
|
|
198
390
|
function errorText(error) {
|
|
199
391
|
return error instanceof Error ? error.message : String(error);
|
|
200
392
|
}
|
|
201
|
-
async function launchTaskSettleWorker(taskId, configPath) {
|
|
393
|
+
async function launchTaskSettleWorker(taskId, retryCommand, configPath) {
|
|
202
394
|
const previousErrorAt = getTask(taskId).terminal_intent?.error_at;
|
|
203
395
|
try {
|
|
204
396
|
await launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, configPath);
|
|
@@ -206,8 +398,12 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
206
398
|
catch (error) {
|
|
207
399
|
await taskRoomService(configPath).recordSettlementError({
|
|
208
400
|
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
209
|
-
recoveryHint: `External settle worker failed to start. Retry
|
|
401
|
+
recoveryHint: `External settle worker failed to start. Retry ${retryCommand}.`,
|
|
210
402
|
});
|
|
403
|
+
const task = getTask(taskId);
|
|
404
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
405
|
+
label: task.title, state: task.state, category: 'settlement_failed',
|
|
406
|
+
eventId: task.terminal_intent?.error_at ?? task.terminal_intent?.accepted_at ?? task.created_at });
|
|
211
407
|
throw error;
|
|
212
408
|
}
|
|
213
409
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -220,7 +416,56 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
220
416
|
}
|
|
221
417
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
222
418
|
}
|
|
223
|
-
|
|
419
|
+
const task = getTask(taskId);
|
|
420
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
|
|
421
|
+
label: task.title, state: task.state, category: 'settlement_pending',
|
|
422
|
+
eventId: task.terminal_intent?.accepted_at ?? task.created_at });
|
|
423
|
+
return { task, timedOut: true };
|
|
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 };
|
|
224
469
|
}
|
|
225
470
|
async function launchRoomDeleteWorker(roomId, configPath) {
|
|
226
471
|
const previousErrorAt = getRoomRecord(roomId)?.close?.error_at;
|
|
@@ -232,6 +477,11 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
232
477
|
actor: { kind: 'local_control', surface: 'cli' }, roomId, error: errorText(error),
|
|
233
478
|
recoveryHint: `External delete worker failed to start. Retry room delete ${roomId} ${roomId}.`,
|
|
234
479
|
});
|
|
480
|
+
const room = getRoomRecord(roomId);
|
|
481
|
+
if (room)
|
|
482
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
483
|
+
label: room.room_name, state: room.state, category: 'cleanup_failed',
|
|
484
|
+
eventId: room.close?.error_at ?? room.close?.accepted_at ?? room.created_at });
|
|
235
485
|
throw error;
|
|
236
486
|
}
|
|
237
487
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -244,7 +494,12 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
244
494
|
}
|
|
245
495
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
246
496
|
}
|
|
247
|
-
|
|
497
|
+
const remaining = getRoomRecord(roomId);
|
|
498
|
+
if (remaining)
|
|
499
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
|
|
500
|
+
label: remaining.room_name, state: remaining.state, category: 'cleanup_pending',
|
|
501
|
+
eventId: remaining.close?.accepted_at ?? remaining.created_at });
|
|
502
|
+
return { deleted: remaining === undefined, timedOut: true };
|
|
248
503
|
}
|
|
249
504
|
function loadCfg(opts) {
|
|
250
505
|
return loadConfig(opts.configuration);
|
|
@@ -271,7 +526,7 @@ function resolveRoomTemplate(cfg, name) {
|
|
|
271
526
|
const template = resolveTemplate(name, allTemplates(cfg));
|
|
272
527
|
if (!template)
|
|
273
528
|
throw new Error(`template not found: ${name}`);
|
|
274
|
-
return snapshotTemplate(template);
|
|
529
|
+
return snapshotTemplate(template, cfg.agentTemplates);
|
|
275
530
|
}
|
|
276
531
|
function durableTaskRoomTemplate(task, room) {
|
|
277
532
|
const snapshot = room.template_snapshot;
|
|
@@ -306,69 +561,6 @@ function roomStartupSections(room) {
|
|
|
306
561
|
}
|
|
307
562
|
return sections;
|
|
308
563
|
}
|
|
309
|
-
async function provisionRoom(cfg, input) {
|
|
310
|
-
const rooms = cfg.rooms;
|
|
311
|
-
if (!rooms)
|
|
312
|
-
throw new ConfigError('rooms: configuration is required');
|
|
313
|
-
const attachOwner = rooms.defaults?.attach_owner !== false;
|
|
314
|
-
if (attachOwner && !cfg.ownerInvite)
|
|
315
|
-
throw new ConfigError('rooms.owner: public_invite or public_invite_file is required when attach_owner is enabled');
|
|
316
|
-
const cowork = coworkFor(cfg);
|
|
317
|
-
const goal = input.goal?.trim() || input.name;
|
|
318
|
-
const briefing = input.brief?.trim() || input.template?.contract?.trim() || goal;
|
|
319
|
-
const created = await cowork.createRoom({
|
|
320
|
-
room_name: input.name,
|
|
321
|
-
goal,
|
|
322
|
-
briefing,
|
|
323
|
-
quiet_membership: input.template?.room?.quiet_membership,
|
|
324
|
-
anonymous: input.template?.room?.anonymous,
|
|
325
|
-
});
|
|
326
|
-
let record = createRoomRecord({
|
|
327
|
-
room_id: created.room_id,
|
|
328
|
-
room_name: input.name,
|
|
329
|
-
room_identity_cid: created.identity_cid,
|
|
330
|
-
task_id: input.taskId,
|
|
331
|
-
template_snapshot: input.template,
|
|
332
|
-
});
|
|
333
|
-
input.onCreated?.(record);
|
|
334
|
-
record = advanceSaga(record.room_id, 'create_room', 1);
|
|
335
|
-
if (attachOwner) {
|
|
336
|
-
try {
|
|
337
|
-
record = advanceSaga(record.room_id, 'attach_owner', 2);
|
|
338
|
-
const accepted = await cowork.acceptInvite(record.room_id, cfg.ownerInvite, {
|
|
339
|
-
role: rooms.owner.role,
|
|
340
|
-
expected_cid: rooms.owner.expected_cid,
|
|
341
|
-
});
|
|
342
|
-
record = setOwnerSeat(record.room_id, accepted.seat_cid, cfg.ownerInviteFingerprint ?? '');
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
const mismatch = error instanceof CoworkProtocolError && /CID|expected/i.test(error.message);
|
|
346
|
-
setSagaError(record.room_id, error instanceof Error ? error.message : String(error), mismatch
|
|
347
|
-
? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
|
|
348
|
-
: 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
|
|
349
|
-
throw error;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
record = advanceSaga(record.room_id, 'create_members', 3);
|
|
353
|
-
if (input.template && input.template.members.length > 0) {
|
|
354
|
-
record = await provisionMembers({
|
|
355
|
-
cfg,
|
|
356
|
-
cowork,
|
|
357
|
-
roomId: record.room_id,
|
|
358
|
-
taskId: input.taskId,
|
|
359
|
-
template: input.template,
|
|
360
|
-
binPath: getBinPath(),
|
|
361
|
-
brief: input.brief,
|
|
362
|
-
goal: input.goal,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
record = activateRoom(record.room_id);
|
|
367
|
-
if (input.taskId)
|
|
368
|
-
activateTask(input.taskId);
|
|
369
|
-
}
|
|
370
|
-
return record;
|
|
371
|
-
}
|
|
372
564
|
export function registerTemplateCommands(parent, cOpt) {
|
|
373
565
|
const templateCmd = parent.command('template').description('room template operations');
|
|
374
566
|
cOpt(templateCmd.command('list'))
|
|
@@ -382,12 +574,11 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
382
574
|
return;
|
|
383
575
|
}
|
|
384
576
|
if (!templates.length) {
|
|
385
|
-
console.log('No templates configured.');
|
|
577
|
+
console.log('No templates configured. Run interactive ours-fleet init to review and create a complete default setup.');
|
|
386
578
|
return;
|
|
387
579
|
}
|
|
388
580
|
for (const t of templates) {
|
|
389
|
-
|
|
390
|
-
console.log(`${t.name}@${t.version}${tag} ${t.description}`);
|
|
581
|
+
console.log(`${t.name}@${t.version} ${t.description} [file: ${t.sourceFile ?? 'manifest'}]`);
|
|
391
582
|
}
|
|
392
583
|
}
|
|
393
584
|
catch (e) {
|
|
@@ -405,12 +596,13 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
405
596
|
return;
|
|
406
597
|
}
|
|
407
598
|
console.log(`${t.name}@${t.version} ${t.description}`);
|
|
599
|
+
console.log(`Source: ${t.sourceFile ?? 'manifest'}`);
|
|
408
600
|
if (t.contract)
|
|
409
601
|
console.log(`\nContract:\n${t.contract}`);
|
|
410
602
|
console.log(`\nMembers:`);
|
|
411
603
|
for (const m of t.members)
|
|
412
604
|
console.log(` ${m.slot}: ${m.count}× ${m.role} (`
|
|
413
|
-
+
|
|
605
|
+
+ `agent template: ${m.agent_template})`);
|
|
414
606
|
console.log(`\nContent hash: ${t.content_hash}`);
|
|
415
607
|
}
|
|
416
608
|
catch (e) {
|
|
@@ -453,20 +645,50 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
453
645
|
.option('--brief-file <path>', 'task brief from file')
|
|
454
646
|
.option('--backlog', 'create in backlog (do not start immediately)')
|
|
455
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')
|
|
456
650
|
.option('--idempotency-key <key>', 'idempotency key')
|
|
457
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')
|
|
458
665
|
.option('--json', 'JSON output')
|
|
459
|
-
.action(async (opts) => {
|
|
666
|
+
.action(async (opts, command) => {
|
|
460
667
|
try {
|
|
461
|
-
const
|
|
668
|
+
const members = cliMemberOverrides(opts.membersFile, commandArgv(command));
|
|
669
|
+
const service = taskRoomService(opts.configuration);
|
|
670
|
+
const record = await service.createTask({
|
|
462
671
|
actor: { kind: 'local_control', surface: 'cli' }, title: opts.title,
|
|
463
672
|
brief: opts.brief, briefFile: opts.briefFile, template: opts.template,
|
|
464
673
|
backlog: opts.backlog, noRoom: opts.room === false,
|
|
674
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
465
675
|
idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
|
|
466
676
|
list: opts.list,
|
|
677
|
+
members,
|
|
467
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);
|
|
468
685
|
if (opts.json) {
|
|
469
|
-
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));
|
|
470
692
|
return;
|
|
471
693
|
}
|
|
472
694
|
console.log(taskActionMarkdown('Task created', record, [
|
|
@@ -488,7 +710,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
488
710
|
.option('--list <name>', 'filter by task list')
|
|
489
711
|
.option('--group-by-list', 'group deterministic results by list (JSON)')
|
|
490
712
|
.option('--json', 'JSON output')
|
|
491
|
-
.action((opts) => {
|
|
713
|
+
.action(async (opts) => {
|
|
492
714
|
try {
|
|
493
715
|
let stateFilter;
|
|
494
716
|
if (opts.state && opts.state !== 'all') {
|
|
@@ -514,9 +736,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
514
736
|
taskCmd.command('lists')
|
|
515
737
|
.description('list named task lists')
|
|
516
738
|
.option('--json', 'JSON output')
|
|
517
|
-
.action((opts) => {
|
|
739
|
+
.action(async (opts) => {
|
|
518
740
|
try {
|
|
519
|
-
const
|
|
741
|
+
const service = taskRoomService(opts.configuration);
|
|
742
|
+
const lists = service.listTaskLists();
|
|
520
743
|
if (opts.json) {
|
|
521
744
|
console.log(JSON.stringify({ schema_version: 1, lists }, null, 2));
|
|
522
745
|
return;
|
|
@@ -607,9 +830,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
607
830
|
taskCmd.command('show <id>')
|
|
608
831
|
.description('show task details')
|
|
609
832
|
.option('--json', 'JSON output')
|
|
610
|
-
.action((id, opts) => {
|
|
833
|
+
.action(async (id, opts) => {
|
|
611
834
|
try {
|
|
612
|
-
const
|
|
835
|
+
const service = taskRoomService();
|
|
836
|
+
const { task: t, orchestration: room } = service.getTask(id);
|
|
613
837
|
if (opts.json) {
|
|
614
838
|
console.log(JSON.stringify({
|
|
615
839
|
schema_version: 1, task: t, orchestration: room ?? null,
|
|
@@ -634,7 +858,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
634
858
|
],
|
|
635
859
|
sections: [
|
|
636
860
|
...(t.member_roles.length ? [{
|
|
637
|
-
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)),
|
|
638
864
|
}] : []),
|
|
639
865
|
...(room ? roomStartupSections(room) : []),
|
|
640
866
|
],
|
|
@@ -647,18 +873,41 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
647
873
|
}
|
|
648
874
|
});
|
|
649
875
|
cOpt(taskCmd.command('start <id>'))
|
|
650
|
-
.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')
|
|
651
893
|
.option('--json', 'JSON output')
|
|
652
|
-
.action(async (id, opts) => {
|
|
894
|
+
.action(async (id, opts, command) => {
|
|
653
895
|
try {
|
|
654
|
-
const
|
|
896
|
+
const service = taskRoomService(opts.configuration);
|
|
897
|
+
const t = await service.startTask({
|
|
655
898
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
899
|
+
template: opts.template, members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
900
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
656
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);
|
|
657
906
|
if (opts.json) {
|
|
658
|
-
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
907
|
+
console.log(JSON.stringify({ schema_version: 1, task: t, provisioning }, null, 2));
|
|
659
908
|
return;
|
|
660
909
|
}
|
|
661
|
-
console.log(
|
|
910
|
+
console.log(provisioningMarkdown(provisioning));
|
|
662
911
|
}
|
|
663
912
|
catch (e) {
|
|
664
913
|
if (e instanceof TaskRoomApplicationError && e.code === 'task_template_drift')
|
|
@@ -677,9 +926,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
677
926
|
.option('--json', 'JSON output')
|
|
678
927
|
.action((id, opts) => {
|
|
679
928
|
try {
|
|
929
|
+
const previous = getTask(id).state;
|
|
680
930
|
const t = taskRoomService().blockTask({
|
|
681
931
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, reason: opts.reason,
|
|
682
932
|
});
|
|
933
|
+
auditTask('block', t, previous);
|
|
683
934
|
if (opts.json) {
|
|
684
935
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
685
936
|
return;
|
|
@@ -697,9 +948,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
697
948
|
.option('--json', 'JSON output')
|
|
698
949
|
.action((id, opts) => {
|
|
699
950
|
try {
|
|
951
|
+
const prior = getTask(id);
|
|
952
|
+
const previous = prior.state;
|
|
700
953
|
const t = taskRoomService().unblockTask({
|
|
701
954
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
702
955
|
});
|
|
956
|
+
auditTask('unblock', t, previous, t.state, prior.blocked?.at);
|
|
703
957
|
if (opts.json) {
|
|
704
958
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
705
959
|
return;
|
|
@@ -717,9 +971,11 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
717
971
|
.option('--json', 'JSON output')
|
|
718
972
|
.action((id, opts) => {
|
|
719
973
|
try {
|
|
974
|
+
const previous = getTask(id).state;
|
|
720
975
|
const t = taskRoomService().reviewTask({
|
|
721
976
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
722
977
|
});
|
|
978
|
+
auditTask('review', t, previous);
|
|
723
979
|
if (opts.json) {
|
|
724
980
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
725
981
|
return;
|
|
@@ -739,6 +995,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
739
995
|
.option('--json', 'JSON output')
|
|
740
996
|
.action(async (id, opts) => {
|
|
741
997
|
try {
|
|
998
|
+
const previous = getTask(id).state;
|
|
742
999
|
let summary = opts.summary;
|
|
743
1000
|
if (opts.summaryFile)
|
|
744
1001
|
summary = readFileSync(opts.summaryFile, 'utf8');
|
|
@@ -746,10 +1003,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
746
1003
|
const plan = await taskRoomService(opts.configuration).completeTask({
|
|
747
1004
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
748
1005
|
});
|
|
1006
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1007
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
749
1008
|
const settled = plan.settlementRequired
|
|
750
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1009
|
+
? await launchTaskSettleWorker(id, `ours-fleet task done ${id}`, opts.configuration)
|
|
751
1010
|
: { task: plan.task, timedOut: false };
|
|
752
1011
|
const t = settled.task;
|
|
1012
|
+
auditTask('done', t, previous);
|
|
753
1013
|
if (opts.json) {
|
|
754
1014
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
755
1015
|
return;
|
|
@@ -758,7 +1018,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
758
1018
|
console.log(renderMarkdownFailure({
|
|
759
1019
|
kind: 'pending', subject: `task done ${id}`,
|
|
760
1020
|
detail: 'The completion request was accepted and is still being settled.',
|
|
761
|
-
action: `
|
|
1021
|
+
action: `Re-run ours-fleet task done ${id}.`,
|
|
762
1022
|
}));
|
|
763
1023
|
return;
|
|
764
1024
|
}
|
|
@@ -777,13 +1037,17 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
777
1037
|
try {
|
|
778
1038
|
if (id !== confirmId)
|
|
779
1039
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
1040
|
+
const previous = getTask(id).state;
|
|
780
1041
|
const plan = await taskRoomService(opts.configuration).cancelTask({
|
|
781
1042
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
782
1043
|
});
|
|
1044
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1045
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
783
1046
|
const settled = plan.settlementRequired
|
|
784
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1047
|
+
? await launchTaskSettleWorker(id, `ours-fleet task cancel ${id} ${id}`, opts.configuration)
|
|
785
1048
|
: { task: plan.task, timedOut: false };
|
|
786
1049
|
const t = settled.task;
|
|
1050
|
+
auditTask('cancel', t, previous);
|
|
787
1051
|
if (opts.json) {
|
|
788
1052
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
789
1053
|
return;
|
|
@@ -792,7 +1056,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
792
1056
|
console.log(renderMarkdownFailure({
|
|
793
1057
|
kind: 'pending', subject: `task cancel ${id} ${id}`,
|
|
794
1058
|
detail: 'The cancellation request was accepted and is still being settled.',
|
|
795
|
-
action: `
|
|
1059
|
+
action: `Re-run ours-fleet task cancel ${id} ${id}.`,
|
|
796
1060
|
}));
|
|
797
1061
|
return;
|
|
798
1062
|
}
|
|
@@ -805,82 +1069,47 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
805
1069
|
}
|
|
806
1070
|
});
|
|
807
1071
|
cOpt(taskCmd.command('delete <id> <confirm-id>'))
|
|
808
|
-
.description('delete a
|
|
1072
|
+
.description('permanently delete a task in any lifecycle state (requires ID twice for confirmation)')
|
|
809
1073
|
.option('--json', 'JSON output')
|
|
810
|
-
.action((id, confirmId, opts) => {
|
|
1074
|
+
.action(async (id, confirmId, opts) => {
|
|
811
1075
|
try {
|
|
812
1076
|
if (id !== confirmId)
|
|
813
1077
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
814
|
-
const
|
|
1078
|
+
const accepted = await taskRoomService(opts.configuration).requestTaskDeletion({
|
|
815
1079
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
816
1080
|
});
|
|
817
|
-
if (
|
|
818
|
-
|
|
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
|
+
}));
|
|
819
1092
|
return;
|
|
820
1093
|
}
|
|
821
|
-
|
|
822
|
-
icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
|
|
823
|
-
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
824
|
-
}));
|
|
825
|
-
}
|
|
826
|
-
catch (e) {
|
|
827
|
-
if (opts.json)
|
|
828
|
-
die(e);
|
|
829
|
-
dieTaskRoom(e);
|
|
830
|
-
}
|
|
831
|
-
});
|
|
832
|
-
cOpt(taskCmd.command('recover <id>'))
|
|
833
|
-
.description('attempt to recover a stuck task')
|
|
834
|
-
.option('--json', 'JSON output')
|
|
835
|
-
.action(async (id, opts) => {
|
|
836
|
-
try {
|
|
837
|
-
const app = taskRoomService(opts.configuration);
|
|
838
|
-
const actor = { kind: 'local_control', surface: 'cli' };
|
|
839
|
-
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
840
|
-
const recovered = begin.kind === 'terminal_worker_required'
|
|
841
|
-
? await (async () => {
|
|
842
|
-
const settled = await launchTaskSettleWorker(id, opts.configuration);
|
|
843
|
-
return app.continueTaskRecovery({ actor, taskId: id, terminalTimedOut: settled.timedOut });
|
|
844
|
-
})()
|
|
845
|
-
: begin.result;
|
|
846
|
-
const t = recovered.task;
|
|
847
|
-
const room = recovered.room;
|
|
848
|
-
const recoveryActions = recovered.issues.map(issue => {
|
|
849
|
-
if (issue.code === 'terminal_pending')
|
|
850
|
-
return `Terminal intent remains pending — retry task recover ${id}`;
|
|
851
|
-
if (issue.code === 'waiting_cowork')
|
|
852
|
-
return 'Cowork management socket unreachable — check ours-cowork service';
|
|
853
|
-
if (issue.code === 'waiting_owner_invite')
|
|
854
|
-
return 'Owner invite invalid or expired — rotate rooms.owner.public_invite in config';
|
|
855
|
-
if (issue.code === 'owner_cid_mismatch')
|
|
856
|
-
return 'Owner CID mismatch — verify rooms.owner.expected_cid matches Messenger identity';
|
|
857
|
-
if (issue.code === 'member_failed')
|
|
858
|
-
return `Member creation failed at saga step ${issue.stepIndex} — inspect and retry`;
|
|
859
|
-
if (issue.code === 'waiting_seats')
|
|
860
|
-
return 'Members have not accepted their one-time room invites yet — inspect role logs, then retry recovery';
|
|
861
|
-
if (issue.code === 'resume_failed')
|
|
862
|
-
return `Resume failed: ${issue.error}`;
|
|
863
|
-
return 'Provisioning resumed successfully';
|
|
864
|
-
});
|
|
865
|
-
const result = { task: t, room: room ?? null, recovery_actions: recoveryActions };
|
|
1094
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
866
1095
|
if (opts.json) {
|
|
867
|
-
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
|
+
}));
|
|
868
1108
|
return;
|
|
869
1109
|
}
|
|
870
1110
|
console.log(renderMarkdownResult({
|
|
871
|
-
icon: '
|
|
872
|
-
fields: [
|
|
873
|
-
{ label: 'Task', value: t.task_id, kind: 'code' },
|
|
874
|
-
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
875
|
-
...(room ? [
|
|
876
|
-
{ label: 'Room', value: room.room_id, kind: 'code' },
|
|
877
|
-
{ label: 'Room status', value: roomStatus(room.state), kind: 'markdown' },
|
|
878
|
-
{ label: 'Saga', value: room.saga.phase, kind: 'code' },
|
|
879
|
-
] : []),
|
|
880
|
-
],
|
|
881
|
-
sections: recoveryActions.length
|
|
882
|
-
? [{ heading: 'Next steps', items: recoveryActions }]
|
|
883
|
-
: [{ heading: 'Result', items: ['No automated recovery action is available.'] }],
|
|
1111
|
+
icon: '🗑️', title: 'Task deleted',
|
|
1112
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
884
1113
|
}));
|
|
885
1114
|
}
|
|
886
1115
|
catch (e) {
|
|
@@ -907,54 +1136,105 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
907
1136
|
catch (e) {
|
|
908
1137
|
await taskRoomService(opts.configuration).recordSettlementError({
|
|
909
1138
|
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
910
|
-
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.`,
|
|
911
1140
|
}).catch(() => { });
|
|
912
1141
|
if (opts.json)
|
|
913
1142
|
die(e);
|
|
914
1143
|
dieTaskRoom(e);
|
|
915
1144
|
}
|
|
916
1145
|
});
|
|
917
|
-
cOpt(taskCmd.command('
|
|
918
|
-
.description('internal:
|
|
1146
|
+
cOpt(taskCmd.command('_provision <id>', { hidden: true }))
|
|
1147
|
+
.description('internal: continue an accepted room-provisioning operation')
|
|
919
1148
|
.option('--json', 'JSON output')
|
|
920
1149
|
.action(async (id, opts) => {
|
|
921
|
-
const app = taskRoomService(opts.configuration);
|
|
922
|
-
const actor = { kind: 'internal_worker', surface: 'cli' };
|
|
923
1150
|
try {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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;
|
|
929
1158
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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;
|
|
936
1166
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
+
}));
|
|
941
1196
|
}
|
|
942
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(() => { });
|
|
943
1203
|
if (opts.json)
|
|
944
1204
|
die(e);
|
|
945
1205
|
dieTaskRoom(e);
|
|
946
1206
|
}
|
|
947
1207
|
});
|
|
948
1208
|
cOpt(taskCmd.command('work <id>'))
|
|
949
|
-
.description('
|
|
1209
|
+
.description('deprecated alias for task start')
|
|
950
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')
|
|
951
1226
|
.option('--json', 'JSON output')
|
|
952
|
-
.action(async (id, opts) => {
|
|
1227
|
+
.action(async (id, opts, command) => {
|
|
953
1228
|
try {
|
|
1229
|
+
const previous = getTask(id).state;
|
|
1230
|
+
console.error('warning: `fleet task work` is deprecated; use `fleet task start`');
|
|
954
1231
|
const result = await taskRoomService(opts.configuration).ensureTaskWork({
|
|
955
1232
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, template: opts.template,
|
|
1233
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1234
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
956
1235
|
});
|
|
957
1236
|
const t = result.task;
|
|
1237
|
+
auditTask('work', t, previous);
|
|
958
1238
|
if (result.status === 'already_active') {
|
|
959
1239
|
if (opts.json) {
|
|
960
1240
|
console.log(JSON.stringify({ schema_version: 1, task: t, status: 'already_active' }, null, 2));
|
|
@@ -993,6 +1273,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
993
1273
|
.option('--json', 'JSON output')
|
|
994
1274
|
.action(async (id, opts) => {
|
|
995
1275
|
try {
|
|
1276
|
+
const previous = getTask(id).state;
|
|
996
1277
|
let summary = opts.summary;
|
|
997
1278
|
if (opts.summaryFile)
|
|
998
1279
|
summary = readFileSync(opts.summaryFile, 'utf8');
|
|
@@ -1000,10 +1281,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1000
1281
|
const plan = await taskRoomService(opts.configuration).finishTask({
|
|
1001
1282
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
1002
1283
|
});
|
|
1284
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1285
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
1003
1286
|
const settled = plan.settlementRequired
|
|
1004
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1287
|
+
? await launchTaskSettleWorker(id, `ours-fleet task finish ${id}`, opts.configuration)
|
|
1005
1288
|
: { task: plan.task, timedOut: false };
|
|
1006
1289
|
const t = settled.task;
|
|
1290
|
+
auditTask('finish', t, previous);
|
|
1007
1291
|
if (opts.json) {
|
|
1008
1292
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
1009
1293
|
return;
|
|
@@ -1012,7 +1296,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1012
1296
|
console.log(renderMarkdownFailure({
|
|
1013
1297
|
kind: 'pending', subject: `task finish ${id}`,
|
|
1014
1298
|
detail: 'The finish request was accepted and is still being settled.',
|
|
1015
|
-
action: `
|
|
1299
|
+
action: `Re-run ours-fleet task finish ${id}.`,
|
|
1016
1300
|
}));
|
|
1017
1301
|
return;
|
|
1018
1302
|
}
|
|
@@ -1033,15 +1317,32 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1033
1317
|
.description('create a standalone room')
|
|
1034
1318
|
.requiredOption('--name <name>', 'room name')
|
|
1035
1319
|
.option('--template <name>', 'room template')
|
|
1320
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1321
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1036
1322
|
.option('--goal <text>', 'room goal')
|
|
1037
1323
|
.option('--brief <text>', 'room briefing')
|
|
1038
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')
|
|
1039
1338
|
.option('--json', 'JSON output')
|
|
1040
|
-
.action(async (opts) => {
|
|
1339
|
+
.action(async (opts, command) => {
|
|
1041
1340
|
try {
|
|
1042
1341
|
const record = await taskRoomService(opts.configuration).createRoom({
|
|
1043
1342
|
actor: { kind: 'local_control', surface: 'cli' }, name: opts.name,
|
|
1044
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)),
|
|
1045
1346
|
});
|
|
1046
1347
|
if (opts.json) {
|
|
1047
1348
|
console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
|
|
@@ -1110,7 +1411,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1110
1411
|
],
|
|
1111
1412
|
sections: [
|
|
1112
1413
|
...(cowork.seats.length ? [{
|
|
1113
|
-
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)),
|
|
1114
1417
|
}] : []),
|
|
1115
1418
|
...(r ? roomStartupSections(r) : []),
|
|
1116
1419
|
],
|
|
@@ -1127,7 +1430,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1127
1430
|
.option('--json', 'JSON output')
|
|
1128
1431
|
.action(async (id, opts) => {
|
|
1129
1432
|
try {
|
|
1130
|
-
const { room } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1433
|
+
const { room, orchestration } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1131
1434
|
const url = `http://localhost:4460/room/${id}`;
|
|
1132
1435
|
if (opts.json) {
|
|
1133
1436
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, url, room_name: room.room_name }, null, 2));
|
|
@@ -1172,7 +1475,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1172
1475
|
],
|
|
1173
1476
|
sections: [{
|
|
1174
1477
|
heading: 'Members',
|
|
1175
|
-
...(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)) }
|
|
1176
1481
|
: { items: ['No members found.'] }),
|
|
1177
1482
|
}],
|
|
1178
1483
|
}));
|
|
@@ -1187,10 +1492,14 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1187
1492
|
try {
|
|
1188
1493
|
if (id !== confirmId)
|
|
1189
1494
|
throw taskRoomPublicError('room_confirmation_mismatch');
|
|
1495
|
+
const previous = getRoomRecord(id);
|
|
1190
1496
|
await taskRoomService(opts.configuration).requestRoomDeletion({
|
|
1191
1497
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1192
1498
|
});
|
|
1499
|
+
const closing = getRoomRecord(id);
|
|
1193
1500
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1501
|
+
if (closing)
|
|
1502
|
+
auditRoom('delete', closing, closing.state, settled.deleted ? 'deleted' : 'closing');
|
|
1194
1503
|
if (opts.json) {
|
|
1195
1504
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, deleted: settled.deleted }, null, 2));
|
|
1196
1505
|
return;
|
|
@@ -1199,7 +1508,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1199
1508
|
console.log(renderMarkdownFailure({
|
|
1200
1509
|
kind: 'pending', subject: `room delete ${id} ${id}`,
|
|
1201
1510
|
detail: 'The deletion request was accepted and is still being settled.',
|
|
1202
|
-
action: `Run ours-fleet room delete ${id} ${id}
|
|
1511
|
+
action: `Run ours-fleet room delete ${id} ${id} again.`,
|
|
1203
1512
|
}));
|
|
1204
1513
|
return;
|
|
1205
1514
|
}
|
|
@@ -1222,57 +1531,6 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1222
1531
|
.description('deprecated alias for room delete (requires ID twice for confirmation)')
|
|
1223
1532
|
.option('--json', 'JSON output')
|
|
1224
1533
|
.action(deleteAction);
|
|
1225
|
-
cOpt(roomCmd.command('recover <id>'))
|
|
1226
|
-
.description('attempt to recover a stuck room')
|
|
1227
|
-
.option('--json', 'JSON output')
|
|
1228
|
-
.action(async (id, opts) => {
|
|
1229
|
-
try {
|
|
1230
|
-
const recovered = await taskRoomService(opts.configuration).recoverRoom({
|
|
1231
|
-
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1232
|
-
});
|
|
1233
|
-
if (recovered.kind === 'deletion_worker_required') {
|
|
1234
|
-
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1235
|
-
if (opts.json) {
|
|
1236
|
-
console.log(JSON.stringify({
|
|
1237
|
-
schema_version: 1, room: null, orchestration: null,
|
|
1238
|
-
recovery_actions: [settled.timedOut
|
|
1239
|
-
? `Deletion remains pending — retry room delete ${id} ${id}`
|
|
1240
|
-
: 'Deletion completed successfully'],
|
|
1241
|
-
}, null, 2));
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
console.log(renderMarkdownResult({
|
|
1245
|
-
icon: settled.timedOut ? '⏳' : '🗑️',
|
|
1246
|
-
title: settled.timedOut ? 'Room deletion pending' : 'Room deleted',
|
|
1247
|
-
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1248
|
-
sections: [{ heading: 'Next step', items: [settled.timedOut
|
|
1249
|
-
? `Run ours-fleet room delete ${id} ${id} or room recover ${id} again.`
|
|
1250
|
-
: 'No recovery action is needed.'] }],
|
|
1251
|
-
}));
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
const { room: cowork, orchestration: r, issues: actions } = recovered;
|
|
1255
|
-
if (opts.json) {
|
|
1256
|
-
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null, recovery_actions: actions }, null, 2));
|
|
1257
|
-
return;
|
|
1258
|
-
}
|
|
1259
|
-
console.log(renderMarkdownResult({
|
|
1260
|
-
icon: '🛟', title: 'Room recovery',
|
|
1261
|
-
fields: [
|
|
1262
|
-
{ label: 'Room', value: cowork.room_id, kind: 'code' },
|
|
1263
|
-
{ label: 'Status', value: roomStatus(cowork.state), kind: 'markdown' },
|
|
1264
|
-
...(r ? [{ label: 'Saga', value: r.saga.phase, kind: 'code' }] : []),
|
|
1265
|
-
],
|
|
1266
|
-
sections: actions.length ? [{ heading: 'Next steps', items: actions }]
|
|
1267
|
-
: [{ heading: 'Result', items: ['No recovery action is needed.'] }],
|
|
1268
|
-
}));
|
|
1269
|
-
}
|
|
1270
|
-
catch (e) {
|
|
1271
|
-
if (opts.json)
|
|
1272
|
-
die(e);
|
|
1273
|
-
dieTaskRoom(e);
|
|
1274
|
-
}
|
|
1275
|
-
});
|
|
1276
1534
|
const internalDeleteAction = async (id, opts) => {
|
|
1277
1535
|
try {
|
|
1278
1536
|
const app = taskRoomService(opts.configuration);
|