@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.21
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 +204 -55
- 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 +435 -79
- 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 +241 -76
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +75 -3
- package/dist/config.js +274 -34
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +3 -2
- 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 +192 -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 +555 -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 +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-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 +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +514 -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 +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/provision.js +184 -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 +96 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +12 -2
- package/dist/runner.js +124 -43
- 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 +15 -31
- package/dist/spawn.js +117 -237
- 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,32 @@ 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,
|
|
103
|
+
loopSource: slot.loop_source ?? 'omitted',
|
|
57
104
|
});
|
|
58
105
|
}
|
|
59
106
|
}
|
|
60
107
|
return result;
|
|
61
108
|
}
|
|
62
|
-
function settingsFor(member, cfg) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
109
|
+
function settingsFor(member, cfg, sealed) {
|
|
110
|
+
const definition = sealed
|
|
111
|
+
? sealed[member.launchDefinitionId]
|
|
112
|
+
: cfg.agentTemplates?.[member.agentTemplate];
|
|
113
|
+
if (!definition)
|
|
114
|
+
throw new Error(sealed
|
|
115
|
+
? `Sealed Agent definition '${member.launchDefinitionId}' not found`
|
|
116
|
+
: `Agent Template '${member.agentTemplate}' not found`);
|
|
117
|
+
const role = definition.role;
|
|
69
118
|
return {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
definition: structuredClone(definition),
|
|
120
|
+
...('inline' in role && typeof role.inline.persona === 'string'
|
|
121
|
+
? { persona: role.inline.persona } : {}),
|
|
122
|
+
template: member.agentTemplate,
|
|
123
|
+
templateHash: member.agentTemplateHash
|
|
124
|
+
?? createHash('sha256').update(canonicalJson(definition)).digest('hex'),
|
|
125
|
+
loopSource: member.loopSource,
|
|
77
126
|
};
|
|
78
127
|
}
|
|
79
128
|
function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCid) {
|
|
@@ -96,7 +145,7 @@ function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCi
|
|
|
96
145
|
})),
|
|
97
146
|
});
|
|
98
147
|
}
|
|
99
|
-
function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid, expectedInviteId) {
|
|
148
|
+
function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid, expectedInviteId, anonymous = false) {
|
|
100
149
|
const provenance = readProvenance(dir);
|
|
101
150
|
if (provenance?.creationActionId !== actionId || provenance.role !== member.name)
|
|
102
151
|
return false;
|
|
@@ -104,13 +153,12 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
|
|
|
104
153
|
const role = parse(readFileSync(`${dir}/role.yaml`, 'utf8'));
|
|
105
154
|
const startup = role.roomMemberStartup;
|
|
106
155
|
return role.identity === member.name
|
|
107
|
-
&& typeof role.mission === 'string'
|
|
108
|
-
&& sha256Text(role.mission) === taskSha
|
|
109
156
|
&& startup?.room_id === roomId
|
|
110
157
|
&& startup.room_identity_cid === roomIdentityCid
|
|
111
158
|
&& startup.identity_name === member.name
|
|
112
159
|
&& startup.role === member.coworkRole
|
|
113
|
-
&& startup.
|
|
160
|
+
&& (startup.anonymous ?? false) === anonymous
|
|
161
|
+
&& sha256Text(startup.task ?? '') === taskSha
|
|
114
162
|
&& (expectedInviteId === undefined || startup.invite_id === expectedInviteId)
|
|
115
163
|
&& typeof startup.invite === 'string'
|
|
116
164
|
&& startup.invite.length > 0;
|
|
@@ -120,21 +168,22 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
|
|
|
120
168
|
}
|
|
121
169
|
}
|
|
122
170
|
async function retainRunningLaunch(input) {
|
|
123
|
-
const { provision, member, task, roomIdentityCid } = input;
|
|
171
|
+
const { provision, member, settings, task, roomIdentityCid } = input;
|
|
124
172
|
let seat = getRoomRecord(provision.roomId).member_seats
|
|
125
173
|
.find(candidate => candidate.role_name === member.name);
|
|
126
174
|
const dir = agentDir(member.name, true);
|
|
127
175
|
const taskSha = sha256Text(task);
|
|
176
|
+
const anonymous = storedRoomLaunchPolicy(getRoomRecord(provision.roomId)?.room_policy).anonymous;
|
|
128
177
|
if ((seat.launch?.state === 'intent' || seat.launch?.state === 'launched'
|
|
129
178
|
|| 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)) {
|
|
179
|
+
if (!seat.launch.action_id || !launchMatches(dir, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous)) {
|
|
131
180
|
const provenance = readProvenance(dir);
|
|
132
181
|
const adoptable = (seat.launch.state === 'intent' || seat.launch.state === 'failed')
|
|
133
182
|
&& Boolean(seat.launch.caller_role)
|
|
134
183
|
&& provenance?.surface === 'agent'
|
|
135
184
|
&& provenance.callerRole === seat.launch.caller_role
|
|
136
185
|
&& typeof provenance.creationActionId === 'string'
|
|
137
|
-
&& launchMatches(dir, member, provenance.creationActionId, taskSha, provision.roomId, roomIdentityCid, seat.invite_id);
|
|
186
|
+
&& launchMatches(dir, member, provenance.creationActionId, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous);
|
|
138
187
|
if (!adoptable)
|
|
139
188
|
throw new Error(`existing launch for ${member.name} does not match its durable intent`);
|
|
140
189
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
@@ -152,9 +201,14 @@ async function retainRunningLaunch(input) {
|
|
|
152
201
|
throw new Error(`existing launch for ${member.name} has unknown liveness`);
|
|
153
202
|
const retainedLaunch = seat.launch;
|
|
154
203
|
if (live === 'running') {
|
|
204
|
+
// A crash between spawn and the seat update (or a pre-upgrade launch)
|
|
205
|
+
// can retain a running member without a captured presentation; backfill
|
|
206
|
+
// it from the same persisted resolved role the match was proved against.
|
|
207
|
+
const presentation = retainedLaunch.presentation
|
|
208
|
+
?? presentationFromStatePath(dir, settings, member.coworkRole);
|
|
155
209
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
156
210
|
...retainedLaunch, state: 'launched', launch_id: supervisor.launchId,
|
|
157
|
-
updated_at: new Date().toISOString(),
|
|
211
|
+
presentation, updated_at: new Date().toISOString(),
|
|
158
212
|
} });
|
|
159
213
|
return true;
|
|
160
214
|
}
|
|
@@ -169,7 +223,7 @@ async function retainRunningLaunch(input) {
|
|
|
169
223
|
if (!seat.launch.launch_id || !seat.launch.action_id)
|
|
170
224
|
throw new Error(`missing durable launch identity for disappeared ${member.name}`);
|
|
171
225
|
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)) {
|
|
226
|
+
if (!launchMatches(archive, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous)) {
|
|
173
227
|
throw new Error(`archive for disappeared ${member.name} does not match its durable intent`);
|
|
174
228
|
}
|
|
175
229
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
@@ -181,7 +235,7 @@ async function retainRunningLaunch(input) {
|
|
|
181
235
|
if (!seat.launch.action_id)
|
|
182
236
|
throw new Error(`missing action ID for disappeared launch intent ${member.name}`);
|
|
183
237
|
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)) {
|
|
238
|
+
if (!archive || !launchMatches(archive.path, member, seat.launch.action_id, taskSha, provision.roomId, roomIdentityCid, seat.invite_id, anonymous)) {
|
|
185
239
|
throw new Error(`launch intent for ${member.name} has no exact live or terminated archive evidence`);
|
|
186
240
|
}
|
|
187
241
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
@@ -191,7 +245,25 @@ async function retainRunningLaunch(input) {
|
|
|
191
245
|
}
|
|
192
246
|
return false;
|
|
193
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Member-launch publication window: for a task-bound room, the durable launch
|
|
250
|
+
* intent, spawn, and launch record publish under the common task-operation
|
|
251
|
+
* lock so an accepted deletion linearizes strictly before (bounded abort — no
|
|
252
|
+
* spawn happens) or strictly after (the seat's launch record is visible to
|
|
253
|
+
* deletion retirement). The lock is held per member only, never across
|
|
254
|
+
* seat-wait loops.
|
|
255
|
+
*/
|
|
194
256
|
async function launchMember(input) {
|
|
257
|
+
const taskId = input.provision.taskId;
|
|
258
|
+
if (!taskId)
|
|
259
|
+
return launchMemberUnlocked(input);
|
|
260
|
+
return withFileLock(taskOperationLockPath(taskId), () => {
|
|
261
|
+
if (taskDeletionState(taskId) !== 'none')
|
|
262
|
+
throw new Error(`task ${taskId} is pending deletion; aborting member launch for ${input.member.name}`);
|
|
263
|
+
return launchMemberUnlocked(input);
|
|
264
|
+
}, {}, TASK_OPERATION_LOCK_STALE_MS);
|
|
265
|
+
}
|
|
266
|
+
async function launchMemberUnlocked(input) {
|
|
195
267
|
const { provision, member, settings, startup } = input;
|
|
196
268
|
const seat = getRoomRecord(provision.roomId).member_seats
|
|
197
269
|
.find(candidate => candidate.role_name === member.name);
|
|
@@ -199,10 +271,14 @@ async function launchMember(input) {
|
|
|
199
271
|
let effectiveActionId = actionId;
|
|
200
272
|
const attempt = (seat.launch?.attempt ?? 0) + 1;
|
|
201
273
|
const taskSha = sha256Text(startup.task);
|
|
274
|
+
const effectiveAgentDefinition = structuredClone(settings.definition);
|
|
275
|
+
const { projection: agentDefinition, fingerprint: agentFingerprint } = launchDefinition(effectiveAgentDefinition);
|
|
202
276
|
const proxyCaller = process.env[FLEET_PROXY_STATE_DIR_ENV]
|
|
203
277
|
? process.env[FLEET_PROXY_CALLER_ENV] : undefined;
|
|
204
278
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
205
279
|
state: 'intent', attempt, action_id: actionId, mission_sha256: taskSha,
|
|
280
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
281
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
206
282
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
207
283
|
updated_at: new Date().toISOString(),
|
|
208
284
|
} });
|
|
@@ -211,13 +287,10 @@ async function launchMember(input) {
|
|
|
211
287
|
name: member.name,
|
|
212
288
|
temp: true,
|
|
213
289
|
identity: member.name,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
approval: settings.approval,
|
|
219
|
-
filesystem: settings.filesystem,
|
|
220
|
-
unattended: settings.unattended,
|
|
290
|
+
agentDefinition: settings.definition,
|
|
291
|
+
...(settings.loopSource === 'cli' && settings.definition.loops === undefined
|
|
292
|
+
? { noLoops: true } : {}),
|
|
293
|
+
loopSource: settings.loopSource,
|
|
221
294
|
surface: 'agent',
|
|
222
295
|
creationActionId: actionId,
|
|
223
296
|
roomMemberStartup: startup,
|
|
@@ -227,27 +300,48 @@ async function launchMember(input) {
|
|
|
227
300
|
if (launched.creationActionId !== actionId) {
|
|
228
301
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
229
302
|
state: 'intent', attempt, action_id: launched.creationActionId,
|
|
230
|
-
mission_sha256: taskSha,
|
|
303
|
+
mission_sha256: taskSha, agent_definition: agentDefinition,
|
|
304
|
+
agent_fingerprint: agentFingerprint,
|
|
305
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
306
|
+
updated_at: new Date().toISOString(),
|
|
231
307
|
...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
|
|
232
308
|
} });
|
|
233
309
|
}
|
|
234
310
|
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)) {
|
|
311
|
+
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
312
|
throw new Error(`new launch for ${member.name} did not persist matching provenance`);
|
|
237
313
|
}
|
|
314
|
+
const presentation = launched.configuration
|
|
315
|
+
? { ...launched.configuration, template: settings.template,
|
|
316
|
+
...(launched.configuration.mission ? {} : { mission: member.coworkRole }) }
|
|
317
|
+
: presentationFromStatePath(launchedDir, settings, member.coworkRole);
|
|
238
318
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
239
319
|
state: 'launched', attempt, action_id: launched.creationActionId, mission_sha256: taskSha,
|
|
320
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
321
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
322
|
+
presentation,
|
|
240
323
|
...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
|
|
241
324
|
launch_id: supervisor.launchId, updated_at: new Date().toISOString(),
|
|
242
325
|
} });
|
|
326
|
+
recordFleetAuditPresentation({ kind: 'agent_started', id: member.name, name: member.name,
|
|
327
|
+
lifetime: 'temporary', brain: selectionSummary(settings.definition.brain),
|
|
328
|
+
role: selectionSummary(settings.definition.role),
|
|
329
|
+
harness: presentation.harness, session: 'acp',
|
|
330
|
+
permissions: permissionSummary(settings.definition), parent: provision.roomId,
|
|
331
|
+
actionId: launched.creationActionId, inherited: [],
|
|
332
|
+
configuration: presentation });
|
|
243
333
|
}
|
|
244
334
|
catch (error) {
|
|
245
335
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
246
336
|
state: 'failed', attempt, action_id: effectiveActionId, mission_sha256: taskSha,
|
|
337
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
338
|
+
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
247
339
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
248
340
|
updated_at: new Date().toISOString(),
|
|
249
341
|
error: error instanceof Error ? error.message : String(error),
|
|
250
342
|
} });
|
|
343
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Agent', id: member.name,
|
|
344
|
+
state: 'failed', category: 'readiness_failed', eventId: effectiveActionId });
|
|
251
345
|
throw error;
|
|
252
346
|
}
|
|
253
347
|
}
|
|
@@ -285,29 +379,56 @@ function reconcileMemberSeats(roomId, members, observed) {
|
|
|
285
379
|
updateMemberSeats(roomId, seats);
|
|
286
380
|
return { complete, seats };
|
|
287
381
|
}
|
|
382
|
+
function assertCoworkRoomPolicy(room, expectedAnonymous) {
|
|
383
|
+
if ((room.anonymous ?? false) !== expectedAnonymous)
|
|
384
|
+
throw new Error(`Cowork anonymity (${String(room.anonymous ?? false)}) does not match Fleet's durable Room policy (${String(expectedAnonymous)})`);
|
|
385
|
+
}
|
|
288
386
|
export async function provisionMembers(input) {
|
|
289
387
|
const { cfg, cowork, roomId, taskId, template } = input;
|
|
388
|
+
// Deletion-epoch pre-check; each member launch re-checks under the lock.
|
|
389
|
+
if (taskId && taskDeletionState(taskId) !== 'none')
|
|
390
|
+
throw new Error(`task ${taskId} is pending deletion; refusing to provision members`);
|
|
290
391
|
const prefix = taskId ? shortId(taskId) : `room-${shortId(roomId)}`;
|
|
291
392
|
const members = expandMembers(template, prefix);
|
|
393
|
+
// Resolve every Agent before persisting launch intent or touching Cowork membership.
|
|
394
|
+
const sealed = template.launch_snapshot_hash
|
|
395
|
+
? readLaunchSnapshot(template.launch_snapshot_hash) : undefined;
|
|
396
|
+
const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg, sealed)]));
|
|
292
397
|
const existing = getRoomRecord(roomId);
|
|
293
398
|
if (!existing?.room_identity_cid)
|
|
294
399
|
throw new Error(`room ${roomId} has no pinned room identity CID`);
|
|
295
400
|
const roomIdentityCid = existing.room_identity_cid;
|
|
296
401
|
const ownerSeatCid = existing.owner_seat_cid ?? null;
|
|
402
|
+
const roomPolicy = storedRoomLaunchPolicy(existing.room_policy);
|
|
297
403
|
const persistedNames = new Set(existing.member_seats.map(seat => seat.role_name));
|
|
298
404
|
const resuming = members.length > 0
|
|
299
405
|
&& members.every(member => persistedNames.has(member.name));
|
|
300
406
|
if (!resuming) {
|
|
301
407
|
advanceSaga(roomId, 'create_members', 3);
|
|
302
|
-
updateMemberSeats(roomId, members.map(member =>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
408
|
+
updateMemberSeats(roomId, members.map(member => {
|
|
409
|
+
const memberSettings = settings.get(member.name);
|
|
410
|
+
const evidence = launchDefinition(memberSettings.definition);
|
|
411
|
+
return ({
|
|
412
|
+
role_name: member.name,
|
|
413
|
+
slot: member.slot,
|
|
414
|
+
cowork_role: member.coworkRole,
|
|
415
|
+
seat_state: 'pending',
|
|
416
|
+
launch: { state: 'pending', attempt: 0,
|
|
417
|
+
agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
|
|
418
|
+
agent_template: memberSettings.template,
|
|
419
|
+
agent_template_hash: memberSettings.templateHash,
|
|
420
|
+
updated_at: new Date().toISOString() },
|
|
421
|
+
});
|
|
422
|
+
}));
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
for (const member of members) {
|
|
426
|
+
const seat = existing.member_seats.find(candidate => candidate.role_name === member.name);
|
|
427
|
+
const evidence = launchDefinition(settings.get(member.name).definition);
|
|
428
|
+
if (!seat.launch?.agent_fingerprint || seat.launch.agent_fingerprint !== evidence.fingerprint)
|
|
429
|
+
throw new Error(`Agent definition drift for ${member.name}; durable launch intent does not match current configuration`);
|
|
430
|
+
}
|
|
309
431
|
}
|
|
310
|
-
const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg)]));
|
|
311
432
|
const tasks = new Map(members.map(member => [member.name, roomTask(input, member, settings.get(member.name), members, roomIdentityCid, ownerSeatCid)]));
|
|
312
433
|
const policy = {
|
|
313
434
|
timeoutMs: input.startupWait?.timeoutMs ?? 60_000,
|
|
@@ -319,6 +440,7 @@ export async function provisionMembers(input) {
|
|
|
319
440
|
advanceSaga(roomId, 'join_role_groups', 4);
|
|
320
441
|
try {
|
|
321
442
|
const initialRoom = await cowork.recoverRoom(roomId);
|
|
443
|
+
assertCoworkRoomPolicy(initialRoom, roomPolicy.anonymous);
|
|
322
444
|
reconcileMemberSeats(roomId, members, initialRoom.seats);
|
|
323
445
|
for (const member of members) {
|
|
324
446
|
const task = tasks.get(member.name);
|
|
@@ -326,13 +448,15 @@ export async function provisionMembers(input) {
|
|
|
326
448
|
.find(seat => seat.role_name === member.name);
|
|
327
449
|
if (currentSeat.seat_state === 'active') {
|
|
328
450
|
if (!await retainRunningLaunch({
|
|
329
|
-
provision: input, member, task, roomIdentityCid,
|
|
451
|
+
provision: input, member, settings: settings.get(member.name), task, roomIdentityCid,
|
|
330
452
|
})) {
|
|
331
453
|
throw new Error(`active Cowork seat ${member.name} has no matching live Fleet launch`);
|
|
332
454
|
}
|
|
333
455
|
continue;
|
|
334
456
|
}
|
|
335
|
-
if (await retainRunningLaunch({
|
|
457
|
+
if (await retainRunningLaunch({
|
|
458
|
+
provision: input, member, settings: settings.get(member.name), task, roomIdentityCid,
|
|
459
|
+
}))
|
|
336
460
|
continue;
|
|
337
461
|
const issued = await cowork.issueInvite(roomId, {
|
|
338
462
|
mode: 'one_time', role: member.coworkRole, min_accepts: 1,
|
|
@@ -353,6 +477,7 @@ export async function provisionMembers(input) {
|
|
|
353
477
|
role: member.coworkRole,
|
|
354
478
|
task,
|
|
355
479
|
owner_seat_cid: ownerSeatCid,
|
|
480
|
+
anonymous: roomPolicy.anonymous,
|
|
356
481
|
},
|
|
357
482
|
});
|
|
358
483
|
}
|
|
@@ -365,8 +490,17 @@ export async function provisionMembers(input) {
|
|
|
365
490
|
catch (error) {
|
|
366
491
|
const reason = error instanceof Error ? error.message : String(error);
|
|
367
492
|
setSagaError(roomId, reason, 'Member invite or launch failed. Retry with `task recover`.', 'member_failed');
|
|
368
|
-
if (taskId)
|
|
369
|
-
|
|
493
|
+
if (taskId) {
|
|
494
|
+
// A deletion-pending (or already-terminal) task rejects the block
|
|
495
|
+
// overlay; the original launch failure must still propagate.
|
|
496
|
+
try {
|
|
497
|
+
blockTask(taskId, reason);
|
|
498
|
+
}
|
|
499
|
+
catch (blockError) {
|
|
500
|
+
if (!(blockError instanceof TaskStateError))
|
|
501
|
+
throw blockError;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
370
504
|
throw error;
|
|
371
505
|
}
|
|
372
506
|
advanceSaga(roomId, 'wait_seats', 5);
|
|
@@ -374,6 +508,7 @@ export async function provisionMembers(input) {
|
|
|
374
508
|
let delay = policy.initialDelayMs;
|
|
375
509
|
for (;;) {
|
|
376
510
|
const remote = await cowork.recoverRoom(roomId);
|
|
511
|
+
assertCoworkRoomPolicy(remote, roomPolicy.anonymous);
|
|
377
512
|
const reconciled = reconcileMemberSeats(roomId, members, remote.seats);
|
|
378
513
|
if (reconciled.complete)
|
|
379
514
|
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 {};
|