@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.
Files changed (202) hide show
  1. package/README.md +204 -55
  2. package/dist/agent-recovery-gate.d.ts +17 -0
  3. package/dist/agent-recovery-gate.js +135 -0
  4. package/dist/application/capabilities.js +18 -6
  5. package/dist/application/fleet-query-service.js +20 -5
  6. package/dist/application/role-command-service.js +9 -0
  7. package/dist/application/role-creation-service.d.ts +6 -25
  8. package/dist/application/role-creation-service.js +34 -72
  9. package/dist/application/role-repository.d.ts +4 -2
  10. package/dist/application/role-repository.js +6 -3
  11. package/dist/application/session-control.d.ts +3 -1
  12. package/dist/application/session-control.js +4 -2
  13. package/dist/application/task-room-service.d.ts +58 -6
  14. package/dist/application/task-room-service.js +435 -79
  15. package/dist/briefing.js +74 -24
  16. package/dist/build-info.json +4 -4
  17. package/dist/canonical-json.d.ts +2 -0
  18. package/dist/canonical-json.js +10 -0
  19. package/dist/cli.js +241 -76
  20. package/dist/config-yaml.d.ts +1 -1
  21. package/dist/config.d.ts +75 -3
  22. package/dist/config.js +274 -34
  23. package/dist/creation.d.ts +1 -1
  24. package/dist/creation.js +3 -2
  25. package/dist/daemon-recovery.d.ts +93 -0
  26. package/dist/daemon-recovery.js +328 -0
  27. package/dist/docs.d.ts +1 -1
  28. package/dist/docs.js +192 -66
  29. package/dist/doctor.d.ts +2 -0
  30. package/dist/doctor.js +54 -4
  31. package/dist/fleet-command-audit.d.ts +165 -0
  32. package/dist/fleet-command-audit.js +555 -0
  33. package/dist/fleet-proxy.d.ts +7 -2
  34. package/dist/fleet-proxy.js +30 -8
  35. package/dist/harness/agent-session.d.ts +5 -0
  36. package/dist/harness/claude-code-session.d.ts +2 -0
  37. package/dist/harness/claude-code-session.js +6 -1
  38. package/dist/harness/claude-code.js +2 -0
  39. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  40. package/dist/harness/codex-app-server-proxy.js +348 -14
  41. package/dist/harness/codex-session.d.ts +11 -2
  42. package/dist/harness/codex-session.js +22 -2
  43. package/dist/harness/codex.d.ts +5 -1
  44. package/dist/harness/codex.js +110 -10
  45. package/dist/harness/types.d.ts +1 -1
  46. package/dist/index.d.ts +5 -4
  47. package/dist/index.js +4 -3
  48. package/dist/init-guidance.d.ts +1 -1
  49. package/dist/init-guidance.js +4 -3
  50. package/dist/init-wizard.d.ts +98 -0
  51. package/dist/init-wizard.js +629 -0
  52. package/dist/lifecycle-summary.d.ts +134 -0
  53. package/dist/lifecycle-summary.js +179 -0
  54. package/dist/loops/config.d.ts +10 -1
  55. package/dist/loops/config.js +38 -5
  56. package/dist/model-env.d.ts +2 -2
  57. package/dist/model-env.js +1 -1
  58. package/dist/model-recovery.d.ts +1 -1
  59. package/dist/ops.js +2 -17
  60. package/dist/owner-channel/channel.d.ts +58 -0
  61. package/dist/owner-channel/channel.js +321 -42
  62. package/dist/owner-channel/commands.d.ts +6 -1
  63. package/dist/owner-channel/commands.js +34 -19
  64. package/dist/owner-channel/state.d.ts +2 -0
  65. package/dist/owner-channel/state.js +8 -2
  66. package/dist/permissions.js +1 -1
  67. package/dist/preset-bootstrap.d.ts +10 -0
  68. package/dist/preset-bootstrap.js +85 -0
  69. package/dist/preset-migration.d.ts +42 -0
  70. package/dist/preset-migration.js +414 -0
  71. package/dist/reports/artifact.d.ts +10 -0
  72. package/dist/reports/artifact.js +32 -0
  73. package/dist/reports/index.d.ts +5 -0
  74. package/dist/reports/index.js +5 -0
  75. package/dist/reports/render.d.ts +6 -0
  76. package/dist/reports/render.js +240 -0
  77. package/dist/reports/service.d.ts +42 -0
  78. package/dist/reports/service.js +57 -0
  79. package/dist/reports/tasks.d.ts +52 -0
  80. package/dist/reports/tasks.js +177 -0
  81. package/dist/reports/types.d.ts +315 -0
  82. package/dist/reports/types.js +6 -0
  83. package/dist/resolved-plan.js +13 -1
  84. package/dist/rooms-tasks/cli.d.ts +2 -0
  85. package/dist/rooms-tasks/cli.js +514 -75
  86. package/dist/rooms-tasks/close.d.ts +9 -1
  87. package/dist/rooms-tasks/close.js +20 -4
  88. package/dist/rooms-tasks/config.js +16 -14
  89. package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
  90. package/dist/rooms-tasks/cowork-adapter.js +6 -0
  91. package/dist/rooms-tasks/deletion.d.ts +51 -0
  92. package/dist/rooms-tasks/deletion.js +217 -0
  93. package/dist/rooms-tasks/external-worker.d.ts +1 -0
  94. package/dist/rooms-tasks/external-worker.js +14 -9
  95. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  96. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  97. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  98. package/dist/rooms-tasks/member-overrides.js +204 -0
  99. package/dist/rooms-tasks/provision.js +184 -49
  100. package/dist/rooms-tasks/room-state.d.ts +1 -0
  101. package/dist/rooms-tasks/room-state.js +12 -1
  102. package/dist/rooms-tasks/task-state.d.ts +115 -3
  103. package/dist/rooms-tasks/task-state.js +361 -11
  104. package/dist/rooms-tasks/templates.d.ts +4 -2
  105. package/dist/rooms-tasks/templates.js +42 -78
  106. package/dist/rooms-tasks/terminal.d.ts +3 -0
  107. package/dist/rooms-tasks/terminal.js +7 -3
  108. package/dist/rooms-tasks/types.d.ts +96 -17
  109. package/dist/rooms-tasks/types.js +14 -4
  110. package/dist/runner.d.ts +12 -2
  111. package/dist/runner.js +124 -43
  112. package/dist/session/acp.d.ts +9 -1
  113. package/dist/session/acp.js +35 -9
  114. package/dist/session/arbiter.d.ts +2 -1
  115. package/dist/session/arbiter.js +2 -0
  116. package/dist/session/codex-app-server-transport.d.ts +53 -0
  117. package/dist/session/codex-app-server-transport.js +184 -0
  118. package/dist/session/codex-app-server.d.ts +122 -0
  119. package/dist/session/codex-app-server.js +1031 -0
  120. package/dist/session/control.d.ts +14 -1
  121. package/dist/session/control.js +38 -0
  122. package/dist/session/conversation-types.d.ts +4 -4
  123. package/dist/session/types.d.ts +24 -6
  124. package/dist/session/types.js +28 -0
  125. package/dist/spawn.d.ts +15 -31
  126. package/dist/spawn.js +117 -237
  127. package/dist/temp-lifecycle.d.ts +1 -1
  128. package/dist/watchdog/config.d.ts +10 -7
  129. package/dist/watchdog/config.js +29 -19
  130. package/dist/watchdog/run.js +4 -13
  131. package/dist/web/fleet-config-service.d.ts +1 -0
  132. package/dist/web/fleet-config-service.js +27 -8
  133. package/dist/web/runtime.js +11 -6
  134. package/dist/web/server.js +28 -1
  135. package/dist/web/topology-promote.js +8 -1
  136. package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
  137. package/dist/web-app/index.html +1 -1
  138. package/package.json +3 -2
  139. package/presets/brain-catalog.json +16 -0
  140. package/presets/fleet/agent_templates/Critic.yaml +4 -0
  141. package/presets/fleet/agent_templates/Developer.yaml +4 -0
  142. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
  143. package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
  144. package/presets/fleet/brains/claude-default.yaml +5 -0
  145. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  146. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  147. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  148. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  149. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  150. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  151. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  152. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  153. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  154. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  155. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  156. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  157. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  158. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  159. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  188. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  189. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  190. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  191. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  192. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  193. package/presets/fleet/roles/Coordinator.yaml +30 -0
  194. package/presets/fleet/roles/Critic.yaml +19 -0
  195. package/presets/fleet/roles/Developer.yaml +19 -0
  196. package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
  197. package/presets/fleet/room_templates/pair.yaml +10 -0
  198. package/presets/fleet/room_templates/single.yaml +7 -0
  199. package/presets/fleet/room_templates/team.yaml +12 -0
  200. package/presets/fleet.yaml +7 -0
  201. package/presets/manifest.json +6 -0
  202. 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 { findRole } from '../config.js';
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
- roleRef: slot.role_ref,
56
- overrides: slot.overrides,
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
- let refRole;
64
- try {
65
- refRole = findRole(cfg, member.roleRef);
66
- }
67
- catch { /* no ref role */ }
68
- const permissions = member.overrides?.permissions;
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
- model: member.overrides?.model ?? refRole?.model,
71
- harness: member.overrides?.harness ?? refRole?.harness,
72
- cwd: member.overrides?.cwd ?? refRole?.cwd,
73
- persona: member.overrides?.persona ?? refRole?.persona,
74
- approval: permissions?.approval,
75
- filesystem: permissions?.filesystem,
76
- unattended: permissions?.unattended,
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.task === role.mission
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
- mission: startup.task,
215
- model: settings.model,
216
- harness: settings.harness,
217
- cwd: settings.cwd,
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, updated_at: new Date().toISOString(),
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
- role_name: member.name,
304
- slot: member.slot,
305
- cowork_role: member.coworkRole,
306
- seat_state: 'pending',
307
- launch: { state: 'pending', attempt: 0, updated_at: new Date().toISOString() },
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({ provision: input, member, task, roomIdentityCid }))
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
- blockTask(taskId, reason);
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
- /** Remove a completed task from Fleet's backlog. Missing tasks are an idempotent no-op. */
47
- export declare function deleteTask(id: string): boolean;
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 {};