@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.20
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 +169 -54
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +6 -25
- package/dist/application/role-creation-service.js +34 -72
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +58 -6
- package/dist/application/task-room-service.js +426 -78
- package/dist/briefing.js +74 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +227 -73
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +67 -2
- package/dist/config.js +260 -30
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +171 -66
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +165 -0
- package/dist/fleet-command-audit.js +543 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/fleet-proxy.js +30 -8
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +629 -0
- package/dist/lifecycle-summary.d.ts +118 -0
- package/dist/lifecycle-summary.js +161 -0
- package/dist/loops/config.js +3 -2
- package/dist/model-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +58 -0
- package/dist/owner-channel/channel.js +321 -42
- package/dist/owner-channel/commands.d.ts +6 -1
- package/dist/owner-channel/commands.js +34 -19
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +414 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +4 -0
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +503 -75
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +20 -4
- package/dist/rooms-tasks/config.js +16 -14
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +176 -0
- package/dist/rooms-tasks/member-overrides.d.ts +27 -0
- package/dist/rooms-tasks/member-overrides.js +170 -0
- package/dist/rooms-tasks/provision.js +179 -49
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +94 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +9 -2
- package/dist/runner.js +107 -40
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +38 -0
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +6 -31
- package/dist/spawn.js +57 -234
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web/topology-promote.js +8 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +3 -2
- package/presets/brain-catalog.json +16 -0
- package/presets/fleet/agent_templates/Critic.yaml +4 -0
- package/presets/fleet/agent_templates/Developer.yaml +4 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +19 -0
- package/presets/fleet/roles/Developer.yaml +19 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
- package/presets/fleet/room_templates/pair.yaml +10 -0
- package/presets/fleet/room_templates/single.yaml +7 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
3
4
|
import { parse } from 'yaml';
|
|
4
5
|
import { advanceSaga, setSagaError, updateMemberSeats, updateMemberStartup, activateRoom, getRoomRecord, } from './room-state.js';
|
|
5
6
|
import { activateTask, updateTaskMembers, blockTask, unblockTask, getTask, } from './task-state.js';
|
|
7
|
+
import { storedRoomLaunchPolicy } from './types.js';
|
|
6
8
|
import { spawnTemp } from '../spawn.js';
|
|
7
|
-
import {
|
|
9
|
+
import { effectivePermissionMode } from '../permissions.js';
|
|
10
|
+
import { selectionOrigin, summarizeResolvedLaunch, } from '../lifecycle-summary.js';
|
|
11
|
+
import { canonicalJson } from '../canonical-json.js';
|
|
12
|
+
import { readLaunchSnapshot, redactLaunchDefinition } from './launch-snapshot.js';
|
|
8
13
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from '../fleet-proxy.js';
|
|
9
14
|
import { controlRequest } from '../session/control.js';
|
|
10
15
|
import { SessionControlError } from '../session/types.js';
|
|
11
16
|
import { closeManagedRoom } from './close.js';
|
|
17
|
+
import { withFileLock } from '../atomic-file.js';
|
|
18
|
+
import { TASK_OPERATION_LOCK_STALE_MS, taskOperationLockPath } from './terminal.js';
|
|
19
|
+
import { TaskStateError, taskDeletionState } from './task-state.js';
|
|
12
20
|
import { buildRoomMemberTask, sha256Text } from './member-startup.js';
|
|
13
21
|
import { agentDir } from '../paths.js';
|
|
14
22
|
import { readProvenance } from '../creation.js';
|
|
15
23
|
import { readTempSupervisor, secureStoppedTempArchive, tempArchiveForCreationAction, tempSupervisorLiveness, } from '../temp-lifecycle.js';
|
|
24
|
+
import { recordFleetAuditPresentation } from '../fleet-command-audit.js';
|
|
16
25
|
export function getBinPath() {
|
|
17
26
|
try {
|
|
18
27
|
return realpathSync(process.argv[1]);
|
|
@@ -21,6 +30,23 @@ export function getBinPath() {
|
|
|
21
30
|
return process.argv[1];
|
|
22
31
|
}
|
|
23
32
|
}
|
|
33
|
+
function selectionSummary(selection) {
|
|
34
|
+
if ('ref' in selection)
|
|
35
|
+
return `ref:${selection.ref} (reference)`;
|
|
36
|
+
return `inline:sha256:${createHash('sha256').update(canonicalJson(selection.inline)).digest('hex').slice(0, 16)} (inline)`;
|
|
37
|
+
}
|
|
38
|
+
function permissionSummary(definition) {
|
|
39
|
+
const permissions = definition.permissions;
|
|
40
|
+
if (!permissions)
|
|
41
|
+
return undefined;
|
|
42
|
+
return `approval=${permissions.approval},filesystem=${permissions.filesystem},unattended=${permissions.unattended}`;
|
|
43
|
+
}
|
|
44
|
+
function launchDefinition(definition) {
|
|
45
|
+
return {
|
|
46
|
+
projection: redactLaunchDefinition(definition),
|
|
47
|
+
fingerprint: createHash('sha256').update(canonicalJson(definition)).digest('hex'),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
24
50
|
async function spawnRoomMember(options, binPath) {
|
|
25
51
|
const stateDir = process.env[FLEET_PROXY_STATE_DIR_ENV];
|
|
26
52
|
if (!stateDir)
|
|
@@ -41,8 +67,27 @@ async function spawnRoomMember(options, binPath) {
|
|
|
41
67
|
statePath: result.statePath,
|
|
42
68
|
creationActionId: result.creationActionId,
|
|
43
69
|
callerRole: result.caller,
|
|
70
|
+
...(result.configuration ? { configuration: result.configuration } : {}),
|
|
44
71
|
};
|
|
45
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Launch-boundary capture: the exact ResolvedRole the spawn persisted,
|
|
75
|
+
* whitelisted into the operator-facing presentation. Capture is required for
|
|
76
|
+
* every launched room member — launchMatches has already proved role.yaml
|
|
77
|
+
* readable, and effectivePermissionMode is total over registered harnesses —
|
|
78
|
+
* so a failure here is a capture defect and must surface, not degrade to the
|
|
79
|
+
* legacy rendering.
|
|
80
|
+
*/
|
|
81
|
+
function presentationFromStatePath(statePath, settings, coworkRole) {
|
|
82
|
+
const resolved = parse(readFileSync(join(statePath, 'role.yaml'), 'utf8'));
|
|
83
|
+
return summarizeResolvedLaunch(resolved, {
|
|
84
|
+
role: selectionOrigin(settings.definition.role),
|
|
85
|
+
brain: selectionOrigin(settings.definition.brain),
|
|
86
|
+
template: settings.template,
|
|
87
|
+
permissionMode: effectivePermissionMode(resolved),
|
|
88
|
+
missionFallback: coworkRole,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
46
91
|
function shortId(id) { return id.slice(0, 8); }
|
|
47
92
|
function expandMembers(template, prefix) {
|
|
48
93
|
const result = [];
|
|
@@ -52,28 +97,30 @@ function expandMembers(template, prefix) {
|
|
|
52
97
|
name: `${prefix}-${slot.slot}-${i}`,
|
|
53
98
|
slot: slot.slot,
|
|
54
99
|
coworkRole: slot.role,
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
agentTemplate: slot.agent_template,
|
|
101
|
+
launchDefinitionId: slot.launch_definition_id ?? slot.agent_template,
|
|
102
|
+
agentTemplateHash: slot.agent_template_hash,
|
|
57
103
|
});
|
|
58
104
|
}
|
|
59
105
|
}
|
|
60
106
|
return result;
|
|
61
107
|
}
|
|
62
|
-
function settingsFor(member, cfg) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
108
|
+
function settingsFor(member, cfg, sealed) {
|
|
109
|
+
const definition = sealed
|
|
110
|
+
? sealed[member.launchDefinitionId]
|
|
111
|
+
: cfg.agentTemplates?.[member.agentTemplate];
|
|
112
|
+
if (!definition)
|
|
113
|
+
throw new Error(sealed
|
|
114
|
+
? `Sealed Agent definition '${member.launchDefinitionId}' not found`
|
|
115
|
+
: `Agent Template '${member.agentTemplate}' not found`);
|
|
116
|
+
const role = definition.role;
|
|
69
117
|
return {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
unattended: permissions?.unattended,
|
|
118
|
+
definition: structuredClone(definition),
|
|
119
|
+
...('inline' in role && typeof role.inline.persona === 'string'
|
|
120
|
+
? { persona: role.inline.persona } : {}),
|
|
121
|
+
template: member.agentTemplate,
|
|
122
|
+
templateHash: member.agentTemplateHash
|
|
123
|
+
?? createHash('sha256').update(canonicalJson(definition)).digest('hex'),
|
|
77
124
|
};
|
|
78
125
|
}
|
|
79
126
|
function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCid) {
|
|
@@ -96,7 +143,7 @@ function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCi
|
|
|
96
143
|
})),
|
|
97
144
|
});
|
|
98
145
|
}
|
|
99
|
-
function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid, expectedInviteId) {
|
|
146
|
+
function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid, expectedInviteId, anonymous = false) {
|
|
100
147
|
const provenance = readProvenance(dir);
|
|
101
148
|
if (provenance?.creationActionId !== actionId || provenance.role !== member.name)
|
|
102
149
|
return false;
|
|
@@ -104,13 +151,12 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
|
|
|
104
151
|
const role = parse(readFileSync(`${dir}/role.yaml`, 'utf8'));
|
|
105
152
|
const startup = role.roomMemberStartup;
|
|
106
153
|
return role.identity === member.name
|
|
107
|
-
&& typeof role.mission === 'string'
|
|
108
|
-
&& sha256Text(role.mission) === taskSha
|
|
109
154
|
&& startup?.room_id === roomId
|
|
110
155
|
&& startup.room_identity_cid === roomIdentityCid
|
|
111
156
|
&& startup.identity_name === member.name
|
|
112
157
|
&& startup.role === member.coworkRole
|
|
113
|
-
&& startup.
|
|
158
|
+
&& (startup.anonymous ?? false) === anonymous
|
|
159
|
+
&& sha256Text(startup.task ?? '') === taskSha
|
|
114
160
|
&& (expectedInviteId === undefined || startup.invite_id === expectedInviteId)
|
|
115
161
|
&& typeof startup.invite === 'string'
|
|
116
162
|
&& startup.invite.length > 0;
|
|
@@ -120,21 +166,22 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
|
|
|
120
166
|
}
|
|
121
167
|
}
|
|
122
168
|
async function retainRunningLaunch(input) {
|
|
123
|
-
const { provision, member, task, roomIdentityCid } = input;
|
|
169
|
+
const { provision, member, settings, task, roomIdentityCid } = input;
|
|
124
170
|
let seat = getRoomRecord(provision.roomId).member_seats
|
|
125
171
|
.find(candidate => candidate.role_name === member.name);
|
|
126
172
|
const dir = agentDir(member.name, true);
|
|
127
173
|
const taskSha = sha256Text(task);
|
|
174
|
+
const anonymous = storedRoomLaunchPolicy(getRoomRecord(provision.roomId)?.room_policy).anonymous;
|
|
128
175
|
if ((seat.launch?.state === 'intent' || seat.launch?.state === 'launched'
|
|
129
176
|
|| seat.launch?.state === 'failed') && existsSync(dir)) {
|
|
130
|
-
if (!seat.launch.action_id || !launchMatches(dir, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id)) {
|
|
177
|
+
if (!seat.launch.action_id || !launchMatches(dir, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous)) {
|
|
131
178
|
const provenance = readProvenance(dir);
|
|
132
179
|
const adoptable = (seat.launch.state === 'intent' || seat.launch.state === 'failed')
|
|
133
180
|
&& Boolean(seat.launch.caller_role)
|
|
134
181
|
&& provenance?.surface === 'agent'
|
|
135
182
|
&& provenance.callerRole === seat.launch.caller_role
|
|
136
183
|
&& typeof provenance.creationActionId === 'string'
|
|
137
|
-
&& launchMatches(dir, member, provenance.creationActionId, taskSha, provision.roomId, roomIdentityCid, seat.invite_id);
|
|
184
|
+
&& launchMatches(dir, member, provenance.creationActionId, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous);
|
|
138
185
|
if (!adoptable)
|
|
139
186
|
throw new Error(`existing launch for ${member.name} does not match its durable intent`);
|
|
140
187
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
@@ -152,9 +199,14 @@ async function retainRunningLaunch(input) {
|
|
|
152
199
|
throw new Error(`existing launch for ${member.name} has unknown liveness`);
|
|
153
200
|
const retainedLaunch = seat.launch;
|
|
154
201
|
if (live === 'running') {
|
|
202
|
+
// A crash between spawn and the seat update (or a pre-upgrade launch)
|
|
203
|
+
// can retain a running member without a captured presentation; backfill
|
|
204
|
+
// it from the same persisted resolved role the match was proved against.
|
|
205
|
+
const presentation = retainedLaunch.presentation
|
|
206
|
+
?? presentationFromStatePath(dir, settings, member.coworkRole);
|
|
155
207
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
156
208
|
...retainedLaunch, state: 'launched', launch_id: supervisor.launchId,
|
|
157
|
-
updated_at: new Date().toISOString(),
|
|
209
|
+
presentation, updated_at: new Date().toISOString(),
|
|
158
210
|
} });
|
|
159
211
|
return true;
|
|
160
212
|
}
|
|
@@ -169,7 +221,7 @@ async function retainRunningLaunch(input) {
|
|
|
169
221
|
if (!seat.launch.launch_id || !seat.launch.action_id)
|
|
170
222
|
throw new Error(`missing durable launch identity for disappeared ${member.name}`);
|
|
171
223
|
const archive = await secureStoppedTempArchive(member.name, seat.launch.launch_id);
|
|
172
|
-
if (!launchMatches(archive, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id)) {
|
|
224
|
+
if (!launchMatches(archive, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous)) {
|
|
173
225
|
throw new Error(`archive for disappeared ${member.name} does not match its durable intent`);
|
|
174
226
|
}
|
|
175
227
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
@@ -181,7 +233,7 @@ async function retainRunningLaunch(input) {
|
|
|
181
233
|
if (!seat.launch.action_id)
|
|
182
234
|
throw new Error(`missing action ID for disappeared launch intent ${member.name}`);
|
|
183
235
|
const archive = tempArchiveForCreationAction(member.name, seat.launch.action_id);
|
|
184
|
-
if (!archive || !launchMatches(archive.path, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id)) {
|
|
236
|
+
if (!archive || !launchMatches(archive.path, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous)) {
|
|
185
237
|
throw new Error(`launch intent for ${member.name} has no exact live or terminated archive evidence`);
|
|
186
238
|
}
|
|
187
239
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
@@ -191,7 +243,25 @@ async function retainRunningLaunch(input) {
|
|
|
191
243
|
}
|
|
192
244
|
return false;
|
|
193
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Member-launch publication window: for a task-bound room, the durable launch
|
|
248
|
+
* intent, spawn, and launch record publish under the common task-operation
|
|
249
|
+
* lock so an accepted deletion linearizes strictly before (bounded abort — no
|
|
250
|
+
* spawn happens) or strictly after (the seat's launch record is visible to
|
|
251
|
+
* deletion retirement). The lock is held per member only, never across
|
|
252
|
+
* seat-wait loops.
|
|
253
|
+
*/
|
|
194
254
|
async function launchMember(input) {
|
|
255
|
+
const taskId = input.provision.taskId;
|
|
256
|
+
if (!taskId)
|
|
257
|
+
return launchMemberUnlocked(input);
|
|
258
|
+
return withFileLock(taskOperationLockPath(taskId), () => {
|
|
259
|
+
if (taskDeletionState(taskId) !== 'none')
|
|
260
|
+
throw new Error(`task ${taskId} is pending deletion; aborting member launch for ${input.member.name}`);
|
|
261
|
+
return launchMemberUnlocked(input);
|
|
262
|
+
}, {}, TASK_OPERATION_LOCK_STALE_MS);
|
|
263
|
+
}
|
|
264
|
+
async function launchMemberUnlocked(input) {
|
|
195
265
|
const { provision, member, settings, startup } = input;
|
|
196
266
|
const seat = getRoomRecord(provision.roomId).member_seats
|
|
197
267
|
.find(candidate => candidate.role_name === member.name);
|
|
@@ -199,10 +269,14 @@ async function launchMember(input) {
|
|
|
199
269
|
let effectiveActionId = actionId;
|
|
200
270
|
const attempt = (seat.launch?.attempt ?? 0) + 1;
|
|
201
271
|
const taskSha = sha256Text(startup.task);
|
|
272
|
+
const effectiveAgentDefinition = structuredClone(settings.definition);
|
|
273
|
+
const { projection: agentDefinition, fingerprint: agentFingerprint } = launchDefinition(effectiveAgentDefinition);
|
|
202
274
|
const proxyCaller = process.env[FLEET_PROXY_STATE_DIR_ENV]
|
|
203
275
|
? process.env[FLEET_PROXY_CALLER_ENV] : undefined;
|
|
204
276
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
205
277
|
state: 'intent', attempt, action_id: actionId, mission_sha256: taskSha,
|
|
278
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
279
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
206
280
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
207
281
|
updated_at: new Date().toISOString(),
|
|
208
282
|
} });
|
|
@@ -211,13 +285,7 @@ async function launchMember(input) {
|
|
|
211
285
|
name: member.name,
|
|
212
286
|
temp: true,
|
|
213
287
|
identity: member.name,
|
|
214
|
-
|
|
215
|
-
model: settings.model,
|
|
216
|
-
harness: settings.harness,
|
|
217
|
-
cwd: settings.cwd,
|
|
218
|
-
approval: settings.approval,
|
|
219
|
-
filesystem: settings.filesystem,
|
|
220
|
-
unattended: settings.unattended,
|
|
288
|
+
agentDefinition: settings.definition,
|
|
221
289
|
surface: 'agent',
|
|
222
290
|
creationActionId: actionId,
|
|
223
291
|
roomMemberStartup: startup,
|
|
@@ -227,27 +295,48 @@ async function launchMember(input) {
|
|
|
227
295
|
if (launched.creationActionId !== actionId) {
|
|
228
296
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
229
297
|
state: 'intent', attempt, action_id: launched.creationActionId,
|
|
230
|
-
mission_sha256: taskSha,
|
|
298
|
+
mission_sha256: taskSha, agent_definition: agentDefinition,
|
|
299
|
+
agent_fingerprint: agentFingerprint,
|
|
300
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
301
|
+
updated_at: new Date().toISOString(),
|
|
231
302
|
...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
|
|
232
303
|
} });
|
|
233
304
|
}
|
|
234
305
|
const supervisor = readTempSupervisor(launchedDir);
|
|
235
|
-
if (!supervisor || supervisor.role !== member.name || !launchMatches(launchedDir, member, launched.creationActionId, taskSha, provision.roomId, startup.room_identity_cid, startup.invite_id)) {
|
|
306
|
+
if (!supervisor || supervisor.role !== member.name || !launchMatches(launchedDir, member, launched.creationActionId, taskSha, provision.roomId, startup.room_identity_cid, startup.invite_id, startup.anonymous ?? false)) {
|
|
236
307
|
throw new Error(`new launch for ${member.name} did not persist matching provenance`);
|
|
237
308
|
}
|
|
309
|
+
const presentation = launched.configuration
|
|
310
|
+
? { ...launched.configuration, template: settings.template,
|
|
311
|
+
...(launched.configuration.mission ? {} : { mission: member.coworkRole }) }
|
|
312
|
+
: presentationFromStatePath(launchedDir, settings, member.coworkRole);
|
|
238
313
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
239
314
|
state: 'launched', attempt, action_id: launched.creationActionId, mission_sha256: taskSha,
|
|
315
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
316
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
317
|
+
presentation,
|
|
240
318
|
...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
|
|
241
319
|
launch_id: supervisor.launchId, updated_at: new Date().toISOString(),
|
|
242
320
|
} });
|
|
321
|
+
recordFleetAuditPresentation({ kind: 'agent_started', id: member.name, name: member.name,
|
|
322
|
+
lifetime: 'temporary', brain: selectionSummary(settings.definition.brain),
|
|
323
|
+
role: selectionSummary(settings.definition.role),
|
|
324
|
+
harness: presentation.harness, session: 'acp',
|
|
325
|
+
permissions: permissionSummary(settings.definition), parent: provision.roomId,
|
|
326
|
+
actionId: launched.creationActionId, inherited: [],
|
|
327
|
+
configuration: presentation });
|
|
243
328
|
}
|
|
244
329
|
catch (error) {
|
|
245
330
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
246
331
|
state: 'failed', attempt, action_id: effectiveActionId, mission_sha256: taskSha,
|
|
332
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
333
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
247
334
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
248
335
|
updated_at: new Date().toISOString(),
|
|
249
336
|
error: error instanceof Error ? error.message : String(error),
|
|
250
337
|
} });
|
|
338
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Agent', id: member.name,
|
|
339
|
+
state: 'failed', category: 'readiness_failed', eventId: effectiveActionId });
|
|
251
340
|
throw error;
|
|
252
341
|
}
|
|
253
342
|
}
|
|
@@ -285,29 +374,56 @@ function reconcileMemberSeats(roomId, members, observed) {
|
|
|
285
374
|
updateMemberSeats(roomId, seats);
|
|
286
375
|
return { complete, seats };
|
|
287
376
|
}
|
|
377
|
+
function assertCoworkRoomPolicy(room, expectedAnonymous) {
|
|
378
|
+
if ((room.anonymous ?? false) !== expectedAnonymous)
|
|
379
|
+
throw new Error(`Cowork anonymity (${String(room.anonymous ?? false)}) does not match Fleet's durable Room policy (${String(expectedAnonymous)})`);
|
|
380
|
+
}
|
|
288
381
|
export async function provisionMembers(input) {
|
|
289
382
|
const { cfg, cowork, roomId, taskId, template } = input;
|
|
383
|
+
// Deletion-epoch pre-check; each member launch re-checks under the lock.
|
|
384
|
+
if (taskId && taskDeletionState(taskId) !== 'none')
|
|
385
|
+
throw new Error(`task ${taskId} is pending deletion; refusing to provision members`);
|
|
290
386
|
const prefix = taskId ? shortId(taskId) : `room-${shortId(roomId)}`;
|
|
291
387
|
const members = expandMembers(template, prefix);
|
|
388
|
+
// Resolve every Agent before persisting launch intent or touching Cowork membership.
|
|
389
|
+
const sealed = template.launch_snapshot_hash
|
|
390
|
+
? readLaunchSnapshot(template.launch_snapshot_hash) : undefined;
|
|
391
|
+
const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg, sealed)]));
|
|
292
392
|
const existing = getRoomRecord(roomId);
|
|
293
393
|
if (!existing?.room_identity_cid)
|
|
294
394
|
throw new Error(`room ${roomId} has no pinned room identity CID`);
|
|
295
395
|
const roomIdentityCid = existing.room_identity_cid;
|
|
296
396
|
const ownerSeatCid = existing.owner_seat_cid ?? null;
|
|
397
|
+
const roomPolicy = storedRoomLaunchPolicy(existing.room_policy);
|
|
297
398
|
const persistedNames = new Set(existing.member_seats.map(seat => seat.role_name));
|
|
298
399
|
const resuming = members.length > 0
|
|
299
400
|
&& members.every(member => persistedNames.has(member.name));
|
|
300
401
|
if (!resuming) {
|
|
301
402
|
advanceSaga(roomId, 'create_members', 3);
|
|
302
|
-
updateMemberSeats(roomId, members.map(member =>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
403
|
+
updateMemberSeats(roomId, members.map(member => {
|
|
404
|
+
const memberSettings = settings.get(member.name);
|
|
405
|
+
const evidence = launchDefinition(memberSettings.definition);
|
|
406
|
+
return ({
|
|
407
|
+
role_name: member.name,
|
|
408
|
+
slot: member.slot,
|
|
409
|
+
cowork_role: member.coworkRole,
|
|
410
|
+
seat_state: 'pending',
|
|
411
|
+
launch: { state: 'pending', attempt: 0,
|
|
412
|
+
agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
|
|
413
|
+
agent_template: memberSettings.template,
|
|
414
|
+
agent_template_hash: memberSettings.templateHash,
|
|
415
|
+
updated_at: new Date().toISOString() },
|
|
416
|
+
});
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
for (const member of members) {
|
|
421
|
+
const seat = existing.member_seats.find(candidate => candidate.role_name === member.name);
|
|
422
|
+
const evidence = launchDefinition(settings.get(member.name).definition);
|
|
423
|
+
if (!seat.launch?.agent_fingerprint || seat.launch.agent_fingerprint !== evidence.fingerprint)
|
|
424
|
+
throw new Error(`Agent definition drift for ${member.name}; durable launch intent does not match current configuration`);
|
|
425
|
+
}
|
|
309
426
|
}
|
|
310
|
-
const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg)]));
|
|
311
427
|
const tasks = new Map(members.map(member => [member.name, roomTask(input, member, settings.get(member.name), members, roomIdentityCid, ownerSeatCid)]));
|
|
312
428
|
const policy = {
|
|
313
429
|
timeoutMs: input.startupWait?.timeoutMs ?? 60_000,
|
|
@@ -319,6 +435,7 @@ export async function provisionMembers(input) {
|
|
|
319
435
|
advanceSaga(roomId, 'join_role_groups', 4);
|
|
320
436
|
try {
|
|
321
437
|
const initialRoom = await cowork.recoverRoom(roomId);
|
|
438
|
+
assertCoworkRoomPolicy(initialRoom, roomPolicy.anonymous);
|
|
322
439
|
reconcileMemberSeats(roomId, members, initialRoom.seats);
|
|
323
440
|
for (const member of members) {
|
|
324
441
|
const task = tasks.get(member.name);
|
|
@@ -326,13 +443,15 @@ export async function provisionMembers(input) {
|
|
|
326
443
|
.find(seat => seat.role_name === member.name);
|
|
327
444
|
if (currentSeat.seat_state === 'active') {
|
|
328
445
|
if (!await retainRunningLaunch({
|
|
329
|
-
provision: input, member, task, roomIdentityCid,
|
|
446
|
+
provision: input, member, settings: settings.get(member.name), task, roomIdentityCid,
|
|
330
447
|
})) {
|
|
331
448
|
throw new Error(`active Cowork seat ${member.name} has no matching live Fleet launch`);
|
|
332
449
|
}
|
|
333
450
|
continue;
|
|
334
451
|
}
|
|
335
|
-
if (await retainRunningLaunch({
|
|
452
|
+
if (await retainRunningLaunch({
|
|
453
|
+
provision: input, member, settings: settings.get(member.name), task, roomIdentityCid,
|
|
454
|
+
}))
|
|
336
455
|
continue;
|
|
337
456
|
const issued = await cowork.issueInvite(roomId, {
|
|
338
457
|
mode: 'one_time', role: member.coworkRole, min_accepts: 1,
|
|
@@ -353,6 +472,7 @@ export async function provisionMembers(input) {
|
|
|
353
472
|
role: member.coworkRole,
|
|
354
473
|
task,
|
|
355
474
|
owner_seat_cid: ownerSeatCid,
|
|
475
|
+
anonymous: roomPolicy.anonymous,
|
|
356
476
|
},
|
|
357
477
|
});
|
|
358
478
|
}
|
|
@@ -365,8 +485,17 @@ export async function provisionMembers(input) {
|
|
|
365
485
|
catch (error) {
|
|
366
486
|
const reason = error instanceof Error ? error.message : String(error);
|
|
367
487
|
setSagaError(roomId, reason, 'Member invite or launch failed. Retry with `task recover`.', 'member_failed');
|
|
368
|
-
if (taskId)
|
|
369
|
-
|
|
488
|
+
if (taskId) {
|
|
489
|
+
// A deletion-pending (or already-terminal) task rejects the block
|
|
490
|
+
// overlay; the original launch failure must still propagate.
|
|
491
|
+
try {
|
|
492
|
+
blockTask(taskId, reason);
|
|
493
|
+
}
|
|
494
|
+
catch (blockError) {
|
|
495
|
+
if (!(blockError instanceof TaskStateError))
|
|
496
|
+
throw blockError;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
370
499
|
throw error;
|
|
371
500
|
}
|
|
372
501
|
advanceSaga(roomId, 'wait_seats', 5);
|
|
@@ -374,6 +503,7 @@ export async function provisionMembers(input) {
|
|
|
374
503
|
let delay = policy.initialDelayMs;
|
|
375
504
|
for (;;) {
|
|
376
505
|
const remote = await cowork.recoverRoom(roomId);
|
|
506
|
+
assertCoworkRoomPolicy(remote, roomPolicy.anonymous);
|
|
377
507
|
const reconciled = reconcileMemberSeats(roomId, members, remote.seats);
|
|
378
508
|
if (reconciled.complete)
|
|
379
509
|
break;
|
|
@@ -9,6 +9,7 @@ export interface CreateRoomInput {
|
|
|
9
9
|
room_identity_cid?: string;
|
|
10
10
|
task_id?: string;
|
|
11
11
|
template_snapshot?: import('./types.js').TemplateSnapshot;
|
|
12
|
+
room_policy?: import('./types.js').RoomLaunchPolicy;
|
|
12
13
|
}
|
|
13
14
|
export declare function createRoomRecord(input: CreateRoomInput): RoomOrchestrationRecord;
|
|
14
15
|
export declare function getRoomRecord(id: string): RoomOrchestrationRecord | undefined;
|
|
@@ -2,6 +2,8 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:f
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { replaceFileAtomically } from '../atomic-file.js';
|
|
4
4
|
import { stateRoot } from '../paths.js';
|
|
5
|
+
import { storedRoomLaunchPolicy } from './types.js';
|
|
6
|
+
import { releaseLaunchSnapshot } from './launch-snapshot.js';
|
|
5
7
|
export const roomsDir = () => join(stateRoot(), 'rooms');
|
|
6
8
|
function roomPath(id) { return join(roomsDir(), `${id}.json`); }
|
|
7
9
|
export class RoomStateError extends Error {
|
|
@@ -18,8 +20,13 @@ function writeRoom(record) {
|
|
|
18
20
|
}
|
|
19
21
|
export function createRoomRecord(input) {
|
|
20
22
|
const existing = getRoomRecord(input.room_id);
|
|
21
|
-
if (existing)
|
|
23
|
+
if (existing) {
|
|
24
|
+
const before = storedRoomLaunchPolicy(existing.room_policy);
|
|
25
|
+
const requested = storedRoomLaunchPolicy(input.room_policy);
|
|
26
|
+
if (JSON.stringify(before) !== JSON.stringify(requested))
|
|
27
|
+
throw new RoomStateError(`room ${input.room_id} launch policy mismatch`);
|
|
22
28
|
return existing;
|
|
29
|
+
}
|
|
23
30
|
const record = {
|
|
24
31
|
room_id: input.room_id,
|
|
25
32
|
room_name: input.room_name,
|
|
@@ -27,6 +34,7 @@ export function createRoomRecord(input) {
|
|
|
27
34
|
room_identity_cid: input.room_identity_cid,
|
|
28
35
|
task_id: input.task_id,
|
|
29
36
|
template_snapshot: input.template_snapshot,
|
|
37
|
+
room_policy: input.room_policy,
|
|
30
38
|
saga: { phase: 'persist_intent', step_index: 0 },
|
|
31
39
|
member_seats: [],
|
|
32
40
|
state: 'provisioning',
|
|
@@ -46,8 +54,11 @@ export function getRoomRecord(id) {
|
|
|
46
54
|
/** Remove a terminal orchestration record from the live room inventory. */
|
|
47
55
|
export function deleteRoomRecord(id) {
|
|
48
56
|
const path = roomPath(id);
|
|
57
|
+
const hash = getRoomRecord(id)?.template_snapshot?.launch_snapshot_hash;
|
|
49
58
|
if (existsSync(path))
|
|
50
59
|
rmSync(path);
|
|
60
|
+
if (hash)
|
|
61
|
+
releaseLaunchSnapshot(hash);
|
|
51
62
|
}
|
|
52
63
|
export function listRoomRecords(filter) {
|
|
53
64
|
const dir = roomsDir();
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import type { TaskRecord, TaskState, TaskOrigin, TaskTemplateRef, TaskOutcome, TaskMemberRole, TaskTerminalIntent } from './types.js';
|
|
1
|
+
import type { TaskRecord, TaskState, TaskOrigin, TaskTemplateRef, TaskOutcome, TaskMemberRole, TaskTerminalIntent, TaskDeletionActor, TaskDeletionMemberPhase } from './types.js';
|
|
2
2
|
export declare const tasksDir: () => string;
|
|
3
3
|
export declare class TaskStateError extends Error {
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Accepted deletion is the per-task epoch guard: once pending, every lifecycle
|
|
7
|
+
* mutation, terminal settlement, and room publication must fail boundedly.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertNoPendingDeletion(task: TaskRecord): void;
|
|
5
10
|
export interface CreateTaskInput {
|
|
6
11
|
title: string;
|
|
7
12
|
brief?: string;
|
|
8
13
|
brief_file?: string;
|
|
9
14
|
template?: TaskTemplateRef;
|
|
15
|
+
execution_plan?: TaskRecord['execution_plan'];
|
|
10
16
|
origin: TaskOrigin;
|
|
11
17
|
idempotency_key?: string;
|
|
12
18
|
start?: boolean;
|
|
@@ -16,9 +22,11 @@ export interface CreateTaskInput {
|
|
|
16
22
|
}
|
|
17
23
|
export declare function createTask(input: CreateTaskInput): TaskRecord;
|
|
18
24
|
export declare function getTask(id: string): TaskRecord;
|
|
25
|
+
export declare function updateTaskExecutionPlan(id: string, executionPlan: NonNullable<TaskRecord['execution_plan']>): TaskRecord;
|
|
19
26
|
export declare function listTasks(filter?: {
|
|
20
27
|
state?: TaskState | TaskState[];
|
|
21
28
|
listId?: string;
|
|
29
|
+
includeDeleting?: boolean;
|
|
22
30
|
}): TaskRecord[];
|
|
23
31
|
export declare function moveTaskToList(id: string, listId: string): TaskRecord;
|
|
24
32
|
export declare function findByIdempotencyKey(key: string): TaskRecord | undefined;
|
|
@@ -43,5 +51,109 @@ export declare function failTask(id: string, error: string): TaskRecord;
|
|
|
43
51
|
export declare function updateTaskRoom(id: string, roomId: string, roomIdentityCid: string): TaskRecord;
|
|
44
52
|
export declare function updateTaskTemplate(id: string, template: TaskTemplateRef): TaskRecord;
|
|
45
53
|
export declare function updateTaskMembers(id: string, members: TaskMemberRole[]): TaskRecord;
|
|
46
|
-
/**
|
|
47
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Lenient read for deletion settlement and status surfaces: a broken list
|
|
56
|
+
* reference must never make a task unreadable or undeletable. Missing records
|
|
57
|
+
* throw the canonical task-not-found error; other read failures propagate.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getDeletingTask(id: string): TaskRecord;
|
|
60
|
+
/** Cheap durable deletion-epoch probe for room/member publication guards. */
|
|
61
|
+
export declare function taskDeletionState(id: string): 'none' | 'pending' | 'absent';
|
|
62
|
+
/**
|
|
63
|
+
* Durable, surface-independent audit evidence for a permanent deletion. The
|
|
64
|
+
* receipt outlives the task record: written with the acceptance intent, and
|
|
65
|
+
* completed before settlement is reported. Metadata only — never brief or
|
|
66
|
+
* room content.
|
|
67
|
+
*/
|
|
68
|
+
export interface TaskDeletionReceipt {
|
|
69
|
+
schema_version: 1;
|
|
70
|
+
task_id: string;
|
|
71
|
+
title: string;
|
|
72
|
+
accepted_at: string;
|
|
73
|
+
actor: TaskDeletionActor;
|
|
74
|
+
original_state: TaskState;
|
|
75
|
+
room_id?: string;
|
|
76
|
+
member_count: number;
|
|
77
|
+
settled_at?: string;
|
|
78
|
+
result?: 'deleted';
|
|
79
|
+
}
|
|
80
|
+
export declare const deletionReceiptsDir: () => string;
|
|
81
|
+
export declare function readTaskDeletionReceipt(id: string): TaskDeletionReceipt | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Ensure the acceptance receipt exists before any cleanup side effect,
|
|
84
|
+
* backfilling it from the durable intent (heals a crash between the intent
|
|
85
|
+
* write and the receipt write). Fails closed: a receipt write error aborts
|
|
86
|
+
* settlement rather than deleting resources without audit evidence.
|
|
87
|
+
*/
|
|
88
|
+
export declare function ensureTaskDeletionReceipt(id: string): void;
|
|
89
|
+
/** Record settlement on the receipt; idempotent, tolerant of legacy absence. */
|
|
90
|
+
export declare function completeTaskDeletionReceipt(id: string): void;
|
|
91
|
+
export type TaskDeletionAcceptance = {
|
|
92
|
+
status: 'accepted' | 'pending';
|
|
93
|
+
task: TaskRecord;
|
|
94
|
+
} | {
|
|
95
|
+
status: 'already_absent';
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Persist the first-wins durable deletion intent. Accepts every lifecycle
|
|
99
|
+
* state, takes precedence over a pending terminal intent, and never touches
|
|
100
|
+
* remote resources. Repeat requests while pending re-arm settlement; a request
|
|
101
|
+
* for a missing record reports the idempotent already-absent outcome.
|
|
102
|
+
*
|
|
103
|
+
* Low-level record mutation only. Callers serialize acceptance with settlement
|
|
104
|
+
* through deletion.ts, which holds the common task-operation lock.
|
|
105
|
+
*/
|
|
106
|
+
export declare function beginTaskDeletionIntent(id: string, actor: TaskDeletionActor): TaskDeletionAcceptance;
|
|
107
|
+
/** Record an actionable settlement failure; the task stays hidden and recoverable. */
|
|
108
|
+
export declare function setTaskDeletionError(id: string, error: string, recoveryHint: string): TaskRecord;
|
|
109
|
+
/** Marker proving a member never launched, mirroring close.ts's short path. */
|
|
110
|
+
export declare const DELETION_MEMBER_NEVER_LAUNCHED = "never-launched";
|
|
111
|
+
/** Marker proving absence verified against temp state AND the CID-wide identity scan. */
|
|
112
|
+
export declare const DELETION_MEMBER_ABSENT_VERIFIED = "absent-verified";
|
|
113
|
+
/**
|
|
114
|
+
* Advance a member retirement cursor carried by the deletion intent. Only
|
|
115
|
+
* adjacent forward transitions are legal — plus the explicit
|
|
116
|
+
* pending → identity_absent short path proved by the 'never-launched' marker —
|
|
117
|
+
* so no managed agent can be claimed retired without the full evidence chain.
|
|
118
|
+
* launch_id and archive_path are immutable once recorded; same-phase retries
|
|
119
|
+
* are idempotent but must not change evidence.
|
|
120
|
+
*/
|
|
121
|
+
export declare function advanceTaskDeletionMember(id: string, name: string, phase: TaskDeletionMemberPhase, launchId?: string, archivePath?: string): TaskRecord;
|
|
122
|
+
interface SeatEvidence {
|
|
123
|
+
role_name: string;
|
|
124
|
+
identity_cid?: string;
|
|
125
|
+
retirement?: {
|
|
126
|
+
phase: TaskDeletionMemberPhase;
|
|
127
|
+
launch_id: string;
|
|
128
|
+
archive_path?: string;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Durably register cursors for late-provisioned members before retirement
|
|
133
|
+
* begins: provisioning publishes room seats before updateTaskMembers, so the
|
|
134
|
+
* acceptance snapshot can be empty while live seats exist. Seats without a
|
|
135
|
+
* recorded identity CID are not registered here — until a CID is recorded
|
|
136
|
+
* there is no managed identity to orphan, and the room record still carries
|
|
137
|
+
* those seats through the close saga that follows.
|
|
138
|
+
*/
|
|
139
|
+
export declare function upsertTaskDeletionMembersFromSeats(id: string, seats: ReadonlyArray<SeatEvidence>): TaskRecord;
|
|
140
|
+
/**
|
|
141
|
+
* The pre-room-delete checkpoint: import completed retirement evidence from
|
|
142
|
+
* room seats into the deletion cursors, so a crash between room-record
|
|
143
|
+
* deletion and task unlink retries from durable cursors instead of
|
|
144
|
+
* reconstructing consumed evidence. Call ONLY after closeManagedRoom has
|
|
145
|
+
* completed retirement and BEFORE cowork.deleteRoom/deleteRoomRecord.
|
|
146
|
+
*
|
|
147
|
+
* The room saga is trusted for phase jumps, but partial or corrupt retained
|
|
148
|
+
* records must not become false success: every seat must be identity_absent;
|
|
149
|
+
* a real launch requires archive evidence; an identity-less seat is accepted
|
|
150
|
+
* only via the never-launched proof.
|
|
151
|
+
*/
|
|
152
|
+
export declare function importTaskDeletionRetirementEvidence(id: string, seats: ReadonlyArray<SeatEvidence>): TaskRecord;
|
|
153
|
+
/**
|
|
154
|
+
* Physically remove a deletion-pending task record. Settlement-only: callers
|
|
155
|
+
* must have completed member retirement and room cleanup first. Missing
|
|
156
|
+
* records are the idempotent already-settled outcome.
|
|
157
|
+
*/
|
|
158
|
+
export declare function unlinkDeletedTask(id: string): boolean;
|
|
159
|
+
export {};
|