@ours.network/fleet 1.1.0-nightly.12 → 1.1.0-nightly.14
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 +7 -0
- package/dist/application/role-creation-service.js +8 -1
- package/dist/application/task-room-service.d.ts +51 -5
- package/dist/application/task-room-service.js +175 -27
- package/dist/briefing.js +3 -3
- package/dist/build-info.json +4 -4
- package/dist/config.d.ts +1 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +25 -5
- package/dist/fleet-command-audit.d.ts +4 -0
- package/dist/fleet-command-audit.js +126 -22
- package/dist/fleet-proxy.d.ts +3 -0
- package/dist/lifecycle-summary.d.ts +118 -0
- package/dist/lifecycle-summary.js +161 -0
- package/dist/owner-channel/channel.d.ts +2 -0
- package/dist/owner-channel/channel.js +45 -9
- package/dist/owner-channel/commands.d.ts +4 -1
- package/dist/owner-channel/commands.js +3 -6
- package/dist/rooms-tasks/cli.js +170 -19
- package/dist/rooms-tasks/close.d.ts +8 -0
- package/dist/rooms-tasks/close.js +10 -3
- package/dist/rooms-tasks/config.js +5 -1
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/launch-snapshot.d.ts +9 -0
- package/dist/rooms-tasks/launch-snapshot.js +18 -1
- package/dist/rooms-tasks/provision.js +94 -14
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +8 -1
- package/dist/rooms-tasks/task-state.d.ts +113 -3
- package/dist/rooms-tasks/task-state.js +345 -13
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +58 -0
- package/dist/rooms-tasks/types.js +12 -0
- package/dist/web/server.js +28 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -525,6 +525,13 @@ Cowork role, and task. The agent creates that identity itself with ours MCP
|
|
|
525
525
|
work immediately. Fleet activates the room from Cowork's authenticated seat; there
|
|
526
526
|
is no briefing hash, startup ACK, or separate role-briefing readiness gate.
|
|
527
527
|
|
|
528
|
+
Set `room.anonymous: true` on a room template, or pass `--anonymous` to
|
|
529
|
+
`task create`, `task start`, `task work`, or `room create`, to create an
|
|
530
|
+
anonymous Cowork room. `--no-anonymous` explicitly overrides an anonymous
|
|
531
|
+
template. Fleet records the resolved value before room creation so retries keep
|
|
532
|
+
the same choice. Temporary members of an anonymous room are instructed to call
|
|
533
|
+
`create_temporary_identity` with `expose_local=false`.
|
|
534
|
+
|
|
528
535
|
Human task and room results use the same compact Markdown presentation in the
|
|
529
536
|
CLI and authenticated owner channel: a short heading, icon-plus-word status,
|
|
530
537
|
code-formatted identifiers, bounded summaries, and actionable recovery or error
|
|
@@ -10,6 +10,7 @@ import { FleetError, normalizeError } from './errors.js';
|
|
|
10
10
|
import { inheritCallerSpawnDefaults } from '../fleet-proxy.js';
|
|
11
11
|
import { effectivePermissionMode } from '../permissions.js';
|
|
12
12
|
import { effectiveRoleModel } from '../model-env.js';
|
|
13
|
+
import { selectionOrigin, summarizeResolvedLaunch } from '../lifecycle-summary.js';
|
|
13
14
|
const canonical = (value) => {
|
|
14
15
|
if (Array.isArray(value))
|
|
15
16
|
return `[${value.map(canonical).join(',')}]`;
|
|
@@ -71,15 +72,21 @@ export class RoleCreationService {
|
|
|
71
72
|
const fingerprint = hash(value.inline).slice(0, 16);
|
|
72
73
|
return `inline:sha256:${fingerprint} (${provenance})`;
|
|
73
74
|
};
|
|
75
|
+
const permissionMode = effectivePermissionMode(plan.preview.resolvedRole);
|
|
74
76
|
return {
|
|
75
77
|
caller: plan.caller, role: plan.options.name,
|
|
76
78
|
lifetime: plan.options.temp ? 'temporary' : 'permanent', statePath,
|
|
77
79
|
harness: plan.preview.resolvedRole.harness, session: plan.preview.resolvedRole.session,
|
|
78
80
|
...(effectiveRoleModel(plan.preview.resolvedRole) ? { model: effectiveRoleModel(plan.preview.resolvedRole) } : {}),
|
|
79
81
|
monitor: { mode: plan.preview.resolvedRole.monitor.mode, interrupt: plan.preview.resolvedRole.monitor.interrupt },
|
|
80
|
-
permissionMode
|
|
82
|
+
permissionMode, inherited: plan.inherited,
|
|
81
83
|
creationActionId,
|
|
82
84
|
brainSummary: selectionSummary('brain'), roleSummary: selectionSummary('role'),
|
|
85
|
+
configuration: summarizeResolvedLaunch(plan.preview.resolvedRole, {
|
|
86
|
+
role: selectionOrigin(plan.options.role ?? plan.options.agentDefinition?.role),
|
|
87
|
+
brain: selectionOrigin(plan.options.brain ?? plan.options.agentDefinition?.brain),
|
|
88
|
+
permissionMode,
|
|
89
|
+
}),
|
|
83
90
|
};
|
|
84
91
|
}
|
|
85
92
|
launchSync(options, creation) {
|
|
@@ -3,7 +3,9 @@ import { type CoworkAdapter } from '../rooms-tasks/cowork-adapter.js';
|
|
|
3
3
|
import { provisionMembers } from '../rooms-tasks/provision.js';
|
|
4
4
|
import { moveTaskToList } from '../rooms-tasks/task-state.js';
|
|
5
5
|
import { type MemberOverrides } from '../rooms-tasks/member-overrides.js';
|
|
6
|
-
import
|
|
6
|
+
import { type TaskDeletionSettleResult } from '../rooms-tasks/deletion.js';
|
|
7
|
+
import type { RoomLaunchPolicy, RoomOrchestrationRecord, TaskListRecord, TaskOrigin, TaskOutcome, TaskRecord, TaskState, TemplateSnapshot } from '../rooms-tasks/types.js';
|
|
8
|
+
import type { TaskDeletionAcceptance } from '../rooms-tasks/task-state.js';
|
|
7
9
|
export type TaskRoomActor = {
|
|
8
10
|
kind: 'local_control';
|
|
9
11
|
surface: 'cli' | 'web';
|
|
@@ -38,14 +40,17 @@ export interface TaskRecoveryResult {
|
|
|
38
40
|
export type TaskRecoveryBegin = {
|
|
39
41
|
kind: 'terminal_worker_required';
|
|
40
42
|
taskId: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'deletion_worker_required';
|
|
45
|
+
taskId: string;
|
|
41
46
|
} | {
|
|
42
47
|
kind: 'final';
|
|
43
48
|
result: TaskRecoveryResult;
|
|
44
49
|
};
|
|
45
50
|
export declare class TaskRoomApplicationError extends Error {
|
|
46
|
-
readonly code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'room_not_found' | 'room_record_not_found';
|
|
51
|
+
readonly code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'task_deleting' | 'room_not_found' | 'room_record_not_found';
|
|
47
52
|
readonly fields: Readonly<Record<string, string>>;
|
|
48
|
-
constructor(code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'room_not_found' | 'room_record_not_found', message: string, fields?: Readonly<Record<string, string>>);
|
|
53
|
+
constructor(code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'task_deleting' | 'room_not_found' | 'room_record_not_found', message: string, fields?: Readonly<Record<string, string>>);
|
|
49
54
|
}
|
|
50
55
|
export interface CreateTaskRequest {
|
|
51
56
|
actor: TaskRoomActor;
|
|
@@ -59,6 +64,7 @@ export interface CreateTaskRequest {
|
|
|
59
64
|
origin: TaskOrigin;
|
|
60
65
|
list?: string;
|
|
61
66
|
members?: MemberOverrides;
|
|
67
|
+
anonymous?: boolean;
|
|
62
68
|
}
|
|
63
69
|
export interface CreateRoomRequest {
|
|
64
70
|
actor: TaskRoomActor;
|
|
@@ -68,13 +74,18 @@ export interface CreateRoomRequest {
|
|
|
68
74
|
brief?: string;
|
|
69
75
|
briefFile?: string;
|
|
70
76
|
members?: MemberOverrides;
|
|
77
|
+
anonymous?: boolean;
|
|
71
78
|
}
|
|
79
|
+
export declare function resolveRoomLaunchPolicy(template: TemplateSnapshot | undefined, override: boolean | undefined): RoomLaunchPolicy;
|
|
72
80
|
export interface TaskRoomServiceDeps {
|
|
73
81
|
loadConfiguration?(path?: string): FleetConfig;
|
|
74
82
|
cowork?(config: FleetConfig): CoworkAdapter;
|
|
75
83
|
binPath?(): string;
|
|
76
84
|
provisionMembers?: typeof provisionMembers;
|
|
77
85
|
moveTaskToList?: typeof moveTaskToList;
|
|
86
|
+
launchDeletionWorker?(taskId: string): Promise<void>;
|
|
87
|
+
sleep?(ms: number): Promise<void>;
|
|
88
|
+
now?(): number;
|
|
78
89
|
}
|
|
79
90
|
/** Exact extraction of the previously CLI-owned task create/start behavior. */
|
|
80
91
|
export declare class TaskRoomApplicationService {
|
|
@@ -89,15 +100,18 @@ export declare class TaskRoomApplicationService {
|
|
|
89
100
|
taskId: string;
|
|
90
101
|
template?: string;
|
|
91
102
|
members?: MemberOverrides;
|
|
103
|
+
anonymous?: boolean;
|
|
92
104
|
}): Promise<TaskRecord>;
|
|
93
105
|
listTasks(filter?: {
|
|
94
106
|
state?: TaskState | TaskState[];
|
|
95
107
|
list?: string;
|
|
108
|
+
includeDeleting?: boolean;
|
|
96
109
|
}): TaskRecord[];
|
|
97
110
|
listTaskLists(): TaskListRecord[];
|
|
98
111
|
groupedTasks(filter?: {
|
|
99
112
|
state?: TaskState | TaskState[];
|
|
100
113
|
list?: string;
|
|
114
|
+
includeDeleting?: boolean;
|
|
101
115
|
}): Array<{
|
|
102
116
|
list: TaskListRecord;
|
|
103
117
|
tasks: TaskRecord[];
|
|
@@ -142,10 +156,40 @@ export declare class TaskRoomApplicationService {
|
|
|
142
156
|
actor: TaskRoomActor;
|
|
143
157
|
taskId: string;
|
|
144
158
|
}): TaskRecord;
|
|
145
|
-
|
|
159
|
+
private deletionActor;
|
|
160
|
+
/** Accept a permanent deletion in any lifecycle state; audit at acceptance. */
|
|
161
|
+
requestTaskDeletion(input: {
|
|
146
162
|
actor: TaskRoomActor;
|
|
147
163
|
taskId: string;
|
|
148
|
-
}):
|
|
164
|
+
}): Promise<TaskDeletionAcceptance>;
|
|
165
|
+
/** Worker entry: converge an accepted deletion; Cowork is resolved lazily. */
|
|
166
|
+
settleTaskDeletion(input: {
|
|
167
|
+
actor: {
|
|
168
|
+
kind: 'internal_worker';
|
|
169
|
+
surface: 'cli';
|
|
170
|
+
};
|
|
171
|
+
taskId: string;
|
|
172
|
+
}): Promise<TaskDeletionSettleResult>;
|
|
173
|
+
/**
|
|
174
|
+
* Launch the external deletion worker outside the caller's lifecycle and
|
|
175
|
+
* wait boundedly for physical absence. Timeouts and launch failures report
|
|
176
|
+
* a pending, recoverable state — never success. A concurrent worker that
|
|
177
|
+
* already removed the record reads as settled.
|
|
178
|
+
*/
|
|
179
|
+
launchTaskDeletionWorker(input: {
|
|
180
|
+
taskId: string;
|
|
181
|
+
waitMs?: number;
|
|
182
|
+
}): Promise<{
|
|
183
|
+
deleted: boolean;
|
|
184
|
+
pending: boolean;
|
|
185
|
+
error?: string;
|
|
186
|
+
}>;
|
|
187
|
+
recordDeletionError(input: {
|
|
188
|
+
actor: TaskRoomActor;
|
|
189
|
+
taskId: string;
|
|
190
|
+
error: string;
|
|
191
|
+
recoveryHint: string;
|
|
192
|
+
}): Promise<TaskRecord>;
|
|
149
193
|
completeTask(input: {
|
|
150
194
|
actor: TaskRoomActor;
|
|
151
195
|
taskId: string;
|
|
@@ -202,6 +246,7 @@ export declare class TaskRoomApplicationService {
|
|
|
202
246
|
identity_cid: string;
|
|
203
247
|
room_name: string;
|
|
204
248
|
state: "provisioning" | "active" | "closing" | "closed";
|
|
249
|
+
anonymous: boolean;
|
|
205
250
|
seats: import("../rooms-tasks/cowork-adapter.js").CoworkSeatInfo[];
|
|
206
251
|
goal?: string;
|
|
207
252
|
briefing?: string;
|
|
@@ -258,6 +303,7 @@ export declare class TaskRoomApplicationService {
|
|
|
258
303
|
taskId: string;
|
|
259
304
|
template?: string;
|
|
260
305
|
members?: MemberOverrides;
|
|
306
|
+
anonymous?: boolean;
|
|
261
307
|
}): Promise<{
|
|
262
308
|
task: TaskRecord;
|
|
263
309
|
status: 'ready' | 'already_active';
|
|
@@ -4,15 +4,18 @@ import { ConfigError, loadConfig } from '../config.js';
|
|
|
4
4
|
import { CoworkProtocolError, CoworkUnavailableError, createCoworkAdapter, } from '../rooms-tasks/cowork-adapter.js';
|
|
5
5
|
import { getBinPath, provisionMembers } from '../rooms-tasks/provision.js';
|
|
6
6
|
import { activateRoom, advanceSaga, createRoomRecord, getRoomRecord, listRoomRecords, setOwnerSeat, setSagaError, } from '../rooms-tasks/room-state.js';
|
|
7
|
-
import { activateTask, blockTask as persistBlockTask, createTask as persistTask,
|
|
7
|
+
import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, } from '../rooms-tasks/task-state.js';
|
|
8
8
|
import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
|
|
9
9
|
import { hashTemplate, listTemplates, resolveTemplate, sealTemplateSnapshot, snapshotTemplate } from '../rooms-tasks/templates.js';
|
|
10
10
|
import { acquireLaunchSnapshotLock, releaseLaunchSnapshot } from '../rooms-tasks/launch-snapshot.js';
|
|
11
11
|
import { hashMemberOverrides, prepareExecutionPlan } from '../rooms-tasks/member-overrides.js';
|
|
12
12
|
import { recordFleetAuditPresentation } from '../fleet-command-audit.js';
|
|
13
13
|
import { acceptManagedRoomClose, deleteLegacyClosedRooms, deleteManagedRoom, recordManagedRoomCloseError, } from '../rooms-tasks/close.js';
|
|
14
|
-
import { acceptTaskTerminalIntent, recordTaskTerminalIntentError, settleTaskTerminalIntent, } from '../rooms-tasks/terminal.js';
|
|
15
|
-
import {
|
|
14
|
+
import { acceptTaskTerminalIntent, recordTaskTerminalIntentError, settleTaskTerminalIntent, TASK_OPERATION_LOCK_STALE_MS, taskOperationLockPath, } from '../rooms-tasks/terminal.js';
|
|
15
|
+
import { acceptTaskDeletion, recordTaskDeletionError, settleTaskDeletion, } from '../rooms-tasks/deletion.js';
|
|
16
|
+
import { withFileLock } from '../atomic-file.js';
|
|
17
|
+
import { launchFleetWorker } from '../rooms-tasks/external-worker.js';
|
|
18
|
+
import { storedRoomLaunchPolicy, TASK_CANCELLABLE_STATES, TASK_TERMINAL_STATES } from '../rooms-tasks/types.js';
|
|
16
19
|
export class TaskRoomApplicationError extends Error {
|
|
17
20
|
code;
|
|
18
21
|
fields;
|
|
@@ -23,6 +26,10 @@ export class TaskRoomApplicationError extends Error {
|
|
|
23
26
|
this.name = 'TaskRoomApplicationError';
|
|
24
27
|
}
|
|
25
28
|
}
|
|
29
|
+
export function resolveRoomLaunchPolicy(template, override) {
|
|
30
|
+
const anonymous = override ?? template?.room?.anonymous ?? false;
|
|
31
|
+
return { anonymous };
|
|
32
|
+
}
|
|
26
33
|
function launchSelection(definition, key) {
|
|
27
34
|
const selected = definition?.[key];
|
|
28
35
|
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
@@ -48,6 +55,7 @@ function roomParticipants(room) {
|
|
|
48
55
|
brain: launchSelection(seat.launch?.agent_definition, 'brain'),
|
|
49
56
|
role: launchSelection(seat.launch?.agent_definition, 'role') ?? seat.cowork_role,
|
|
50
57
|
permissions: launchPermissions(seat.launch?.agent_definition),
|
|
58
|
+
...(seat.launch?.presentation ? { configuration: seat.launch.presentation } : {}),
|
|
51
59
|
}));
|
|
52
60
|
}
|
|
53
61
|
function taskAgents(task) {
|
|
@@ -57,7 +65,8 @@ function taskAgents(task) {
|
|
|
57
65
|
const seat = seats.get(member.name);
|
|
58
66
|
return { name: member.name, brain: launchSelection(seat?.launch?.agent_definition, 'brain'),
|
|
59
67
|
role: launchSelection(seat?.launch?.agent_definition, 'role') ?? member.cowork_role,
|
|
60
|
-
permissions: launchPermissions(seat?.launch?.agent_definition)
|
|
68
|
+
permissions: launchPermissions(seat?.launch?.agent_definition),
|
|
69
|
+
...(seat?.launch?.presentation ? { configuration: seat.launch.presentation } : {}) };
|
|
61
70
|
});
|
|
62
71
|
}
|
|
63
72
|
function roomFailureEventId(room) {
|
|
@@ -76,6 +85,9 @@ export class TaskRoomApplicationService {
|
|
|
76
85
|
async createTask(request) {
|
|
77
86
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
78
87
|
let template = this.createTemplate(cfg, request.template, request.noRoom);
|
|
88
|
+
if (request.noRoom && request.anonymous !== undefined)
|
|
89
|
+
throw new ConfigError('--anonymous/--no-anonymous cannot be combined with --no-room');
|
|
90
|
+
const roomPolicy = resolveRoomLaunchPolicy(template, request.anonymous);
|
|
79
91
|
let launchDefinitions;
|
|
80
92
|
let executionPlan;
|
|
81
93
|
let preparedPlan;
|
|
@@ -92,7 +104,8 @@ export class TaskRoomApplicationService {
|
|
|
92
104
|
if (preparedPlan) {
|
|
93
105
|
template = sealTemplateSnapshot(preparedPlan.snapshot, cfg.agentTemplates ?? {}, preparedPlan.launchDefinitions);
|
|
94
106
|
sealedHash = template.launch_snapshot_hash;
|
|
95
|
-
executionPlan = { schema_version: 1, snapshot: template,
|
|
107
|
+
executionPlan = { schema_version: 1, snapshot: template, room_policy: roomPolicy,
|
|
108
|
+
overrides: preparedPlan.overrides,
|
|
96
109
|
overrides_hash: preparedPlan.overridesHash,
|
|
97
110
|
plan_hash: preparedPlan.planHash };
|
|
98
111
|
}
|
|
@@ -124,7 +137,7 @@ export class TaskRoomApplicationService {
|
|
|
124
137
|
try {
|
|
125
138
|
const room = await this.provisionRoom(cfg, task, template, created => {
|
|
126
139
|
task = updateTaskRoom(task.task_id, created.room_id, created.room_identity_cid);
|
|
127
|
-
}, launchDefinitions);
|
|
140
|
+
}, launchDefinitions, roomPolicy);
|
|
128
141
|
task = readTask(task.task_id);
|
|
129
142
|
if (task.state === 'active' && room.state === 'active')
|
|
130
143
|
recordFleetAuditPresentation({ kind: 'task', operation: 'work', id: task.task_id,
|
|
@@ -159,20 +172,22 @@ export class TaskRoomApplicationService {
|
|
|
159
172
|
if (request.members && Object.keys(request.members).length) {
|
|
160
173
|
const prepared = prepareExecutionPlan(definition, cfg, request.members);
|
|
161
174
|
template = prepared.snapshot;
|
|
162
|
-
|
|
175
|
+
const roomPolicy = resolveRoomLaunchPolicy(template, request.anonymous);
|
|
176
|
+
return this.provisionRoom(cfg, { title: request.name, brief, goal: request.goal }, template, () => { }, prepared.launchDefinitions, roomPolicy);
|
|
163
177
|
}
|
|
164
178
|
template = snapshotTemplate(definition, cfg.agentTemplates);
|
|
165
179
|
}
|
|
180
|
+
const roomPolicy = resolveRoomLaunchPolicy(template, request.anonymous);
|
|
166
181
|
return this.provisionRoom(cfg, {
|
|
167
182
|
title: request.name, brief, goal: request.goal,
|
|
168
|
-
}, template, () => { });
|
|
183
|
+
}, template, () => { }, undefined, roomPolicy);
|
|
169
184
|
}
|
|
170
185
|
async startTask(input) {
|
|
171
186
|
return (await this.ensureTaskWork(input)).task;
|
|
172
187
|
}
|
|
173
188
|
listTasks(filter) {
|
|
174
189
|
const listId = filter?.list === undefined ? undefined : resolveTaskList(filter.list).list_id;
|
|
175
|
-
return readTasks({ state: filter?.state, listId });
|
|
190
|
+
return readTasks({ state: filter?.state, listId, includeDeleting: filter?.includeDeleting });
|
|
176
191
|
}
|
|
177
192
|
listTaskLists() { return readTaskLists(); }
|
|
178
193
|
groupedTasks(filter) {
|
|
@@ -225,8 +240,93 @@ export class TaskRoomApplicationService {
|
|
|
225
240
|
reviewTask(input) {
|
|
226
241
|
return persistReviewTask(input.taskId);
|
|
227
242
|
}
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
deletionActor(actor) {
|
|
244
|
+
if (actor.kind === 'local_control')
|
|
245
|
+
return { kind: 'local_control', surface: actor.surface };
|
|
246
|
+
if (actor.kind === 'authenticated_owner')
|
|
247
|
+
return { kind: 'authenticated_owner', surface: 'messenger', cid: actor.cid };
|
|
248
|
+
throw new Error('internal workers cannot originate a task deletion acceptance');
|
|
249
|
+
}
|
|
250
|
+
/** Accept a permanent deletion in any lifecycle state; audit at acceptance. */
|
|
251
|
+
async requestTaskDeletion(input) {
|
|
252
|
+
const result = await acceptTaskDeletion(input.taskId, this.deletionActor(input.actor));
|
|
253
|
+
if (result.status === 'accepted') {
|
|
254
|
+
const task = result.task;
|
|
255
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'delete', id: task.task_id,
|
|
256
|
+
title: task.title, previousState: task.state, newState: 'deleting',
|
|
257
|
+
revision: task.deletion?.accepted_at ?? task.created_at, list: task.list_name ?? 'default',
|
|
258
|
+
roomId: task.room_id,
|
|
259
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
260
|
+
agents: [] });
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
/** Worker entry: converge an accepted deletion; Cowork is resolved lazily. */
|
|
265
|
+
async settleTaskDeletion(input) {
|
|
266
|
+
const result = await settleTaskDeletion({
|
|
267
|
+
taskId: input.taskId,
|
|
268
|
+
cowork: () => {
|
|
269
|
+
// Config resolves only when room work exists: a no-room deletion must
|
|
270
|
+
// settle even with missing or invalid rooms configuration.
|
|
271
|
+
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
272
|
+
if (!cfg.rooms)
|
|
273
|
+
throw new ConfigError('rooms: configuration is required before deleting task rooms');
|
|
274
|
+
return this.deps.cowork ? this.deps.cowork(cfg)
|
|
275
|
+
: createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
if (result.deleted && result.previous_state) {
|
|
279
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'delete', id: result.task_id,
|
|
280
|
+
title: result.title, previousState: result.previous_state, newState: 'deleted',
|
|
281
|
+
revision: new Date().toISOString(), list: 'default', roomId: undefined,
|
|
282
|
+
template: undefined, agents: [] });
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Launch the external deletion worker outside the caller's lifecycle and
|
|
288
|
+
* wait boundedly for physical absence. Timeouts and launch failures report
|
|
289
|
+
* a pending, recoverable state — never success. A concurrent worker that
|
|
290
|
+
* already removed the record reads as settled.
|
|
291
|
+
*/
|
|
292
|
+
async launchTaskDeletionWorker(input) {
|
|
293
|
+
const launch = this.deps.launchDeletionWorker
|
|
294
|
+
?? ((taskId) => launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, this.configurationPath));
|
|
295
|
+
const errorAtBefore = (() => {
|
|
296
|
+
try {
|
|
297
|
+
return getDeletingTask(input.taskId).deletion?.error_at;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
try {
|
|
304
|
+
await launch(input.taskId);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
308
|
+
await recordTaskDeletionError(input.taskId, message, `External delete worker failed to start. Repeat the delete request for ${input.taskId}.`).catch(() => { });
|
|
309
|
+
return { deleted: false, pending: true, error: message };
|
|
310
|
+
}
|
|
311
|
+
const now = this.deps.now ?? Date.now;
|
|
312
|
+
const sleep = this.deps.sleep
|
|
313
|
+
?? ((ms) => new Promise(resolve => { setTimeout(resolve, ms); }));
|
|
314
|
+
const deadline = now() + (input.waitMs ?? 10_000);
|
|
315
|
+
while (now() < deadline) {
|
|
316
|
+
if (taskDeletionState(input.taskId) === 'absent')
|
|
317
|
+
return { deleted: true, pending: false };
|
|
318
|
+
try {
|
|
319
|
+
const current = getDeletingTask(input.taskId).deletion;
|
|
320
|
+
if (current?.error_at !== errorAtBefore && current?.error)
|
|
321
|
+
return { deleted: false, pending: true, error: current.error };
|
|
322
|
+
}
|
|
323
|
+
catch { /* re-checked as absence on the next iteration */ }
|
|
324
|
+
await sleep(100);
|
|
325
|
+
}
|
|
326
|
+
return { deleted: false, pending: true };
|
|
327
|
+
}
|
|
328
|
+
recordDeletionError(input) {
|
|
329
|
+
return recordTaskDeletionError(input.taskId, input.error, input.recoveryHint);
|
|
230
330
|
}
|
|
231
331
|
async completeTask(input) {
|
|
232
332
|
const task = readTask(input.taskId);
|
|
@@ -265,10 +365,22 @@ export class TaskRoomApplicationService {
|
|
|
265
365
|
return recordTaskTerminalIntentError(input.taskId, input.error, input.recoveryHint);
|
|
266
366
|
}
|
|
267
367
|
async beginTaskRecovery(input) {
|
|
368
|
+
// The deletion-vs-terminal routing decision serializes on the common
|
|
369
|
+
// task-operation lock and must not require configuration: recovery of a
|
|
370
|
+
// deletion-pending task continues the deletion and never resurrects.
|
|
371
|
+
const routed = await withFileLock(taskOperationLockPath(input.taskId), () => {
|
|
372
|
+
const task = readTask(input.taskId);
|
|
373
|
+
if (task.deletion?.status === 'pending')
|
|
374
|
+
return 'deletion';
|
|
375
|
+
if (task.terminal_intent?.status === 'pending')
|
|
376
|
+
return 'terminal';
|
|
377
|
+
return 'none';
|
|
378
|
+
}, {}, TASK_OPERATION_LOCK_STALE_MS);
|
|
379
|
+
if (routed === 'deletion')
|
|
380
|
+
return { kind: 'deletion_worker_required', taskId: input.taskId };
|
|
268
381
|
const config = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
269
382
|
this.recovery = { taskId: input.taskId, config };
|
|
270
|
-
|
|
271
|
-
if (task.terminal_intent?.status === 'pending')
|
|
383
|
+
if (routed === 'terminal')
|
|
272
384
|
return { kind: 'terminal_worker_required', taskId: input.taskId };
|
|
273
385
|
return { kind: 'final', result: await this.continueTaskRecovery({
|
|
274
386
|
actor: input.actor, taskId: input.taskId, terminalTimedOut: false,
|
|
@@ -430,6 +542,14 @@ export class TaskRoomApplicationService {
|
|
|
430
542
|
return { kind: 'deletion_worker_required', roomId: input.roomId };
|
|
431
543
|
const room = await adapter.recoverRoom(input.roomId);
|
|
432
544
|
orchestration = getRoomRecord(input.roomId);
|
|
545
|
+
if (orchestration) {
|
|
546
|
+
const policy = storedRoomLaunchPolicy(orchestration.room_policy);
|
|
547
|
+
if ((room.anonymous ?? false) !== policy.anonymous) {
|
|
548
|
+
const error = `Cowork anonymity (${String(room.anonymous ?? false)}) does not match Fleet's durable Room policy (${String(policy.anonymous)})`;
|
|
549
|
+
setSagaError(input.roomId, error, 'Do not respawn members. Repair or upgrade Cowork, then recover the Room without changing its policy.', 'waiting_cowork');
|
|
550
|
+
throw new Error(error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
433
553
|
if (orchestration && !orchestration.owner_seat_cid
|
|
434
554
|
&& (orchestration.provisioning_detail === 'waiting_owner_invite'
|
|
435
555
|
|| orchestration.provisioning_detail === 'owner_cid_mismatch')) {
|
|
@@ -499,6 +619,10 @@ export class TaskRoomApplicationService {
|
|
|
499
619
|
async ensureTaskWork(input) {
|
|
500
620
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
501
621
|
let task = readTask(input.taskId);
|
|
622
|
+
const recordedRoom = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
623
|
+
const recordedPolicy = storedRoomLaunchPolicy(recordedRoom?.room_policy ?? task.execution_plan?.room_policy);
|
|
624
|
+
if (task.room_id && input.anonymous !== undefined && input.anonymous !== recordedPolicy.anonymous)
|
|
625
|
+
throw new TaskRoomApplicationError('template_mismatch', 'anonymous override does not match the existing Room launch policy', { room: task.room_id });
|
|
502
626
|
if (TASK_TERMINAL_STATES.includes(task.state))
|
|
503
627
|
throw new TaskRoomApplicationError('task_terminal', 'task terminal', { task: input.taskId, state: task.state });
|
|
504
628
|
if (task.state === 'active' && task.room_id) {
|
|
@@ -542,6 +666,11 @@ export class TaskRoomApplicationService {
|
|
|
542
666
|
let preparedPlan = !durable && definition
|
|
543
667
|
? prepareExecutionPlan(definition, cfg, input.members ?? {}) : undefined;
|
|
544
668
|
let snapshot = durable ?? preparedPlan.snapshot;
|
|
669
|
+
const roomPolicy = input.anonymous !== undefined
|
|
670
|
+
? resolveRoomLaunchPolicy(snapshot, input.anonymous)
|
|
671
|
+
: task.execution_plan
|
|
672
|
+
? storedRoomLaunchPolicy(task.execution_plan.room_policy)
|
|
673
|
+
: resolveRoomLaunchPolicy(snapshot, undefined);
|
|
545
674
|
if (preparedPlan)
|
|
546
675
|
launchDefinitions = preparedPlan.launchDefinitions;
|
|
547
676
|
if (input.members && Object.keys(input.members).length && !storedOverridesMatch) {
|
|
@@ -584,6 +713,7 @@ export class TaskRoomApplicationService {
|
|
|
584
713
|
try {
|
|
585
714
|
sealed = sealTemplateSnapshot(snapshot, cfg.agentTemplates ?? {}, launchDefinitions);
|
|
586
715
|
task = updateTaskExecutionPlan(task.task_id, { schema_version: 1, snapshot: sealed,
|
|
716
|
+
room_policy: roomPolicy,
|
|
587
717
|
overrides: preparedPlan.overrides, overrides_hash: preparedPlan.overridesHash,
|
|
588
718
|
plan_hash: preparedPlan.planHash });
|
|
589
719
|
snapshot = sealed;
|
|
@@ -596,6 +726,9 @@ export class TaskRoomApplicationService {
|
|
|
596
726
|
}
|
|
597
727
|
unlock();
|
|
598
728
|
}
|
|
729
|
+
else if (!task.room_id && task.execution_plan && input.anonymous !== undefined) {
|
|
730
|
+
task = updateTaskExecutionPlan(task.task_id, { ...task.execution_plan, room_policy: roomPolicy });
|
|
731
|
+
}
|
|
599
732
|
else if (!task.template || task.template.name !== snapshot.name || task.template.content_hash !== snapshot.content_hash)
|
|
600
733
|
task = updateTaskTemplate(task.task_id, { name: snapshot.name, version: snapshot.version, content_hash: snapshot.content_hash });
|
|
601
734
|
if (task.state === 'backlog') {
|
|
@@ -609,7 +742,7 @@ export class TaskRoomApplicationService {
|
|
|
609
742
|
try {
|
|
610
743
|
await this.provisionRoom(cfg, task, snapshot, created => {
|
|
611
744
|
task = updateTaskRoom(task.task_id, created.room_id, created.room_identity_cid);
|
|
612
|
-
}, launchDefinitions);
|
|
745
|
+
}, launchDefinitions, roomPolicy);
|
|
613
746
|
task = readTask(task.task_id);
|
|
614
747
|
}
|
|
615
748
|
catch (error) {
|
|
@@ -676,7 +809,7 @@ export class TaskRoomApplicationService {
|
|
|
676
809
|
throw new TaskRoomApplicationError('task_template_drift', `task template snapshot no longer matches ${ref.name}@${ref.version}`);
|
|
677
810
|
return snapshot;
|
|
678
811
|
}
|
|
679
|
-
async provisionRoom(cfg, task, template, onCreated, launchDefinitions) {
|
|
812
|
+
async provisionRoom(cfg, task, template, onCreated, launchDefinitions, policy = resolveRoomLaunchPolicy(template, undefined)) {
|
|
680
813
|
const rooms = cfg.rooms;
|
|
681
814
|
if (!rooms)
|
|
682
815
|
throw new ConfigError('rooms: configuration is required');
|
|
@@ -690,21 +823,37 @@ export class TaskRoomApplicationService {
|
|
|
690
823
|
})();
|
|
691
824
|
const unlockSnapshot = template ? acquireLaunchSnapshotLock() : undefined;
|
|
692
825
|
let launchTemplate;
|
|
693
|
-
let created;
|
|
694
826
|
let room;
|
|
695
827
|
try {
|
|
696
828
|
launchTemplate = template ? (template.launch_snapshot_hash ? template
|
|
697
829
|
: sealTemplateSnapshot(template, cfg.agentTemplates ?? {}, launchDefinitions)) : undefined;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
830
|
+
// Room publication window: for a task-bound room, hold the common
|
|
831
|
+
// task-operation lock across remote creation, local record creation, and
|
|
832
|
+
// the task link, so an accepted deletion linearizes strictly before
|
|
833
|
+
// (bounded task_deleting abort) or strictly after (record visible to the
|
|
834
|
+
// deletion scan). Lock order stays launch-snapshot → task-operation.
|
|
835
|
+
const publishRoom = async () => {
|
|
836
|
+
if (task.task_id) {
|
|
837
|
+
const fresh = readTask(task.task_id);
|
|
838
|
+
if (fresh.deletion?.status === 'pending')
|
|
839
|
+
throw new TaskRoomApplicationError('task_deleting', `task ${task.task_id} is pending deletion`, { task: task.task_id });
|
|
840
|
+
}
|
|
841
|
+
const created = await cowork.createRoom({
|
|
842
|
+
room_name: task.title, goal: task.goal?.trim() || task.title,
|
|
843
|
+
briefing: task.brief?.trim() || launchTemplate?.contract?.trim() || task.goal?.trim() || task.title,
|
|
844
|
+
quiet_membership: launchTemplate?.room?.quiet_membership,
|
|
845
|
+
anonymous: policy.anonymous,
|
|
846
|
+
});
|
|
847
|
+
const record = createRoomRecord({
|
|
848
|
+
room_id: created.room_id, room_name: task.title, room_identity_cid: created.identity_cid,
|
|
849
|
+
task_id: task.task_id, template_snapshot: launchTemplate, room_policy: policy,
|
|
850
|
+
});
|
|
851
|
+
onCreated(record);
|
|
852
|
+
return record;
|
|
853
|
+
};
|
|
854
|
+
room = task.task_id
|
|
855
|
+
? await withFileLock(taskOperationLockPath(task.task_id), publishRoom, {}, TASK_OPERATION_LOCK_STALE_MS)
|
|
856
|
+
: await publishRoom();
|
|
708
857
|
}
|
|
709
858
|
catch (error) {
|
|
710
859
|
unlockSnapshot?.();
|
|
@@ -718,7 +867,6 @@ export class TaskRoomApplicationService {
|
|
|
718
867
|
revision: room.created_at, taskId: room.task_id,
|
|
719
868
|
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
720
869
|
participants: [] });
|
|
721
|
-
onCreated(room);
|
|
722
870
|
room = advanceSaga(room.room_id, 'create_room', 1);
|
|
723
871
|
if (attachOwner) {
|
|
724
872
|
try {
|
package/dist/briefing.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { userInfo } from 'node:os';
|
|
2
2
|
import { oversightTaxonomyLines } from './session/control.js';
|
|
3
|
-
function temporaryIdentityBootstrap(id, v) {
|
|
3
|
+
function temporaryIdentityBootstrap(id, v, anonymous = false) {
|
|
4
4
|
return [
|
|
5
5
|
`2. CREATE your ours identity now: call **${v.temporaryCreateTool}** through ours MCP`,
|
|
6
|
-
` with the exact assigned name "${id}". The ours connector owns its cleanup when this`,
|
|
6
|
+
` with the exact assigned name "${id}"${anonymous ? ' and expose_local=false' : ''}. The ours connector owns its cleanup when this`,
|
|
7
7
|
' connector session lifecycle ends.',
|
|
8
8
|
' Do not inspect, preserve, adopt, or use any pre-existing or persistent identity.',
|
|
9
9
|
' On a collision, missing tool, or creation error, STOP and',
|
|
@@ -24,7 +24,7 @@ function generateRoomMemberBriefing(role, v, opts, prefix) {
|
|
|
24
24
|
L.push('', '### One-time room invite', '', '```text', startup.invite, '```');
|
|
25
25
|
L.push('', '## Do these NOW, in order');
|
|
26
26
|
L.push(`1. ${v.launchNote(role.name)}`);
|
|
27
|
-
L.push(...temporaryIdentityBootstrap(startup.identity_name, v));
|
|
27
|
+
L.push(...temporaryIdentityBootstrap(startup.identity_name, v, startup.anonymous));
|
|
28
28
|
L.push('3. Call **add_contact** through ours MCP with the exact one-time invite above. Confirm');
|
|
29
29
|
L.push(` that it resolves to room CID \`${startup.room_identity_cid}\`. The contact may remain`);
|
|
30
30
|
L.push(' pending while the room finishes its asynchronous verification.');
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.1.0-nightly.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "1.1.0-nightly.14",
|
|
3
|
+
"buildId": "3651cb00af5a",
|
|
4
|
+
"commit": "974e9150cbb9d24f92412866de7a53a0cd6fde8f",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-
|
|
6
|
+
"builtAt": "2026-09-01T08:58:08.364Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|
package/dist/config.d.ts
CHANGED
|
@@ -175,6 +175,7 @@ export interface RoomMemberStartup {
|
|
|
175
175
|
role: string;
|
|
176
176
|
task: string;
|
|
177
177
|
owner_seat_cid: string | null;
|
|
178
|
+
anonymous?: boolean;
|
|
178
179
|
}
|
|
179
180
|
export interface ResolvedRole extends Omit<RoleConfig, 'model' | 'owner_channel' | 'worklog'> {
|
|
180
181
|
name: string;
|