@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
@@ -4,6 +4,7 @@ import { classifyActivity } from '../session/activity.js';
4
4
  import { controlRequest } from '../session/control.js';
5
5
  import { SessionControlError } from '../session/types.js';
6
6
  import { readExitRecord, readRestartLedger } from '../runner.js';
7
+ import { readDaemonRecoveryStatus } from '../daemon-recovery.js';
7
8
  import { roleCapabilities } from './capabilities.js';
8
9
  import { FleetError } from './errors.js';
9
10
  const clean = (value, max = 512) => value.replace(/[\0-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').trim().slice(0, max);
@@ -46,7 +47,8 @@ function readIsolation(dir) {
46
47
  function sessionOverall(supervisor, session, restart, monitor, isolation, problems) {
47
48
  if (restart.circuit === 'open' || monitor.health === 'failed' || monitor.health === 'degraded'
48
49
  || isolation.degraded
49
- || problems.some(problem => problem.severity === 'error' || problem.source === 'watchdog')
50
+ || problems.some(problem => problem.severity === 'error'
51
+ || problem.source === 'watchdog' || problem.source === 'daemon-recovery')
50
52
  || session.readiness === 'failed')
51
53
  return 'attention';
52
54
  // A reachable agent session is the user's live interaction surface. Its
@@ -74,7 +76,8 @@ export class FleetQueryService {
74
76
  this.control = options.control ?? controlRequest;
75
77
  }
76
78
  async list() {
77
- const roles = await this.options.repository.list();
79
+ const roles = (await this.options.repository.list())
80
+ .filter(role => role.configured && role.lifetime === 'permanent');
78
81
  return Promise.all(roles.map(async (role) => {
79
82
  const status = await this.status(role);
80
83
  return { role, status, capabilities: roleCapabilities(role, status, this.options.capabilityContext) };
@@ -103,6 +106,16 @@ export class FleetQueryService {
103
106
  const lastExit = dir ? readExitRecord(join(dir, '.exit-status')) ?? undefined : undefined;
104
107
  const session = await this.session(role, dir, live.state);
105
108
  const problems = [...role.problems];
109
+ const recovery = dir ? readDaemonRecoveryStatus(dir) : undefined;
110
+ if (recovery && recovery.state !== 'recovered') {
111
+ const paths = ['agent', 'owner']
112
+ .filter(name => recovery.paths[name].state !== 'recovered')
113
+ .map(name => `${name}:${recovery.paths[name].state}`);
114
+ problems.push({
115
+ code: 'daemon_recovery', severity: 'warning', source: 'daemon-recovery',
116
+ detail: `${recovery.state}; ${paths.join(', ') || 'no degraded path'}; epoch ${recovery.epoch || 'unavailable'}`,
117
+ });
118
+ }
106
119
  if (live.state === 'running' && session.reachability !== 'online')
107
120
  problems.push({
108
121
  code: 'supervisor_session_disagreement', severity: 'warning',
@@ -141,14 +154,16 @@ export class FleetQueryService {
141
154
  }
142
155
  async session(role, dir, supervisor) {
143
156
  const intended = role.configuredBackend;
144
- if (intended === 'acp' && dir) {
157
+ if (intended && dir) {
145
158
  try {
146
159
  const response = await this.control(dir, { command: 'snapshot' }, 2_000);
147
160
  if (!response.ok)
148
161
  throw new SessionControlError(response.kind ?? 'backend', response.error ?? 'snapshot failed');
149
162
  const snapshot = response.result;
163
+ if (snapshot.backend !== intended)
164
+ throw new SessionControlError('backend', `live session reports backend '${String(snapshot.backend)}', expected '${intended}'`);
150
165
  return {
151
- backend: 'acp', reachability: snapshot.alive ? 'online' : 'offline',
166
+ backend: intended, reachability: snapshot.alive ? 'online' : 'offline',
152
167
  readiness: snapshot.readiness, evidence: 'authoritative',
153
168
  sessionId: snapshot.sessionId, lastError: snapshot.lastError,
154
169
  pendingPermissionId: snapshot.pendingPermissionId,
@@ -162,7 +177,7 @@ export class FleetQueryService {
162
177
  const failure = error instanceof SessionControlError ? error.kind : 'backend';
163
178
  const offline = failure === 'offline';
164
179
  return {
165
- backend: 'acp',
180
+ backend: intended,
166
181
  reachability: offline ? 'offline'
167
182
  : failure === 'control-unavailable' ? 'unavailable' : 'unknown',
168
183
  readiness: offline ? 'failed' : 'unknown',
@@ -12,6 +12,10 @@ export class RoleLifecycleService {
12
12
  const config = input.config ?? loadConfig(this.options.configPath);
13
13
  const selected = input.roleIds.length ? input.roleIds : config.roles.map(role => role.name);
14
14
  for (const roleId of selected) {
15
+ if (config.agentTemplates?.[roleId] && !config.roles.some(role => role.name === roleId))
16
+ throw new FleetError('capability_unavailable', `'${roleId}' is an inert Agent Template; lifecycle commands target only persistent fleet/agents instances`);
17
+ if (!config.roles.some(role => role.name === roleId))
18
+ throw new FleetError('role_not_found', `no such role '${roleId}'`);
15
19
  const role = await this.options.repository.get(roleId);
16
20
  if (!role)
17
21
  throw new FleetError('role_not_found', `no such role '${roleId}'`);
@@ -51,6 +55,11 @@ export class RoleCommandService {
51
55
  throw new FleetError('idempotency_conflict', 'action ID already belongs to another operation');
52
56
  return prior;
53
57
  }
58
+ const config = loadConfig(this.options.configPath);
59
+ if (config.agentTemplates?.[input.roleId] && !config.roles.some(role => role.name === input.roleId))
60
+ throw new FleetError('capability_unavailable', `'${input.roleId}' is an inert Agent Template; lifecycle commands target only persistent fleet/agents instances`);
61
+ if (!config.roles.some(role => role.name === input.roleId))
62
+ throw new FleetError('role_not_found', `no such role '${input.roleId}'`);
54
63
  const role = await this.options.repository.get(input.roleId);
55
64
  if (!role)
56
65
  throw new FleetError('role_not_found', `no such role '${input.roleId}'`);
@@ -1,25 +1,18 @@
1
- import { type CommonPermissions, type MonitorConfig, type MonitorInterrupt, type NotifyEventType } from '../config.js';
1
+ import { type CommonPermissions, type MonitorConfig, type MonitorInterrupt, type NotifyEventType, type SessionBackendId } from '../config.js';
2
2
  import { type IdentityProvisioner } from '../creation.js';
3
3
  import { spawnDryRun, type SpawnOpts, type SupervisorLauncher } from '../spawn.js';
4
4
  import type { OpsDeps } from '../ops.js';
5
5
  import type { ResolvedRole } from '../config.js';
6
6
  import { FleetError } from './errors.js';
7
7
  import { type ManagedFleetSpawnResult } from '../fleet-proxy.js';
8
- import { type HarnessModelCatalog, type HarnessModelOption } from './model-catalog.js';
9
8
  export interface CreateRoleSessionRequest {
10
9
  name: string;
11
- harness: 'codex' | 'claude-code';
12
- /** null means the selected harness's own default; web blank fields send null. */
13
- model?: string | null;
14
- reasoningEffort?: string | null;
15
- session: 'acp';
10
+ brain: import('../config.js').AgentSelection;
11
+ role: import('../config.js').AgentSelection;
16
12
  cwd?: string;
17
13
  lifetime: 'permanent' | 'temporary';
18
- mission?: string;
19
14
  coordinator?: string;
20
15
  permissions: CommonPermissions;
21
- bio?: string;
22
- persona?: string;
23
16
  monitor?: WebCreationMonitor;
24
17
  openAfterCreate: boolean;
25
18
  highRiskAcknowledged?: boolean;
@@ -38,17 +31,6 @@ export type WebCreationMonitor = {
38
31
  export interface CreationCapabilities {
39
32
  available: boolean;
40
33
  reasons: string[];
41
- harnesses: Array<{
42
- id: 'codex' | 'claude-code';
43
- available: boolean;
44
- sessions: Array<'acp'>;
45
- defaultModel?: string;
46
- models: string[];
47
- modelOptions: HarnessModelOption[];
48
- catalogSource: string;
49
- customModelAllowed: true;
50
- warnings: string[];
51
- }>;
52
34
  lifetimes: Array<'permanent' | 'temporary'>;
53
35
  identityBootstrap: {
54
36
  mode: 'current-fleet-first-boot';
@@ -71,7 +53,7 @@ export interface CreationPreview {
71
53
  name: string;
72
54
  identity: string;
73
55
  harness: string;
74
- session: 'acp';
56
+ session: SessionBackendId;
75
57
  model?: string;
76
58
  reasoningEffort?: string;
77
59
  cwd?: string;
@@ -95,7 +77,7 @@ export interface CreationAction {
95
77
  actionId: string;
96
78
  requestHash: string;
97
79
  roleId: string;
98
- session: 'acp';
80
+ session: SessionBackendId;
99
81
  lifetime: 'permanent' | 'temporary';
100
82
  state: CreationStage;
101
83
  stages: Array<{
@@ -121,9 +103,8 @@ export interface RoleCreationServiceOptions {
121
103
  tempLauncher?: SupervisorLauncher;
122
104
  allowedCwdRoots?: string[];
123
105
  journalDir?: string;
124
- probeReady?: (name: string, session: 'acp') => Promise<'ready' | 'attention' | 'unknown'>;
106
+ probeReady?: (name: string, session: SessionBackendId) => Promise<'ready' | 'attention' | 'unknown'>;
125
107
  onProgress?: (action: CreationAction) => void;
126
- modelCatalogs?: Partial<Record<'codex' | 'claude-code', () => HarnessModelCatalog>>;
127
108
  /** Direct/managed callers must not create or restore the web action journal. */
128
109
  journal?: boolean;
129
110
  }
@@ -1,17 +1,16 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
3
3
  import { isAbsolute, relative } from 'node:path';
4
- import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig, resolveRoleModel, resolvePermissions, ROLE_NAME_RE, validateMonitorConfig, } from '../config.js';
4
+ import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig, resolvePermissions, ROLE_NAME_RE, validateMonitorConfig, } from '../config.js';
5
5
  import { daemonIdentityProvisioner, } from '../creation.js';
6
6
  import { replaceFileAtomically } from '../atomic-file.js';
7
7
  import { stateRoot } from '../paths.js';
8
- import { buildRoleConfig, spawnDryRun, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
8
+ import { spawnDryRun, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
9
9
  import { FleetError, normalizeError } from './errors.js';
10
10
  import { inheritCallerSpawnDefaults } from '../fleet-proxy.js';
11
11
  import { effectivePermissionMode } from '../permissions.js';
12
12
  import { effectiveRoleModel } from '../model-env.js';
13
- import { getAdapter } from '../harness/registry.js';
14
- import { claudeModelCatalog, codexModelCatalog, } from './model-catalog.js';
13
+ import { selectionOrigin, summarizeResolvedLaunch } from '../lifecycle-summary.js';
15
14
  const canonical = (value) => {
16
15
  if (Array.isArray(value))
17
16
  return `[${value.map(canonical).join(',')}]`;
@@ -63,14 +62,31 @@ export class RoleCreationService {
63
62
  const creationActionId = randomUUID();
64
63
  plan.options.creationActionId = creationActionId;
65
64
  const statePath = await this.launchSync(plan.options);
65
+ const selectionSummary = (kind) => {
66
+ const value = plan.options[kind];
67
+ const provenance = plan.inherited.includes(kind) ? 'inherited' : 'explicit';
68
+ if (!value)
69
+ return `unresolved (${provenance})`;
70
+ if ('ref' in value)
71
+ return `ref:${value.ref} (${provenance})`;
72
+ const fingerprint = hash(value.inline).slice(0, 16);
73
+ return `inline:sha256:${fingerprint} (${provenance})`;
74
+ };
75
+ const permissionMode = effectivePermissionMode(plan.preview.resolvedRole);
66
76
  return {
67
77
  caller: plan.caller, role: plan.options.name,
68
78
  lifetime: plan.options.temp ? 'temporary' : 'permanent', statePath,
69
79
  harness: plan.preview.resolvedRole.harness, session: plan.preview.resolvedRole.session,
70
80
  ...(effectiveRoleModel(plan.preview.resolvedRole) ? { model: effectiveRoleModel(plan.preview.resolvedRole) } : {}),
71
81
  monitor: { mode: plan.preview.resolvedRole.monitor.mode, interrupt: plan.preview.resolvedRole.monitor.interrupt },
72
- permissionMode: effectivePermissionMode(plan.preview.resolvedRole), inherited: plan.inherited,
82
+ permissionMode, inherited: plan.inherited,
73
83
  creationActionId,
84
+ brainSummary: selectionSummary('brain'), roleSummary: selectionSummary('role'),
85
+ configuration: summarizeResolvedLaunch(plan.preview.resolvedRole, {
86
+ role: selectionOrigin(plan.options.role ?? plan.options.agentDefinition?.role),
87
+ brain: selectionOrigin(plan.options.brain ?? plan.options.agentDefinition?.brain),
88
+ permissionMode,
89
+ }),
74
90
  };
75
91
  }
76
92
  launchSync(options, creation) {
@@ -81,47 +97,16 @@ export class RoleCreationService {
81
97
  async capabilities() {
82
98
  const reasons = [];
83
99
  let defaults = {};
84
- let roles = [];
85
100
  try {
86
101
  const config = loadConfig(this.options.configPath);
87
102
  defaults = config.defaults;
88
- roles = config.roles;
89
103
  }
90
104
  catch (error) {
91
105
  reasons.push(`configuration is invalid: ${error.message}`);
92
106
  }
93
- const modelsFor = (harness, catalog) => {
94
- const configured = roles.filter(role => role.harness === harness)
95
- .flatMap(role => [role.model, ...(role.model_chain ?? [])])
96
- .filter((model) => Boolean(model));
97
- const inherited = resolveRoleModel(undefined, harness, defaults);
98
- const configuredOptions = [...new Set([...(inherited ? [inherited] : []), ...configured])]
99
- .filter(id => !catalog.models.some(model => model.id === id))
100
- .map(id => ({ id, label: `${id} (configured)`, reasoningEfforts: [], source: 'fleet-config' }));
101
- const modelOptions = [...configuredOptions, ...catalog.models];
102
- return { modelOptions, models: modelOptions.map(model => model.id) };
103
- };
104
- const codexCatalog = this.options.modelCatalogs?.codex?.() ?? codexModelCatalog();
105
- const claudeCatalog = this.options.modelCatalogs?.['claude-code']?.() ?? claudeModelCatalog();
106
- const codexModels = modelsFor('codex', codexCatalog);
107
- const claudeModels = modelsFor('claude-code', claudeCatalog);
108
107
  return {
109
108
  available: reasons.length === 0,
110
109
  reasons,
111
- harnesses: [
112
- {
113
- id: 'codex', available: true, sessions: ['acp'],
114
- defaultModel: resolveRoleModel(undefined, 'codex', defaults),
115
- ...codexModels, catalogSource: 'codex-runtime-catalog', customModelAllowed: true,
116
- warnings: codexCatalog.warnings,
117
- },
118
- {
119
- id: 'claude-code', available: true, sessions: ['acp'],
120
- defaultModel: resolveRoleModel(undefined, 'claude-code', defaults),
121
- ...claudeModels, catalogSource: 'claude-adapter-2.1', customModelAllowed: true,
122
- warnings: claudeCatalog.warnings,
123
- },
124
- ],
125
110
  lifetimes: ['permanent', 'temporary'],
126
111
  identityBootstrap: {
127
112
  mode: 'current-fleet-first-boot',
@@ -145,14 +130,12 @@ export class RoleCreationService {
145
130
  const defaults = cfg.defaults;
146
131
  const opts = this.spawnOptions(request);
147
132
  validateSpawnOpts(opts);
148
- buildRoleConfig(opts, defaults.harness);
133
+ const resolved = spawnDryRun(opts).resolvedRole;
149
134
  const cwd = request.cwd ? this.resolveCwd(request.cwd) : undefined;
150
135
  const effective = {
151
136
  name: request.name, identity: request.name,
152
- harness: request.harness ?? defaults.harness ?? 'claude-code',
153
- session: request.session ?? defaults.session ?? 'acp',
154
- model: resolveRoleModel(request.model, request.harness, defaults),
155
- reasoningEffort: request.reasoningEffort ?? undefined,
137
+ harness: resolved.harness, session: resolved.session, model: resolved.model,
138
+ reasoningEffort: resolved.effort,
156
139
  cwd, lifetime: request.lifetime,
157
140
  permissions: resolvePermissions(defaults.permissions, request.permissions),
158
141
  monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
@@ -184,9 +167,7 @@ export class RoleCreationService {
184
167
  if (existingIdentity === 'unknown' && !request.unverifiedIdentityAcknowledged)
185
168
  prerequisites.push('confirm creation with an unverified identity preflight');
186
169
  const provenance = {
187
- harness: 'request', session: 'request', identity: 'built-in',
188
- model: request.model !== undefined ? 'request'
189
- : resolveRoleModel(undefined, request.harness, defaults) ? 'fleet-default' : 'built-in',
170
+ brain: 'request', role: 'request', identity: 'built-in',
190
171
  cwd: request.cwd ? 'request' : 'built-in', permissions: 'request',
191
172
  monitor: request.monitor ? 'request' : defaults.monitor ? 'fleet-default' : 'built-in',
192
173
  };
@@ -297,8 +278,8 @@ export class RoleCreationService {
297
278
  }
298
279
  validate(input) {
299
280
  const allowed = new Set([
300
- 'name', 'harness', 'model', 'reasoningEffort', 'session', 'cwd', 'lifetime', 'mission',
301
- 'coordinator', 'permissions', 'bio', 'persona', 'monitor', 'openAfterCreate',
281
+ 'name', 'brain', 'role', 'cwd', 'lifetime',
282
+ 'coordinator', 'permissions', 'monitor', 'openAfterCreate',
302
283
  'highRiskAcknowledged', 'reuseExistingIdentityAcknowledged',
303
284
  'unverifiedIdentityAcknowledged',
304
285
  ]);
@@ -308,26 +289,11 @@ export class RoleCreationService {
308
289
  throw new FleetError('invalid_request', `unsupported web creation field: ${unsupported[0]}`);
309
290
  if (!ROLE_NAME_RE.test(input.name))
310
291
  throw new FleetError('invalid_request', 'invalid role name');
311
- if (!['codex', 'claude-code'].includes(input.harness))
312
- throw new FleetError('invalid_request', 'unsupported harness');
313
- if (input.session !== 'acp')
314
- throw new FleetError('invalid_request', 'unsupported session backend');
292
+ if (!input.brain || !input.role)
293
+ throw new FleetError('invalid_request', 'brain and role selections are required');
315
294
  if (!['permanent', 'temporary'].includes(input.lifetime))
316
295
  throw new FleetError('invalid_request', 'unsupported lifetime');
317
- bounded(input.model ?? undefined, 'model', 128);
318
- bounded(input.reasoningEffort ?? undefined, 'reasoning effort', 16);
319
- if (input.reasoningEffort != null) {
320
- try {
321
- getAdapter(input.harness).agentSession.resolveBrain({ effort: input.reasoningEffort });
322
- }
323
- catch (error) {
324
- throw new FleetError('invalid_request', error.message);
325
- }
326
- }
327
- bounded(input.mission, 'mission', 4_096);
328
296
  bounded(input.coordinator, 'coordinator', 128);
329
- bounded(input.bio, 'bio', 8_192);
330
- bounded(input.persona, 'persona', 16_384);
331
297
  resolvePermissions(undefined, input.permissions);
332
298
  if (input.monitor) {
333
299
  const problems = validateMonitorConfig(input.monitor);
@@ -337,10 +303,8 @@ export class RoleCreationService {
337
303
  throw new FleetError('invalid_request', 'web creation supports monitor.inject=notification only');
338
304
  }
339
305
  return {
340
- ...input, model: input.model === null ? null : input.model?.trim() || undefined,
341
- reasoningEffort: input.reasoningEffort === null ? null : input.reasoningEffort?.trim() || undefined,
342
- mission: input.mission?.trim() || undefined, coordinator: input.coordinator?.trim() || undefined,
343
- bio: input.bio?.trim() || undefined, persona: input.persona?.trim() || undefined,
306
+ ...input, brain: structuredClone(input.brain), role: structuredClone(input.role),
307
+ coordinator: input.coordinator?.trim() || undefined,
344
308
  monitor: input.monitor ? structuredClone(input.monitor) : undefined,
345
309
  };
346
310
  }
@@ -367,12 +331,10 @@ export class RoleCreationService {
367
331
  spawnOptions(request, creationActionId) {
368
332
  return {
369
333
  name: request.name, temp: request.lifetime === 'temporary',
370
- identity: request.name, harness: request.harness,
371
- model: request.model, session: request.session, cwd: request.cwd,
372
- reasoningEffort: request.reasoningEffort,
373
- mission: request.mission, coordinator: request.coordinator,
334
+ identity: request.name, brain: request.brain, role: request.role, cwd: request.cwd,
335
+ coordinator: request.coordinator,
374
336
  approval: request.permissions.approval, filesystem: request.permissions.filesystem,
375
- unattended: request.permissions.unattended, bio: request.bio, persona: request.persona,
337
+ unattended: request.permissions.unattended,
376
338
  monitorConfig: request.monitor,
377
339
  configPath: this.options.configPath,
378
340
  surface: creationActionId ? 'web' : undefined, creationActionId,
@@ -1,10 +1,12 @@
1
+ import { type SessionBackendId } from '../config.js';
1
2
  import type { RoleRecord } from './types.js';
2
3
  export interface RoleRepositoryOptions {
3
4
  configPath?: string;
4
5
  permanentRoot?: string;
5
6
  temporaryRoot?: string;
6
- probeBackend?: (name: string, intended?: 'acp') => Promise<{
7
- acp: boolean;
7
+ probeBackend?: (name: string, intended?: SessionBackendId) => Promise<{
8
+ acp?: boolean;
9
+ backend?: SessionBackendId;
8
10
  }>;
9
11
  concurrency?: number;
10
12
  timeoutMs?: number;
@@ -44,9 +44,10 @@ function snapshot(path, name) {
44
44
  throw new Error('snapshot must be a mapping');
45
45
  if (raw.session === 'tmux')
46
46
  throw new Error('session: tmux is no longer supported; use session: acp with the Codex or Claude Code adapter');
47
- if (raw.session !== undefined && raw.session !== 'acp')
48
- throw new Error(`unsupported session '${String(raw.session)}'; expected session: acp`);
49
- const session = 'acp';
47
+ if (raw.session !== undefined && raw.session !== 'acp'
48
+ && raw.session !== 'codex-app-server')
49
+ throw new Error(`unsupported session '${String(raw.session)}'; expected session: acp or codex-app-server`);
50
+ const session = raw.session ?? 'acp';
50
51
  return {
51
52
  role: {
52
53
  ...raw, name, harness: typeof raw.harness === 'string' ? raw.harness : 'claude-code',
@@ -173,6 +174,8 @@ export class RoleRepository {
173
174
  this.options.probeBackend(name, intended),
174
175
  new Promise(resolve => setTimeout(() => resolve({ acp: false }), timeoutMs)),
175
176
  ]).catch(() => ({ acp: false }));
177
+ if (result.backend)
178
+ return result.backend;
176
179
  if (result.acp)
177
180
  return 'acp';
178
181
  return 'none';
@@ -71,7 +71,7 @@ export interface RoleSessionControl {
71
71
  onClose(reason?: string): void;
72
72
  }): Promise<ConversationFollowHandle>;
73
73
  }
74
- export declare class AcpRoleSessionAdapter implements RoleSessionControl {
74
+ export declare class RoleSessionControlAdapter implements RoleSessionControl {
75
75
  private readonly stateDir;
76
76
  private readonly request;
77
77
  constructor(stateDir: string, request?: typeof controlRequest);
@@ -119,3 +119,5 @@ export declare class AcpRoleSessionAdapter implements RoleSessionControl {
119
119
  }>;
120
120
  private call;
121
121
  }
122
+ /** @deprecated Compatibility name; control is provider-neutral. */
123
+ export { RoleSessionControlAdapter as AcpRoleSessionAdapter };
@@ -1,7 +1,7 @@
1
1
  import { controlRequest, followConversation } from '../session/control.js';
2
2
  import { FleetError, normalizeError } from './errors.js';
3
3
  const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
4
- export class AcpRoleSessionAdapter {
4
+ export class RoleSessionControlAdapter {
5
5
  stateDir;
6
6
  request;
7
7
  constructor(stateDir, request = controlRequest) {
@@ -12,7 +12,7 @@ export class AcpRoleSessionAdapter {
12
12
  const response = await this.call('snapshot');
13
13
  const result = response;
14
14
  return {
15
- backend: 'acp', protocolVersion: result.protocolVersion ?? 1,
15
+ backend: result.backend, protocolVersion: result.protocolVersion ?? 1,
16
16
  features: result.features ?? [], snapshot: result,
17
17
  };
18
18
  }
@@ -147,3 +147,5 @@ export class AcpRoleSessionAdapter {
147
147
  }
148
148
  }
149
149
  }
150
+ /** @deprecated Compatibility name; control is provider-neutral. */
151
+ export { RoleSessionControlAdapter as AcpRoleSessionAdapter };
@@ -2,7 +2,10 @@ import { type FleetConfig } from '../config.js';
2
2
  import { type CoworkAdapter } from '../rooms-tasks/cowork-adapter.js';
3
3
  import { provisionMembers } from '../rooms-tasks/provision.js';
4
4
  import { moveTaskToList } from '../rooms-tasks/task-state.js';
5
- import type { RoomOrchestrationRecord, TaskListRecord, TaskOrigin, TaskOutcome, TaskRecord, TaskState } from '../rooms-tasks/types.js';
5
+ import { type MemberOverrides } from '../rooms-tasks/member-overrides.js';
6
+ import { type TaskDeletionSettleResult } from '../rooms-tasks/deletion.js';
7
+ import type { RoomLaunchPolicy, RoomOrchestrationRecord, TaskListRecord, TaskOrigin, TaskOutcome, TaskRecord, TaskState, TemplateSnapshot } from '../rooms-tasks/types.js';
8
+ import type { TaskDeletionAcceptance } from '../rooms-tasks/task-state.js';
6
9
  export type TaskRoomActor = {
7
10
  kind: 'local_control';
8
11
  surface: 'cli' | 'web';
@@ -37,14 +40,17 @@ export interface TaskRecoveryResult {
37
40
  export type TaskRecoveryBegin = {
38
41
  kind: 'terminal_worker_required';
39
42
  taskId: string;
43
+ } | {
44
+ kind: 'deletion_worker_required';
45
+ taskId: string;
40
46
  } | {
41
47
  kind: 'final';
42
48
  result: TaskRecoveryResult;
43
49
  };
44
50
  export declare class TaskRoomApplicationError extends Error {
45
- readonly code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'room_not_found' | 'room_record_not_found';
51
+ readonly code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'task_deleting' | 'room_not_found' | 'room_record_not_found';
46
52
  readonly fields: Readonly<Record<string, string>>;
47
- constructor(code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'room_not_found' | 'room_record_not_found', message: string, fields?: Readonly<Record<string, string>>);
53
+ constructor(code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'task_deleting' | 'room_not_found' | 'room_record_not_found', message: string, fields?: Readonly<Record<string, string>>);
48
54
  }
49
55
  export interface CreateTaskRequest {
50
56
  actor: TaskRoomActor;
@@ -57,6 +63,8 @@ export interface CreateTaskRequest {
57
63
  idempotencyKey?: string;
58
64
  origin: TaskOrigin;
59
65
  list?: string;
66
+ members?: MemberOverrides;
67
+ anonymous?: boolean;
60
68
  }
61
69
  export interface CreateRoomRequest {
62
70
  actor: TaskRoomActor;
@@ -65,13 +73,19 @@ export interface CreateRoomRequest {
65
73
  goal?: string;
66
74
  brief?: string;
67
75
  briefFile?: string;
76
+ members?: MemberOverrides;
77
+ anonymous?: boolean;
68
78
  }
79
+ export declare function resolveRoomLaunchPolicy(template: TemplateSnapshot | undefined, override: boolean | undefined): RoomLaunchPolicy;
69
80
  export interface TaskRoomServiceDeps {
70
81
  loadConfiguration?(path?: string): FleetConfig;
71
82
  cowork?(config: FleetConfig): CoworkAdapter;
72
83
  binPath?(): string;
73
84
  provisionMembers?: typeof provisionMembers;
74
85
  moveTaskToList?: typeof moveTaskToList;
86
+ launchDeletionWorker?(taskId: string): Promise<void>;
87
+ sleep?(ms: number): Promise<void>;
88
+ now?(): number;
75
89
  }
76
90
  /** Exact extraction of the previously CLI-owned task create/start behavior. */
77
91
  export declare class TaskRoomApplicationService {
@@ -84,15 +98,20 @@ export declare class TaskRoomApplicationService {
84
98
  startTask(input: {
85
99
  actor: TaskRoomActor;
86
100
  taskId: string;
101
+ template?: string;
102
+ members?: MemberOverrides;
103
+ anonymous?: boolean;
87
104
  }): Promise<TaskRecord>;
88
105
  listTasks(filter?: {
89
106
  state?: TaskState | TaskState[];
90
107
  list?: string;
108
+ includeDeleting?: boolean;
91
109
  }): TaskRecord[];
92
110
  listTaskLists(): TaskListRecord[];
93
111
  groupedTasks(filter?: {
94
112
  state?: TaskState | TaskState[];
95
113
  list?: string;
114
+ includeDeleting?: boolean;
96
115
  }): Array<{
97
116
  list: TaskListRecord;
98
117
  tasks: TaskRecord[];
@@ -137,10 +156,40 @@ export declare class TaskRoomApplicationService {
137
156
  actor: TaskRoomActor;
138
157
  taskId: string;
139
158
  }): TaskRecord;
140
- deleteTask(input: {
159
+ private deletionActor;
160
+ /** Accept a permanent deletion in any lifecycle state; audit at acceptance. */
161
+ requestTaskDeletion(input: {
162
+ actor: TaskRoomActor;
163
+ taskId: string;
164
+ }): Promise<TaskDeletionAcceptance>;
165
+ /** Worker entry: converge an accepted deletion; Cowork is resolved lazily. */
166
+ settleTaskDeletion(input: {
167
+ actor: {
168
+ kind: 'internal_worker';
169
+ surface: 'cli';
170
+ };
171
+ taskId: string;
172
+ }): Promise<TaskDeletionSettleResult>;
173
+ /**
174
+ * Launch the external deletion worker outside the caller's lifecycle and
175
+ * wait boundedly for physical absence. Timeouts and launch failures report
176
+ * a pending, recoverable state — never success. A concurrent worker that
177
+ * already removed the record reads as settled.
178
+ */
179
+ launchTaskDeletionWorker(input: {
180
+ taskId: string;
181
+ waitMs?: number;
182
+ }): Promise<{
183
+ deleted: boolean;
184
+ pending: boolean;
185
+ error?: string;
186
+ }>;
187
+ recordDeletionError(input: {
141
188
  actor: TaskRoomActor;
142
189
  taskId: string;
143
- }): boolean;
190
+ error: string;
191
+ recoveryHint: string;
192
+ }): Promise<TaskRecord>;
144
193
  completeTask(input: {
145
194
  actor: TaskRoomActor;
146
195
  taskId: string;
@@ -179,7 +228,7 @@ export declare class TaskRoomApplicationService {
179
228
  name: string;
180
229
  version: number;
181
230
  description: string;
182
- builtin?: boolean;
231
+ sourceFile?: string;
183
232
  room?: import("../rooms-tasks/types.js").TemplateRoomConfig;
184
233
  contract?: string;
185
234
  members: import("../rooms-tasks/types.js").TemplateMemberSlot[];
@@ -197,6 +246,7 @@ export declare class TaskRoomApplicationService {
197
246
  identity_cid: string;
198
247
  room_name: string;
199
248
  state: "provisioning" | "active" | "closing" | "closed";
249
+ anonymous: boolean;
200
250
  seats: import("../rooms-tasks/cowork-adapter.js").CoworkSeatInfo[];
201
251
  goal?: string;
202
252
  briefing?: string;
@@ -252,6 +302,8 @@ export declare class TaskRoomApplicationService {
252
302
  actor: TaskRoomActor;
253
303
  taskId: string;
254
304
  template?: string;
305
+ members?: MemberOverrides;
306
+ anonymous?: boolean;
255
307
  }): Promise<{
256
308
  task: TaskRecord;
257
309
  status: 'ready' | 'already_active';