@ours.network/fleet 1.1.0-nightly.8 → 1.1.0
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 +231 -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 +9 -2
- 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 +579 -173
- package/dist/briefing.js +85 -28
- 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 +130 -55
- package/dist/config.d.ts +38 -3
- package/dist/config.js +193 -28
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +193 -55
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +59 -9
- package/dist/fleet-command-audit.js +426 -63
- package/dist/fleet-proxy.d.ts +5 -2
- package/dist/harness/claude-code-session.js +2 -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 +18 -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 +21 -5
- package/dist/owner-channel/channel.js +312 -138
- 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/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- 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 +434 -279
- 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 -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 -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 +92 -33
- package/dist/session/acp.d.ts +4 -1
- package/dist/session/acp.js +29 -13
- 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 +2 -2
- package/dist/session/control.js +18 -2
- 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/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,35 +258,120 @@ const taskActionMarkdown = (title, task, fields = []) => {
|
|
|
215
258
|
],
|
|
216
259
|
});
|
|
217
260
|
};
|
|
218
|
-
function
|
|
219
|
-
const
|
|
220
|
-
if (
|
|
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))
|
|
221
300
|
return 'unresolved';
|
|
222
|
-
const selection =
|
|
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)`;
|
|
223
306
|
if (typeof selection.ref === 'string')
|
|
224
307
|
return `ref:${selection.ref}`;
|
|
225
308
|
if (!Object.hasOwn(selection, 'inline'))
|
|
226
309
|
return 'unresolved';
|
|
227
|
-
const
|
|
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);
|
|
228
317
|
return `inline:sha256:${fingerprint}`;
|
|
229
318
|
}
|
|
230
|
-
function
|
|
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;
|
|
231
335
|
recordFleetAuditResource('task', task.task_id);
|
|
232
336
|
if (task.room_id)
|
|
233
337
|
recordFleetAuditResource('room', task.room_id);
|
|
234
338
|
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
235
|
-
const
|
|
236
|
-
|
|
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,
|
|
237
348
|
previousState, newState,
|
|
349
|
+
revision: revision ?? task.blocked?.at ?? task.ended_at ?? task.started_at ?? task.created_at,
|
|
350
|
+
list: task.list_name ?? 'default',
|
|
238
351
|
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
239
|
-
roomId: task.room_id,
|
|
240
|
-
|
|
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) })) });
|
|
241
360
|
}
|
|
242
361
|
function auditRoom(operation, room, previousState, newState = room.state) {
|
|
362
|
+
if (previousState === newState)
|
|
363
|
+
return;
|
|
243
364
|
recordFleetAuditResource('room', room.room_id);
|
|
244
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,
|
|
245
368
|
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
246
|
-
participants: room.member_seats.map(member => ({ name: member.role_name,
|
|
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 })) });
|
|
247
375
|
}
|
|
248
376
|
const roomActionMarkdown = (title, room, fields = []) => {
|
|
249
377
|
recordFleetAuditResource('room', room.room_id);
|
|
@@ -262,7 +390,7 @@ const sleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms); });
|
|
|
262
390
|
function errorText(error) {
|
|
263
391
|
return error instanceof Error ? error.message : String(error);
|
|
264
392
|
}
|
|
265
|
-
async function launchTaskSettleWorker(taskId, configPath) {
|
|
393
|
+
async function launchTaskSettleWorker(taskId, retryCommand, configPath) {
|
|
266
394
|
const previousErrorAt = getTask(taskId).terminal_intent?.error_at;
|
|
267
395
|
try {
|
|
268
396
|
await launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, configPath);
|
|
@@ -270,8 +398,12 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
270
398
|
catch (error) {
|
|
271
399
|
await taskRoomService(configPath).recordSettlementError({
|
|
272
400
|
actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
|
|
273
|
-
recoveryHint: `External settle worker failed to start. Retry
|
|
401
|
+
recoveryHint: `External settle worker failed to start. Retry ${retryCommand}.`,
|
|
274
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 });
|
|
275
407
|
throw error;
|
|
276
408
|
}
|
|
277
409
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -284,7 +416,56 @@ async function launchTaskSettleWorker(taskId, configPath) {
|
|
|
284
416
|
}
|
|
285
417
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
286
418
|
}
|
|
287
|
-
|
|
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 };
|
|
288
469
|
}
|
|
289
470
|
async function launchRoomDeleteWorker(roomId, configPath) {
|
|
290
471
|
const previousErrorAt = getRoomRecord(roomId)?.close?.error_at;
|
|
@@ -296,6 +477,11 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
296
477
|
actor: { kind: 'local_control', surface: 'cli' }, roomId, error: errorText(error),
|
|
297
478
|
recoveryHint: `External delete worker failed to start. Retry room delete ${roomId} ${roomId}.`,
|
|
298
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 });
|
|
299
485
|
throw error;
|
|
300
486
|
}
|
|
301
487
|
const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
|
|
@@ -308,7 +494,12 @@ async function launchRoomDeleteWorker(roomId, configPath) {
|
|
|
308
494
|
}
|
|
309
495
|
await sleep(PUBLIC_SETTLE_POLL_MS);
|
|
310
496
|
}
|
|
311
|
-
|
|
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 };
|
|
312
503
|
}
|
|
313
504
|
function loadCfg(opts) {
|
|
314
505
|
return loadConfig(opts.configuration);
|
|
@@ -335,7 +526,7 @@ function resolveRoomTemplate(cfg, name) {
|
|
|
335
526
|
const template = resolveTemplate(name, allTemplates(cfg));
|
|
336
527
|
if (!template)
|
|
337
528
|
throw new Error(`template not found: ${name}`);
|
|
338
|
-
return snapshotTemplate(template);
|
|
529
|
+
return snapshotTemplate(template, cfg.agentTemplates);
|
|
339
530
|
}
|
|
340
531
|
function durableTaskRoomTemplate(task, room) {
|
|
341
532
|
const snapshot = room.template_snapshot;
|
|
@@ -370,69 +561,6 @@ function roomStartupSections(room) {
|
|
|
370
561
|
}
|
|
371
562
|
return sections;
|
|
372
563
|
}
|
|
373
|
-
async function provisionRoom(cfg, input) {
|
|
374
|
-
const rooms = cfg.rooms;
|
|
375
|
-
if (!rooms)
|
|
376
|
-
throw new ConfigError('rooms: configuration is required');
|
|
377
|
-
const attachOwner = rooms.defaults?.attach_owner !== false;
|
|
378
|
-
if (attachOwner && !cfg.ownerInvite)
|
|
379
|
-
throw new ConfigError('rooms.owner: public_invite or public_invite_file is required when attach_owner is enabled');
|
|
380
|
-
const cowork = coworkFor(cfg);
|
|
381
|
-
const goal = input.goal?.trim() || input.name;
|
|
382
|
-
const briefing = input.brief?.trim() || input.template?.contract?.trim() || goal;
|
|
383
|
-
const created = await cowork.createRoom({
|
|
384
|
-
room_name: input.name,
|
|
385
|
-
goal,
|
|
386
|
-
briefing,
|
|
387
|
-
quiet_membership: input.template?.room?.quiet_membership,
|
|
388
|
-
anonymous: input.template?.room?.anonymous,
|
|
389
|
-
});
|
|
390
|
-
let record = createRoomRecord({
|
|
391
|
-
room_id: created.room_id,
|
|
392
|
-
room_name: input.name,
|
|
393
|
-
room_identity_cid: created.identity_cid,
|
|
394
|
-
task_id: input.taskId,
|
|
395
|
-
template_snapshot: input.template,
|
|
396
|
-
});
|
|
397
|
-
input.onCreated?.(record);
|
|
398
|
-
record = advanceSaga(record.room_id, 'create_room', 1);
|
|
399
|
-
if (attachOwner) {
|
|
400
|
-
try {
|
|
401
|
-
record = advanceSaga(record.room_id, 'attach_owner', 2);
|
|
402
|
-
const accepted = await cowork.acceptInvite(record.room_id, cfg.ownerInvite, {
|
|
403
|
-
role: rooms.owner.role,
|
|
404
|
-
expected_cid: rooms.owner.expected_cid,
|
|
405
|
-
});
|
|
406
|
-
record = setOwnerSeat(record.room_id, accepted.seat_cid, cfg.ownerInviteFingerprint ?? '');
|
|
407
|
-
}
|
|
408
|
-
catch (error) {
|
|
409
|
-
const mismatch = error instanceof CoworkProtocolError && /CID|expected/i.test(error.message);
|
|
410
|
-
setSagaError(record.room_id, error instanceof Error ? error.message : String(error), mismatch
|
|
411
|
-
? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
|
|
412
|
-
: 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
|
|
413
|
-
throw error;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
record = advanceSaga(record.room_id, 'create_members', 3);
|
|
417
|
-
if (input.template && input.template.members.length > 0) {
|
|
418
|
-
record = await provisionMembers({
|
|
419
|
-
cfg,
|
|
420
|
-
cowork,
|
|
421
|
-
roomId: record.room_id,
|
|
422
|
-
taskId: input.taskId,
|
|
423
|
-
template: input.template,
|
|
424
|
-
binPath: getBinPath(),
|
|
425
|
-
brief: input.brief,
|
|
426
|
-
goal: input.goal,
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
record = activateRoom(record.room_id);
|
|
431
|
-
if (input.taskId)
|
|
432
|
-
activateTask(input.taskId);
|
|
433
|
-
}
|
|
434
|
-
return record;
|
|
435
|
-
}
|
|
436
564
|
export function registerTemplateCommands(parent, cOpt) {
|
|
437
565
|
const templateCmd = parent.command('template').description('room template operations');
|
|
438
566
|
cOpt(templateCmd.command('list'))
|
|
@@ -446,7 +574,7 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
446
574
|
return;
|
|
447
575
|
}
|
|
448
576
|
if (!templates.length) {
|
|
449
|
-
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.');
|
|
450
578
|
return;
|
|
451
579
|
}
|
|
452
580
|
for (const t of templates) {
|
|
@@ -474,7 +602,7 @@ export function registerTemplateCommands(parent, cOpt) {
|
|
|
474
602
|
console.log(`\nMembers:`);
|
|
475
603
|
for (const m of t.members)
|
|
476
604
|
console.log(` ${m.slot}: ${m.count}× ${m.role} (`
|
|
477
|
-
+
|
|
605
|
+
+ `agent template: ${m.agent_template})`);
|
|
478
606
|
console.log(`\nContent hash: ${t.content_hash}`);
|
|
479
607
|
}
|
|
480
608
|
catch (e) {
|
|
@@ -517,21 +645,50 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
517
645
|
.option('--brief-file <path>', 'task brief from file')
|
|
518
646
|
.option('--backlog', 'create in backlog (do not start immediately)')
|
|
519
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')
|
|
520
650
|
.option('--idempotency-key <key>', 'idempotency key')
|
|
521
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')
|
|
522
665
|
.option('--json', 'JSON output')
|
|
523
|
-
.action(async (opts) => {
|
|
666
|
+
.action(async (opts, command) => {
|
|
524
667
|
try {
|
|
525
|
-
const
|
|
668
|
+
const members = cliMemberOverrides(opts.membersFile, commandArgv(command));
|
|
669
|
+
const service = taskRoomService(opts.configuration);
|
|
670
|
+
const record = await service.createTask({
|
|
526
671
|
actor: { kind: 'local_control', surface: 'cli' }, title: opts.title,
|
|
527
672
|
brief: opts.brief, briefFile: opts.briefFile, template: opts.template,
|
|
528
673
|
backlog: opts.backlog, noRoom: opts.room === false,
|
|
674
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
529
675
|
idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
|
|
530
676
|
list: opts.list,
|
|
677
|
+
members,
|
|
531
678
|
});
|
|
532
|
-
|
|
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);
|
|
533
685
|
if (opts.json) {
|
|
534
|
-
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));
|
|
535
692
|
return;
|
|
536
693
|
}
|
|
537
694
|
console.log(taskActionMarkdown('Task created', record, [
|
|
@@ -553,7 +710,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
553
710
|
.option('--list <name>', 'filter by task list')
|
|
554
711
|
.option('--group-by-list', 'group deterministic results by list (JSON)')
|
|
555
712
|
.option('--json', 'JSON output')
|
|
556
|
-
.action((opts) => {
|
|
713
|
+
.action(async (opts) => {
|
|
557
714
|
try {
|
|
558
715
|
let stateFilter;
|
|
559
716
|
if (opts.state && opts.state !== 'all') {
|
|
@@ -579,9 +736,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
579
736
|
taskCmd.command('lists')
|
|
580
737
|
.description('list named task lists')
|
|
581
738
|
.option('--json', 'JSON output')
|
|
582
|
-
.action((opts) => {
|
|
739
|
+
.action(async (opts) => {
|
|
583
740
|
try {
|
|
584
|
-
const
|
|
741
|
+
const service = taskRoomService(opts.configuration);
|
|
742
|
+
const lists = service.listTaskLists();
|
|
585
743
|
if (opts.json) {
|
|
586
744
|
console.log(JSON.stringify({ schema_version: 1, lists }, null, 2));
|
|
587
745
|
return;
|
|
@@ -672,9 +830,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
672
830
|
taskCmd.command('show <id>')
|
|
673
831
|
.description('show task details')
|
|
674
832
|
.option('--json', 'JSON output')
|
|
675
|
-
.action((id, opts) => {
|
|
833
|
+
.action(async (id, opts) => {
|
|
676
834
|
try {
|
|
677
|
-
const
|
|
835
|
+
const service = taskRoomService();
|
|
836
|
+
const { task: t, orchestration: room } = service.getTask(id);
|
|
678
837
|
if (opts.json) {
|
|
679
838
|
console.log(JSON.stringify({
|
|
680
839
|
schema_version: 1, task: t, orchestration: room ?? null,
|
|
@@ -699,7 +858,9 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
699
858
|
],
|
|
700
859
|
sections: [
|
|
701
860
|
...(t.member_roles.length ? [{
|
|
702
|
-
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)),
|
|
703
864
|
}] : []),
|
|
704
865
|
...(room ? roomStartupSections(room) : []),
|
|
705
866
|
],
|
|
@@ -712,20 +873,41 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
712
873
|
}
|
|
713
874
|
});
|
|
714
875
|
cOpt(taskCmd.command('start <id>'))
|
|
715
|
-
.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')
|
|
716
893
|
.option('--json', 'JSON output')
|
|
717
|
-
.action(async (id, opts) => {
|
|
894
|
+
.action(async (id, opts, command) => {
|
|
718
895
|
try {
|
|
719
|
-
const
|
|
720
|
-
const t = await
|
|
896
|
+
const service = taskRoomService(opts.configuration);
|
|
897
|
+
const t = await service.startTask({
|
|
721
898
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
899
|
+
template: opts.template, members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
900
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
722
901
|
});
|
|
723
|
-
|
|
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);
|
|
724
906
|
if (opts.json) {
|
|
725
|
-
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
907
|
+
console.log(JSON.stringify({ schema_version: 1, task: t, provisioning }, null, 2));
|
|
726
908
|
return;
|
|
727
909
|
}
|
|
728
|
-
console.log(
|
|
910
|
+
console.log(provisioningMarkdown(provisioning));
|
|
729
911
|
}
|
|
730
912
|
catch (e) {
|
|
731
913
|
if (e instanceof TaskRoomApplicationError && e.code === 'task_template_drift')
|
|
@@ -766,11 +948,12 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
766
948
|
.option('--json', 'JSON output')
|
|
767
949
|
.action((id, opts) => {
|
|
768
950
|
try {
|
|
769
|
-
const
|
|
951
|
+
const prior = getTask(id);
|
|
952
|
+
const previous = prior.state;
|
|
770
953
|
const t = taskRoomService().unblockTask({
|
|
771
954
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
772
955
|
});
|
|
773
|
-
auditTask('unblock', t, previous);
|
|
956
|
+
auditTask('unblock', t, previous, t.state, prior.blocked?.at);
|
|
774
957
|
if (opts.json) {
|
|
775
958
|
console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
|
|
776
959
|
return;
|
|
@@ -820,8 +1003,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
820
1003
|
const plan = await taskRoomService(opts.configuration).completeTask({
|
|
821
1004
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
822
1005
|
});
|
|
1006
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1007
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
823
1008
|
const settled = plan.settlementRequired
|
|
824
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1009
|
+
? await launchTaskSettleWorker(id, `ours-fleet task done ${id}`, opts.configuration)
|
|
825
1010
|
: { task: plan.task, timedOut: false };
|
|
826
1011
|
const t = settled.task;
|
|
827
1012
|
auditTask('done', t, previous);
|
|
@@ -833,7 +1018,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
833
1018
|
console.log(renderMarkdownFailure({
|
|
834
1019
|
kind: 'pending', subject: `task done ${id}`,
|
|
835
1020
|
detail: 'The completion request was accepted and is still being settled.',
|
|
836
|
-
action: `
|
|
1021
|
+
action: `Re-run ours-fleet task done ${id}.`,
|
|
837
1022
|
}));
|
|
838
1023
|
return;
|
|
839
1024
|
}
|
|
@@ -856,8 +1041,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
856
1041
|
const plan = await taskRoomService(opts.configuration).cancelTask({
|
|
857
1042
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
858
1043
|
});
|
|
1044
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1045
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
859
1046
|
const settled = plan.settlementRequired
|
|
860
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1047
|
+
? await launchTaskSettleWorker(id, `ours-fleet task cancel ${id} ${id}`, opts.configuration)
|
|
861
1048
|
: { task: plan.task, timedOut: false };
|
|
862
1049
|
const t = settled.task;
|
|
863
1050
|
auditTask('cancel', t, previous);
|
|
@@ -869,7 +1056,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
869
1056
|
console.log(renderMarkdownFailure({
|
|
870
1057
|
kind: 'pending', subject: `task cancel ${id} ${id}`,
|
|
871
1058
|
detail: 'The cancellation request was accepted and is still being settled.',
|
|
872
|
-
action: `
|
|
1059
|
+
action: `Re-run ours-fleet task cancel ${id} ${id}.`,
|
|
873
1060
|
}));
|
|
874
1061
|
return;
|
|
875
1062
|
}
|
|
@@ -882,91 +1069,47 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
882
1069
|
}
|
|
883
1070
|
});
|
|
884
1071
|
cOpt(taskCmd.command('delete <id> <confirm-id>'))
|
|
885
|
-
.description('delete a
|
|
1072
|
+
.description('permanently delete a task in any lifecycle state (requires ID twice for confirmation)')
|
|
886
1073
|
.option('--json', 'JSON output')
|
|
887
|
-
.action((id, confirmId, opts) => {
|
|
1074
|
+
.action(async (id, confirmId, opts) => {
|
|
888
1075
|
try {
|
|
889
1076
|
if (id !== confirmId)
|
|
890
1077
|
throw taskRoomPublicError('task_confirmation_mismatch');
|
|
891
|
-
|
|
892
|
-
try {
|
|
893
|
-
prior = getTask(id);
|
|
894
|
-
}
|
|
895
|
-
catch { /* idempotent already-absent delete */ }
|
|
896
|
-
const deleted = taskRoomService().deleteTask({
|
|
1078
|
+
const accepted = await taskRoomService(opts.configuration).requestTaskDeletion({
|
|
897
1079
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
|
|
898
1080
|
});
|
|
899
|
-
if (
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
+
}));
|
|
903
1092
|
return;
|
|
904
1093
|
}
|
|
905
|
-
|
|
906
|
-
icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
|
|
907
|
-
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
908
|
-
}));
|
|
909
|
-
}
|
|
910
|
-
catch (e) {
|
|
911
|
-
if (opts.json)
|
|
912
|
-
die(e);
|
|
913
|
-
dieTaskRoom(e);
|
|
914
|
-
}
|
|
915
|
-
});
|
|
916
|
-
cOpt(taskCmd.command('recover <id>'))
|
|
917
|
-
.description('attempt to recover a stuck task')
|
|
918
|
-
.option('--json', 'JSON output')
|
|
919
|
-
.action(async (id, opts) => {
|
|
920
|
-
try {
|
|
921
|
-
const previous = getTask(id).state;
|
|
922
|
-
const app = taskRoomService(opts.configuration);
|
|
923
|
-
const actor = { kind: 'local_control', surface: 'cli' };
|
|
924
|
-
const begin = await app.beginTaskRecovery({ actor, taskId: id });
|
|
925
|
-
const recovered = begin.kind === 'terminal_worker_required'
|
|
926
|
-
? await (async () => {
|
|
927
|
-
const settled = await launchTaskSettleWorker(id, opts.configuration);
|
|
928
|
-
return app.continueTaskRecovery({ actor, taskId: id, terminalTimedOut: settled.timedOut });
|
|
929
|
-
})()
|
|
930
|
-
: begin.result;
|
|
931
|
-
const t = recovered.task;
|
|
932
|
-
auditTask('recover', t, previous);
|
|
933
|
-
const room = recovered.room;
|
|
934
|
-
const recoveryActions = recovered.issues.map(issue => {
|
|
935
|
-
if (issue.code === 'terminal_pending')
|
|
936
|
-
return `Terminal intent remains pending — retry task recover ${id}`;
|
|
937
|
-
if (issue.code === 'waiting_cowork')
|
|
938
|
-
return 'Cowork management socket unreachable — check ours-cowork service';
|
|
939
|
-
if (issue.code === 'waiting_owner_invite')
|
|
940
|
-
return 'Owner invite invalid or expired — rotate rooms.owner.public_invite in config';
|
|
941
|
-
if (issue.code === 'owner_cid_mismatch')
|
|
942
|
-
return 'Owner CID mismatch — verify rooms.owner.expected_cid matches Messenger identity';
|
|
943
|
-
if (issue.code === 'member_failed')
|
|
944
|
-
return `Member creation failed at saga step ${issue.stepIndex} — inspect and retry`;
|
|
945
|
-
if (issue.code === 'waiting_seats')
|
|
946
|
-
return 'Members have not accepted their one-time room invites yet — inspect role logs, then retry recovery';
|
|
947
|
-
if (issue.code === 'resume_failed')
|
|
948
|
-
return `Resume failed: ${issue.error}`;
|
|
949
|
-
return 'Provisioning resumed successfully';
|
|
950
|
-
});
|
|
951
|
-
const result = { task: t, room: room ?? null, recovery_actions: recoveryActions };
|
|
1094
|
+
const settled = await launchTaskDeleteWorker(id, opts.configuration);
|
|
952
1095
|
if (opts.json) {
|
|
953
|
-
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
|
+
}));
|
|
954
1108
|
return;
|
|
955
1109
|
}
|
|
956
1110
|
console.log(renderMarkdownResult({
|
|
957
|
-
icon: '
|
|
958
|
-
fields: [
|
|
959
|
-
{ label: 'Task', value: t.task_id, kind: 'code' },
|
|
960
|
-
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
961
|
-
...(room ? [
|
|
962
|
-
{ label: 'Room', value: room.room_id, kind: 'code' },
|
|
963
|
-
{ label: 'Room status', value: roomStatus(room.state), kind: 'markdown' },
|
|
964
|
-
{ label: 'Saga', value: room.saga.phase, kind: 'code' },
|
|
965
|
-
] : []),
|
|
966
|
-
],
|
|
967
|
-
sections: recoveryActions.length
|
|
968
|
-
? [{ heading: 'Next steps', items: recoveryActions }]
|
|
969
|
-
: [{ heading: 'Result', items: ['No automated recovery action is available.'] }],
|
|
1111
|
+
icon: '🗑️', title: 'Task deleted',
|
|
1112
|
+
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
970
1113
|
}));
|
|
971
1114
|
}
|
|
972
1115
|
catch (e) {
|
|
@@ -993,53 +1136,102 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
993
1136
|
catch (e) {
|
|
994
1137
|
await taskRoomService(opts.configuration).recordSettlementError({
|
|
995
1138
|
actor: { kind: 'internal_worker', surface: 'cli' }, taskId: id,
|
|
996
|
-
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.`,
|
|
997
1140
|
}).catch(() => { });
|
|
998
1141
|
if (opts.json)
|
|
999
1142
|
die(e);
|
|
1000
1143
|
dieTaskRoom(e);
|
|
1001
1144
|
}
|
|
1002
1145
|
});
|
|
1003
|
-
cOpt(taskCmd.command('
|
|
1004
|
-
.description('internal:
|
|
1146
|
+
cOpt(taskCmd.command('_provision <id>', { hidden: true }))
|
|
1147
|
+
.description('internal: continue an accepted room-provisioning operation')
|
|
1005
1148
|
.option('--json', 'JSON output')
|
|
1006
1149
|
.action(async (id, opts) => {
|
|
1007
|
-
const app = taskRoomService(opts.configuration);
|
|
1008
|
-
const actor = { kind: 'internal_worker', surface: 'cli' };
|
|
1009
1150
|
try {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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;
|
|
1015
1158
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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;
|
|
1022
1166
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
+
}));
|
|
1027
1196
|
}
|
|
1028
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(() => { });
|
|
1029
1203
|
if (opts.json)
|
|
1030
1204
|
die(e);
|
|
1031
1205
|
dieTaskRoom(e);
|
|
1032
1206
|
}
|
|
1033
1207
|
});
|
|
1034
1208
|
cOpt(taskCmd.command('work <id>'))
|
|
1035
|
-
.description('
|
|
1209
|
+
.description('deprecated alias for task start')
|
|
1036
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')
|
|
1037
1226
|
.option('--json', 'JSON output')
|
|
1038
|
-
.action(async (id, opts) => {
|
|
1227
|
+
.action(async (id, opts, command) => {
|
|
1039
1228
|
try {
|
|
1040
1229
|
const previous = getTask(id).state;
|
|
1230
|
+
console.error('warning: `fleet task work` is deprecated; use `fleet task start`');
|
|
1041
1231
|
const result = await taskRoomService(opts.configuration).ensureTaskWork({
|
|
1042
1232
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, template: opts.template,
|
|
1233
|
+
members: cliMemberOverrides(opts.membersFile, commandArgv(command)),
|
|
1234
|
+
anonymous: cliAnonymousOverride(commandArgv(command)),
|
|
1043
1235
|
});
|
|
1044
1236
|
const t = result.task;
|
|
1045
1237
|
auditTask('work', t, previous);
|
|
@@ -1089,8 +1281,10 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1089
1281
|
const plan = await taskRoomService(opts.configuration).finishTask({
|
|
1090
1282
|
actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
|
|
1091
1283
|
});
|
|
1284
|
+
if (plan.task.terminal_intent?.status === 'pending')
|
|
1285
|
+
auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
|
|
1092
1286
|
const settled = plan.settlementRequired
|
|
1093
|
-
? await launchTaskSettleWorker(id, opts.configuration)
|
|
1287
|
+
? await launchTaskSettleWorker(id, `ours-fleet task finish ${id}`, opts.configuration)
|
|
1094
1288
|
: { task: plan.task, timedOut: false };
|
|
1095
1289
|
const t = settled.task;
|
|
1096
1290
|
auditTask('finish', t, previous);
|
|
@@ -1102,7 +1296,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1102
1296
|
console.log(renderMarkdownFailure({
|
|
1103
1297
|
kind: 'pending', subject: `task finish ${id}`,
|
|
1104
1298
|
detail: 'The finish request was accepted and is still being settled.',
|
|
1105
|
-
action: `
|
|
1299
|
+
action: `Re-run ours-fleet task finish ${id}.`,
|
|
1106
1300
|
}));
|
|
1107
1301
|
return;
|
|
1108
1302
|
}
|
|
@@ -1123,17 +1317,33 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1123
1317
|
.description('create a standalone room')
|
|
1124
1318
|
.requiredOption('--name <name>', 'room name')
|
|
1125
1319
|
.option('--template <name>', 'room template')
|
|
1320
|
+
.option('--anonymous', 'create an anonymous Cowork room')
|
|
1321
|
+
.option('--no-anonymous', 'explicitly disable template anonymous mode')
|
|
1126
1322
|
.option('--goal <text>', 'room goal')
|
|
1127
1323
|
.option('--brief <text>', 'room briefing')
|
|
1128
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')
|
|
1129
1338
|
.option('--json', 'JSON output')
|
|
1130
|
-
.action(async (opts) => {
|
|
1339
|
+
.action(async (opts, command) => {
|
|
1131
1340
|
try {
|
|
1132
1341
|
const record = await taskRoomService(opts.configuration).createRoom({
|
|
1133
1342
|
actor: { kind: 'local_control', surface: 'cli' }, name: opts.name,
|
|
1134
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)),
|
|
1135
1346
|
});
|
|
1136
|
-
auditRoom('create', record, 'none');
|
|
1137
1347
|
if (opts.json) {
|
|
1138
1348
|
console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
|
|
1139
1349
|
return;
|
|
@@ -1201,7 +1411,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1201
1411
|
],
|
|
1202
1412
|
sections: [
|
|
1203
1413
|
...(cowork.seats.length ? [{
|
|
1204
|
-
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)),
|
|
1205
1417
|
}] : []),
|
|
1206
1418
|
...(r ? roomStartupSections(r) : []),
|
|
1207
1419
|
],
|
|
@@ -1219,8 +1431,6 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1219
1431
|
.action(async (id, opts) => {
|
|
1220
1432
|
try {
|
|
1221
1433
|
const { room, orchestration } = await taskRoomService(opts.configuration).getRoomDetail(id);
|
|
1222
|
-
if (orchestration)
|
|
1223
|
-
auditRoom('open', orchestration, orchestration.state);
|
|
1224
1434
|
const url = `http://localhost:4460/room/${id}`;
|
|
1225
1435
|
if (opts.json) {
|
|
1226
1436
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, url, room_name: room.room_name }, null, 2));
|
|
@@ -1247,8 +1457,6 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1247
1457
|
.action(async (id, opts) => {
|
|
1248
1458
|
try {
|
|
1249
1459
|
const { orchestration: r, members } = await taskRoomService(opts.configuration).getRoomMembers(id);
|
|
1250
|
-
if (r)
|
|
1251
|
-
auditRoom('members', r, r.state);
|
|
1252
1460
|
if (opts.json) {
|
|
1253
1461
|
console.log(JSON.stringify({
|
|
1254
1462
|
schema_version: 1,
|
|
@@ -1267,7 +1475,9 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1267
1475
|
],
|
|
1268
1476
|
sections: [{
|
|
1269
1477
|
heading: 'Members',
|
|
1270
|
-
...(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)) }
|
|
1271
1481
|
: { items: ['No members found.'] }),
|
|
1272
1482
|
}],
|
|
1273
1483
|
}));
|
|
@@ -1286,9 +1496,10 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1286
1496
|
await taskRoomService(opts.configuration).requestRoomDeletion({
|
|
1287
1497
|
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1288
1498
|
});
|
|
1499
|
+
const closing = getRoomRecord(id);
|
|
1289
1500
|
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1290
|
-
if (
|
|
1291
|
-
auditRoom('delete',
|
|
1501
|
+
if (closing)
|
|
1502
|
+
auditRoom('delete', closing, closing.state, settled.deleted ? 'deleted' : 'closing');
|
|
1292
1503
|
if (opts.json) {
|
|
1293
1504
|
console.log(JSON.stringify({ schema_version: 1, room_id: id, deleted: settled.deleted }, null, 2));
|
|
1294
1505
|
return;
|
|
@@ -1297,7 +1508,7 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1297
1508
|
console.log(renderMarkdownFailure({
|
|
1298
1509
|
kind: 'pending', subject: `room delete ${id} ${id}`,
|
|
1299
1510
|
detail: 'The deletion request was accepted and is still being settled.',
|
|
1300
|
-
action: `Run ours-fleet room delete ${id} ${id}
|
|
1511
|
+
action: `Run ours-fleet room delete ${id} ${id} again.`,
|
|
1301
1512
|
}));
|
|
1302
1513
|
return;
|
|
1303
1514
|
}
|
|
@@ -1320,62 +1531,6 @@ export function registerRoomCommands(parent, cOpt) {
|
|
|
1320
1531
|
.description('deprecated alias for room delete (requires ID twice for confirmation)')
|
|
1321
1532
|
.option('--json', 'JSON output')
|
|
1322
1533
|
.action(deleteAction);
|
|
1323
|
-
cOpt(roomCmd.command('recover <id>'))
|
|
1324
|
-
.description('attempt to recover a stuck room')
|
|
1325
|
-
.option('--json', 'JSON output')
|
|
1326
|
-
.action(async (id, opts) => {
|
|
1327
|
-
try {
|
|
1328
|
-
const previous = getRoomRecord(id);
|
|
1329
|
-
const recovered = await taskRoomService(opts.configuration).recoverRoom({
|
|
1330
|
-
actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
|
|
1331
|
-
});
|
|
1332
|
-
if (recovered.kind === 'deletion_worker_required') {
|
|
1333
|
-
const settled = await launchRoomDeleteWorker(id, opts.configuration);
|
|
1334
|
-
if (previous)
|
|
1335
|
-
auditRoom('recover', previous, previous.state, settled.deleted ? 'deleted' : 'closing');
|
|
1336
|
-
if (opts.json) {
|
|
1337
|
-
console.log(JSON.stringify({
|
|
1338
|
-
schema_version: 1, room: null, orchestration: null,
|
|
1339
|
-
recovery_actions: [settled.timedOut
|
|
1340
|
-
? `Deletion remains pending — retry room delete ${id} ${id}`
|
|
1341
|
-
: 'Deletion completed successfully'],
|
|
1342
|
-
}, null, 2));
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1345
|
-
console.log(renderMarkdownResult({
|
|
1346
|
-
icon: settled.timedOut ? '⏳' : '🗑️',
|
|
1347
|
-
title: settled.timedOut ? 'Room deletion pending' : 'Room deleted',
|
|
1348
|
-
fields: [{ label: 'ID', value: id, kind: 'code' }],
|
|
1349
|
-
sections: [{ heading: 'Next step', items: [settled.timedOut
|
|
1350
|
-
? `Run ours-fleet room delete ${id} ${id} or room recover ${id} again.`
|
|
1351
|
-
: 'No recovery action is needed.'] }],
|
|
1352
|
-
}));
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1355
|
-
const { room: cowork, orchestration: r, issues: actions } = recovered;
|
|
1356
|
-
if (r)
|
|
1357
|
-
auditRoom('recover', r, previous?.state ?? 'unresolved', r.state);
|
|
1358
|
-
if (opts.json) {
|
|
1359
|
-
console.log(JSON.stringify({ schema_version: 1, room: cowork, orchestration: r ?? null, recovery_actions: actions }, null, 2));
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
console.log(renderMarkdownResult({
|
|
1363
|
-
icon: '🛟', title: 'Room recovery',
|
|
1364
|
-
fields: [
|
|
1365
|
-
{ label: 'Room', value: cowork.room_id, kind: 'code' },
|
|
1366
|
-
{ label: 'Status', value: roomStatus(cowork.state), kind: 'markdown' },
|
|
1367
|
-
...(r ? [{ label: 'Saga', value: r.saga.phase, kind: 'code' }] : []),
|
|
1368
|
-
],
|
|
1369
|
-
sections: actions.length ? [{ heading: 'Next steps', items: actions }]
|
|
1370
|
-
: [{ heading: 'Result', items: ['No recovery action is needed.'] }],
|
|
1371
|
-
}));
|
|
1372
|
-
}
|
|
1373
|
-
catch (e) {
|
|
1374
|
-
if (opts.json)
|
|
1375
|
-
die(e);
|
|
1376
|
-
dieTaskRoom(e);
|
|
1377
|
-
}
|
|
1378
|
-
});
|
|
1379
1534
|
const internalDeleteAction = async (id, opts) => {
|
|
1380
1535
|
try {
|
|
1381
1536
|
const app = taskRoomService(opts.configuration);
|