@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +169 -54
  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 +426 -78
  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 +227 -73
  20. package/dist/config-yaml.d.ts +1 -1
  21. package/dist/config.d.ts +67 -2
  22. package/dist/config.js +260 -30
  23. package/dist/daemon-recovery.d.ts +93 -0
  24. package/dist/daemon-recovery.js +328 -0
  25. package/dist/docs.d.ts +1 -1
  26. package/dist/docs.js +171 -66
  27. package/dist/doctor.d.ts +2 -0
  28. package/dist/doctor.js +54 -4
  29. package/dist/fleet-command-audit.d.ts +165 -0
  30. package/dist/fleet-command-audit.js +543 -0
  31. package/dist/fleet-proxy.d.ts +7 -2
  32. package/dist/fleet-proxy.js +30 -8
  33. package/dist/harness/agent-session.d.ts +5 -0
  34. package/dist/harness/claude-code-session.d.ts +2 -0
  35. package/dist/harness/claude-code-session.js +6 -1
  36. package/dist/harness/claude-code.js +2 -0
  37. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  38. package/dist/harness/codex-app-server-proxy.js +348 -14
  39. package/dist/harness/codex-session.d.ts +11 -2
  40. package/dist/harness/codex-session.js +22 -2
  41. package/dist/harness/codex.d.ts +5 -1
  42. package/dist/harness/codex.js +110 -10
  43. package/dist/harness/types.d.ts +1 -1
  44. package/dist/index.d.ts +5 -4
  45. package/dist/index.js +4 -3
  46. package/dist/init-guidance.d.ts +1 -1
  47. package/dist/init-guidance.js +4 -3
  48. package/dist/init-wizard.d.ts +98 -0
  49. package/dist/init-wizard.js +629 -0
  50. package/dist/lifecycle-summary.d.ts +118 -0
  51. package/dist/lifecycle-summary.js +161 -0
  52. package/dist/loops/config.js +3 -2
  53. package/dist/model-env.d.ts +2 -2
  54. package/dist/model-env.js +1 -1
  55. package/dist/model-recovery.d.ts +1 -1
  56. package/dist/ops.js +2 -17
  57. package/dist/owner-channel/channel.d.ts +58 -0
  58. package/dist/owner-channel/channel.js +321 -42
  59. package/dist/owner-channel/commands.d.ts +6 -1
  60. package/dist/owner-channel/commands.js +34 -19
  61. package/dist/owner-channel/state.d.ts +2 -0
  62. package/dist/owner-channel/state.js +8 -2
  63. package/dist/permissions.js +1 -1
  64. package/dist/preset-bootstrap.d.ts +10 -0
  65. package/dist/preset-bootstrap.js +85 -0
  66. package/dist/preset-migration.d.ts +42 -0
  67. package/dist/preset-migration.js +414 -0
  68. package/dist/reports/artifact.d.ts +10 -0
  69. package/dist/reports/artifact.js +32 -0
  70. package/dist/reports/index.d.ts +5 -0
  71. package/dist/reports/index.js +5 -0
  72. package/dist/reports/render.d.ts +6 -0
  73. package/dist/reports/render.js +240 -0
  74. package/dist/reports/service.d.ts +42 -0
  75. package/dist/reports/service.js +57 -0
  76. package/dist/reports/tasks.d.ts +52 -0
  77. package/dist/reports/tasks.js +177 -0
  78. package/dist/reports/types.d.ts +315 -0
  79. package/dist/reports/types.js +6 -0
  80. package/dist/resolved-plan.js +4 -0
  81. package/dist/rooms-tasks/cli.d.ts +2 -0
  82. package/dist/rooms-tasks/cli.js +503 -75
  83. package/dist/rooms-tasks/close.d.ts +9 -1
  84. package/dist/rooms-tasks/close.js +20 -4
  85. package/dist/rooms-tasks/config.js +16 -14
  86. package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
  87. package/dist/rooms-tasks/cowork-adapter.js +6 -0
  88. package/dist/rooms-tasks/deletion.d.ts +51 -0
  89. package/dist/rooms-tasks/deletion.js +217 -0
  90. package/dist/rooms-tasks/external-worker.d.ts +1 -0
  91. package/dist/rooms-tasks/external-worker.js +14 -9
  92. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  93. package/dist/rooms-tasks/launch-snapshot.js +176 -0
  94. package/dist/rooms-tasks/member-overrides.d.ts +27 -0
  95. package/dist/rooms-tasks/member-overrides.js +170 -0
  96. package/dist/rooms-tasks/provision.js +179 -49
  97. package/dist/rooms-tasks/room-state.d.ts +1 -0
  98. package/dist/rooms-tasks/room-state.js +12 -1
  99. package/dist/rooms-tasks/task-state.d.ts +115 -3
  100. package/dist/rooms-tasks/task-state.js +361 -11
  101. package/dist/rooms-tasks/templates.d.ts +4 -2
  102. package/dist/rooms-tasks/templates.js +42 -78
  103. package/dist/rooms-tasks/terminal.d.ts +3 -0
  104. package/dist/rooms-tasks/terminal.js +7 -3
  105. package/dist/rooms-tasks/types.d.ts +94 -17
  106. package/dist/rooms-tasks/types.js +14 -4
  107. package/dist/runner.d.ts +9 -2
  108. package/dist/runner.js +107 -40
  109. package/dist/session/acp.d.ts +9 -1
  110. package/dist/session/acp.js +35 -9
  111. package/dist/session/arbiter.d.ts +2 -1
  112. package/dist/session/arbiter.js +2 -0
  113. package/dist/session/codex-app-server-transport.d.ts +53 -0
  114. package/dist/session/codex-app-server-transport.js +184 -0
  115. package/dist/session/codex-app-server.d.ts +122 -0
  116. package/dist/session/codex-app-server.js +1031 -0
  117. package/dist/session/control.d.ts +14 -1
  118. package/dist/session/control.js +38 -0
  119. package/dist/session/conversation-types.d.ts +4 -4
  120. package/dist/session/types.d.ts +24 -6
  121. package/dist/session/types.js +28 -0
  122. package/dist/spawn.d.ts +6 -31
  123. package/dist/spawn.js +57 -234
  124. package/dist/temp-lifecycle.d.ts +1 -1
  125. package/dist/watchdog/config.d.ts +10 -7
  126. package/dist/watchdog/config.js +29 -19
  127. package/dist/watchdog/run.js +4 -13
  128. package/dist/web/fleet-config-service.d.ts +1 -0
  129. package/dist/web/fleet-config-service.js +27 -8
  130. package/dist/web/runtime.js +11 -6
  131. package/dist/web/server.js +28 -1
  132. package/dist/web/topology-promote.js +8 -1
  133. package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
  134. package/dist/web-app/index.html +1 -1
  135. package/package.json +3 -2
  136. package/presets/brain-catalog.json +16 -0
  137. package/presets/fleet/agent_templates/Critic.yaml +4 -0
  138. package/presets/fleet/agent_templates/Developer.yaml +4 -0
  139. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
  140. package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
  141. package/presets/fleet/brains/claude-default.yaml +5 -0
  142. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  143. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  144. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  145. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  146. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  147. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  148. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  149. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  150. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  151. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  152. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  153. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  154. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  155. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  156. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  157. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  158. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  159. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  188. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  189. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  190. package/presets/fleet/roles/Coordinator.yaml +30 -0
  191. package/presets/fleet/roles/Critic.yaml +19 -0
  192. package/presets/fleet/roles/Developer.yaml +19 -0
  193. package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
  194. package/presets/fleet/room_templates/pair.yaml +10 -0
  195. package/presets/fleet/room_templates/single.yaml +7 -0
  196. package/presets/fleet/room_templates/team.yaml +12 -0
  197. package/presets/fleet.yaml +7 -0
  198. package/presets/manifest.json +6 -0
  199. package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
package/dist/spawn.js CHANGED
@@ -4,8 +4,7 @@ import { join } from 'node:path';
4
4
  import { parse, stringify } from 'yaml';
5
5
  import { agentDir, defaultConfigPath } from './paths.js';
6
6
  import { validateIsolationConfig } from './isolation/policy.js';
7
- import { loadConfig, resolveAuthProxy, resolveModelChain, resolveMonitorConfig, resolveOwnerChannelConfig, resolvePermissions, resolveRoleModel, resolveWorklogPolicy, splitRootFor, validateMonitorConfig, } from './config.js';
8
- import { resolveRoleModelEnv } from './model-env.js';
7
+ import { loadConfig, findRole, splitRootFor, validateMonitorConfig, } from './config.js';
9
8
  import { applyRole, up } from './ops.js';
10
9
  import { START_STAGGER_FILE } from './runner.js';
11
10
  import { buildProvenance, daemonIdentityProvisioner, ensureIdentity, provenanceOf, withCreationTransaction, writeProvenance, writeRoleFile, } from './creation.js';
@@ -13,86 +12,46 @@ import { VERSION } from './version.js';
13
12
  import { recordGeneratedAgentSource } from './generated-agent-source.js';
14
13
  import './harness/claude-code.js';
15
14
  import './harness/codex.js';
16
- import { getAdapter } from './harness/registry.js';
17
15
  import { archiveTempState, makeTempSupervisorLauncher, prepareTempSupervisor, reclaimStaleTempState, } from './temp-lifecycle.js';
18
16
  /**
19
17
  * The provenance record written by the most recent spawn in this process, so
20
18
  * the CLI can print the same summary it persisted rather than rebuilding it.
21
19
  */
22
20
  export let lastProvenance;
23
- export function profileValues(o) {
24
- if (o.bio !== undefined && o.bioFile)
25
- throw new Error('bio and bioFile are mutually exclusive');
26
- if (o.persona !== undefined && o.personaFile)
27
- throw new Error('persona and personaFile are mutually exclusive');
21
+ export function agentDefinitionFromSpawn(o) {
22
+ if (o.agentDefinition) {
23
+ if (o.brain !== undefined || o.role !== undefined || o.cwd !== undefined
24
+ || o.coordinator !== undefined || o.approval !== undefined || o.filesystem !== undefined
25
+ || o.unattended !== undefined || o.isolationFile !== undefined || o.monitorConfig !== undefined)
26
+ throw new Error('canonical agentDefinition conflicts with separate Agent fields');
27
+ const definition = structuredClone(o.agentDefinition);
28
+ if (o.identity)
29
+ definition.identity = o.identity;
30
+ return definition;
31
+ }
32
+ if (!o.role)
33
+ throw new Error('--role is required (declared ID or inline mapping)');
34
+ if (!o.brain)
35
+ throw new Error('--brain is required (declared ID or inline mapping)');
36
+ const permissions = o.approval || o.filesystem || o.unattended ? {
37
+ ...(o.approval ? { approval: o.approval } : {}),
38
+ ...(o.filesystem ? { filesystem: o.filesystem } : {}),
39
+ ...(o.unattended ? { unattended: o.unattended } : {}),
40
+ } : undefined;
28
41
  return {
29
- bio: o.bio !== undefined ? o.bio.trim()
30
- : o.bioFile ? readFileSync(o.bioFile, 'utf8').trim() : undefined,
31
- persona: o.persona !== undefined ? o.persona.trim()
32
- : o.personaFile ? readFileSync(o.personaFile, 'utf8').trim() : undefined,
42
+ role: structuredClone(o.role), brain: structuredClone(o.brain),
43
+ ...(o.identity ? { identity: o.identity } : {}),
44
+ ...(o.cwd ? { cwd: o.cwd } : {}),
45
+ ...(o.coordinator ? { coordinator: o.coordinator } : {}),
46
+ ...(permissions ? { permissions } : {}),
47
+ ...(o.isolationFile ? { isolation: readIsolationFile(o.isolationFile) } : {}),
48
+ ...(o.monitorConfig ? { monitor: structuredClone(o.monitorConfig) } : {}),
33
49
  };
34
50
  }
35
- /** Pure option-to-role mapping shared by CLI and application services. */
36
- export function buildRoleConfig(o, defaultHarness) {
37
- const r = {};
38
- const harness = o.harness ?? defaultHarness;
39
- if (harness)
40
- r.harness = harness;
41
- if (o.session)
42
- r.session = o.session;
43
- if (o.identity)
44
- r.identity = o.identity;
45
- if (o.cwd)
46
- r.cwd = o.cwd;
47
- if (o.coordinator)
48
- r.coordinator = o.coordinator;
49
- if (o.missionFile)
50
- r.mission = readMissionFile(o.missionFile);
51
- else if (o.mission !== undefined)
52
- r.mission = o.mission;
53
- if (o.model === null)
54
- r.model = null;
55
- else if (o.model?.trim())
56
- r.model = o.model.trim();
57
- if (o.reasoningEffort?.trim())
58
- r.effort = o.reasoningEffort.trim();
59
- const harnessOptions = {};
60
- if (o.permissionMode)
61
- harnessOptions[harness === 'claude-code' ? 'permission_mode' : 'approval'] = o.permissionMode;
62
- if (o.sandbox)
63
- harnessOptions.sandbox = o.sandbox;
64
- if (o.profile)
65
- harnessOptions.profile = o.profile;
66
- if (o.launcher)
67
- harnessOptions.launcher = o.launcher;
68
- if (o.search === true)
69
- harnessOptions.search = true;
70
- const codexConfig = { ...(o.codexConfig ?? {}) };
71
- if (Object.keys(codexConfig).length)
72
- harnessOptions.config = codexConfig;
73
- if (o.addDirs?.length)
74
- harnessOptions.add_dirs = o.addDirs;
75
- if (o.monitor === true)
76
- harnessOptions.monitor = true;
77
- if (Object.keys(harnessOptions).length)
78
- r.harness_options = harnessOptions;
79
- if (o.approval || o.filesystem || o.unattended) {
80
- r.permissions = {
81
- ...(o.approval ? { approval: o.approval } : {}),
82
- ...(o.filesystem ? { filesystem: o.filesystem } : {}),
83
- ...(o.unattended ? { unattended: o.unattended } : {}),
84
- };
85
- }
86
- const profile = profileValues(o);
87
- if (profile.bio)
88
- r.bio = profile.bio;
89
- if (profile.persona)
90
- r.persona = profile.persona;
91
- if (o.isolationFile)
92
- r.isolation = readIsolationFile(o.isolationFile);
93
- if (o.monitorConfig)
94
- r.monitor = { ...o.monitorConfig };
95
- return r;
51
+ function resolvedSpawn(o) {
52
+ const definition = agentDefinitionFromSpawn(o);
53
+ const cfg = loadConfig(o.configPath, { additionalAgent: { id: o.name, definition } });
54
+ return { definition, role: findRole(cfg, o.name) };
96
55
  }
97
56
  /**
98
57
  * Read and validate an `--isolation-file`. The file is the existing
@@ -120,12 +79,6 @@ export function readIsolationFile(path) {
120
79
  return cfg;
121
80
  }
122
81
  export function validateSpawnOpts(o) {
123
- if (o.mission !== undefined && o.missionFile)
124
- throw new Error('--mission and --mission-file are mutually exclusive');
125
- if (o.session === 'tmux')
126
- throw new Error("invalid --session 'tmux'; tmux is no longer supported; use --session acp");
127
- if (o.session && o.session !== 'acp')
128
- throw new Error(`invalid --session '${o.session}'; allowed: acp`);
129
82
  if (o.approval && !['ask', 'auto', 'allow', 'deny'].includes(o.approval))
130
83
  throw new Error(`invalid --approval '${o.approval}'; allowed: ask, auto, allow (deprecated alias: deny)`);
131
84
  if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
@@ -136,23 +89,12 @@ export function validateSpawnOpts(o) {
136
89
  throw new Error(`invalid role name '${o.name}'`);
137
90
  if (o.identity && !/^[A-Za-z0-9_-]+$/.test(o.identity))
138
91
  throw new Error(`invalid identity name '${o.identity}'`);
139
- if (o.model && (o.model.length > 128 || /[\0-\x1f\x7f]/.test(o.model)))
140
- throw new Error('model must be printable and at most 128 characters');
141
92
  if (o.monitorConfig) {
142
93
  const problems = validateMonitorConfig(o.monitorConfig);
143
94
  if (problems.length)
144
95
  throw new Error(problems.join('; '));
145
96
  }
146
97
  }
147
- /** Read mission text without trimming or newline rewriting. */
148
- export function readMissionFile(path) {
149
- try {
150
- return readFileSync(path, 'utf8');
151
- }
152
- catch (e) {
153
- throw new Error(`--mission-file ${path}: ${e.message}`);
154
- }
155
- }
156
98
  /**
157
99
  * Reject names that are already USED. This is a precondition, not a claim: it
158
100
  * runs INSIDE the creation transaction, after both names are reserved, so the
@@ -160,7 +102,15 @@ export function readMissionFile(path) {
160
102
  * by the reservation rather than by this function.
161
103
  */
162
104
  function assertNameFree(o) {
163
- const cfg = loadConfig(o.configPath);
105
+ let cfg;
106
+ try {
107
+ cfg = loadConfig(o.configPath);
108
+ }
109
+ catch (error) {
110
+ if (error instanceof Error && error.message.includes(`resolve to duplicate identity '${effectiveIdentity(o)}'`))
111
+ throw new Error(`ours identity '${effectiveIdentity(o)}' is already taken or being created right now`);
112
+ throw error;
113
+ }
164
114
  if (cfg.roles.some(r => r.name === o.name))
165
115
  throw new Error(`role '${o.name}' already exists (${cfg.roles.find(r => r.name === o.name).sourceFile})`);
166
116
  if (existsSync(agentDir(o.name)) || existsSync(agentDir(o.name, true)))
@@ -173,23 +123,6 @@ function assertNameFree(o) {
173
123
  }
174
124
  /** The ours identity a spawn will bind: explicit, else the role name. */
175
125
  export const effectiveIdentity = (o) => o.identity ?? o.name;
176
- /** Bare Agent document written by v2 spawn: inline Role × inline Brain + operations. */
177
- export function buildAgentDocument(raw) {
178
- const role = Object.fromEntries(['mission', 'persona', 'bio', 'briefing_file']
179
- .filter(key => raw[key] !== undefined)
180
- .map(key => [key, raw[key]]));
181
- const brain = Object.fromEntries([
182
- 'harness', 'session', 'session_options', 'model', 'model_chain', 'max_tokens',
183
- 'autocompact_pct', 'harness_options', 'effort',
184
- ].filter(key => raw[key] !== undefined)
185
- .map(key => [key, raw[key]]));
186
- const operational = Object.fromEntries([
187
- 'permissions', 'identity', 'cwd', 'coordinator', 'env', 'oversee', 'isolation',
188
- 'monitor', 'owner_channel', 'worklog', 'auth_proxy',
189
- ].filter(key => raw[key] !== undefined)
190
- .map(key => [key, raw[key]]));
191
- return { role: { inline: role }, brain: { inline: brain }, ...operational };
192
- }
193
126
  /**
194
127
  * Validate and resolve a spawn without reserving names, contacting the daemon,
195
128
  * or writing state. Collision checks are necessarily a point-in-time snapshot.
@@ -198,68 +131,13 @@ export function spawnDryRun(o) {
198
131
  validateSpawnOpts(o);
199
132
  if (o.isolationFile)
200
133
  readIsolationFile(o.isolationFile);
201
- if (o.missionFile)
202
- readMissionFile(o.missionFile);
203
134
  assertNameFree(o);
204
- const cfg = loadConfig(o.configPath);
205
- const raw = buildRoleConfig(o, cfg.defaults.harness ?? 'claude-code');
206
- const harnessOptions = {
207
- ...(cfg.defaults.harness_options ?? {}),
208
- ...(raw.harness_options ?? {}),
209
- };
210
- const harness = raw.harness ?? cfg.defaults.harness ?? 'claude-code';
211
- const defaultHarness = cfg.defaults.harness ?? 'claude-code';
212
- const inheritsModelDefaults = harness === defaultHarness && raw.model !== null;
213
- const authProxy = resolveAuthProxy(cfg.defaults.auth_proxy, raw.auth_proxy);
214
- // One resolution for the environment and the model it pins (src/model-env.ts).
215
- const modelEnv = resolveRoleModelEnv({
216
- harness,
217
- model: resolveRoleModel(raw.model, raw.harness, cfg.defaults),
218
- modelWasExplicit: raw.model !== undefined,
219
- defaultsEnv: (cfg.defaults.env ?? {}),
220
- roleEnv: raw.env,
221
- ...(authProxy ? { authProxyBaseUrl: authProxy.base_url } : {}),
222
- });
223
- const adapter = getAdapter(harness);
224
- const brain = adapter.agentSession.resolveBrain({
225
- model: modelEnv.model,
226
- effort: raw.effort,
227
- harnessOptions: Object.keys(harnessOptions).length ? harnessOptions : undefined,
228
- });
229
- const model = brain.model ?? undefined;
230
- const session = raw.session ?? cfg.defaults.session ?? 'acp';
231
- const resolvedRole = {
232
- ...raw,
233
- name: o.name,
234
- sourceFile: o.temp ? '(temp dry-run)' : join(splitRootFor(o.configPath ?? defaultConfigPath()), 'agents', `${o.name}.yaml`),
235
- harness,
236
- session,
237
- session_options: raw.session_options,
238
- permissions: resolvePermissions(cfg.defaults.permissions, raw.permissions),
239
- permissionsDeclared: raw.permissions !== undefined || cfg.defaults.permissions !== undefined,
240
- identity: effectiveIdentity(o),
241
- effort: raw.effort,
242
- model,
243
- model_chain: resolveModelChain(model, raw.model_chain ?? (inheritsModelDefaults
244
- ? cfg.defaults.model_chain
245
- : undefined)),
246
- harness_options: brain.harnessOptions,
247
- isolation: raw.isolation ?? cfg.defaults.isolation,
248
- monitor: resolveMonitorConfig(cfg.defaults.monitor, raw.monitor),
249
- owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, raw.owner_channel, session),
250
- worklog: resolveWorklogPolicy(cfg.defaults.worklog, raw.worklog),
251
- auth_proxy: authProxy,
252
- };
253
- resolvedRole.env = modelEnv.env;
254
- if (resolvedRole.auth_proxy && resolvedRole.harness !== 'claude-code')
255
- throw new Error('auth_proxy is supported only by claude-code');
256
- const optionProblems = adapter.validateOptions(resolvedRole.harness_options, resolvedRole);
257
- if (optionProblems.length)
258
- throw new Error(optionProblems.map(problem => `${problem.path}: ${problem.message}`).join('; '));
135
+ const resolved = resolvedSpawn(o);
136
+ const resolvedRole = resolved.role;
259
137
  return {
260
138
  schemaVersion: 1,
261
139
  warning: 'collision checks are a snapshot; a real spawn reserves names atomically',
262
- roleDocument: buildAgentDocument(raw),
140
+ roleDocument: structuredClone(resolved.definition),
263
141
  resolvedRole,
264
142
  };
265
143
  }
@@ -275,22 +153,15 @@ function provenanceSettings(o, defaults) {
275
153
  const perms = (defaults.permissions ?? {});
276
154
  const callerDefaults = new Set(o.inheritedFromCaller ?? []);
277
155
  const tagged = (key, entry) => callerDefaults.has(key) ? { ...entry, source: 'caller-role' } : entry;
278
- const explicitModel = typeof o.model === 'string' ? o.model.trim() : undefined;
279
- const inheritedModel = resolveRoleModel(undefined, o.harness, defaults);
156
+ const selection = (value) => value && 'ref' in value ? `ref:${value.ref}` : value ? 'inline' : undefined;
280
157
  return {
281
- harness: tagged('harness', provenanceOf(o.harness, defaults.harness, 'claude-code')),
282
- session: tagged('session', provenanceOf(o.session, defaults.session, 'acp')),
158
+ brain: tagged('brain', provenanceOf(selection(o.brain), undefined, undefined)),
159
+ role: tagged('role', provenanceOf(selection(o.role), undefined, undefined)),
283
160
  identity: o.identity
284
161
  ? { value: o.identity, source: 'cli' }
285
162
  : { value: o.name, source: 'built-in' }, // defaults to the role name
286
163
  cwd: tagged('cwd', provenanceOf(o.cwd, undefined, undefined)),
287
- model: tagged('model', o.model === null
288
- ? { value: undefined, source: 'cli' }
289
- : explicitModel
290
- ? { value: explicitModel, source: 'cli' }
291
- : { value: inheritedModel, source: inheritedModel ? 'fleet-default' : 'built-in' }),
292
164
  coordinator: tagged('coordinator', provenanceOf(o.coordinator, undefined, undefined)),
293
- permission_mode: provenanceOf(o.permissionMode, undefined, undefined),
294
165
  approval: tagged('approval', provenanceOf(o.approval, perms.approval, 'ask')),
295
166
  filesystem: tagged('filesystem', provenanceOf(o.filesystem, perms.filesystem, 'workspace')),
296
167
  unattended: tagged('unattended', provenanceOf(o.unattended, perms.unattended, 'deny')),
@@ -305,8 +176,10 @@ export async function spawnPermanent(o, deps, creation = {}) {
305
176
  validateSpawnOpts(o);
306
177
  if (o.isolationFile)
307
178
  readIsolationFile(o.isolationFile); // fail before reserving
308
- if (o.missionFile)
309
- readMissionFile(o.missionFile); // fail before reserving
179
+ // Preserve the detailed owner/source diagnostic for an existing identity.
180
+ // The check is repeated inside the reservation boundary to close races.
181
+ assertNameFree(o);
182
+ const prepared = resolvedSpawn(o); // canonical validation before mutation
310
183
  // Reserve name and identity together before anything is written or started.
311
184
  // A loser of the race creates no config, state, or service.
312
185
  creation.onStage?.('reserving');
@@ -316,7 +189,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
316
189
  // Establish the identity BEFORE the service is enabled, and record
317
190
  // what was actually guaranteed so the briefing can say something true.
318
191
  creation.onStage?.('checking_identity');
319
- const guarantee = await ensureIdentity(effectiveIdentity(o), profileValues(o), creation.identityProvisioner ?? deps.identityProvisioner ?? daemonIdentityProvisioner(), deps.log);
192
+ const guarantee = await ensureIdentity(effectiveIdentity(o), { bio: prepared.role.bio, persona: prepared.role.persona }, creation.identityProvisioner ?? deps.identityProvisioner ?? daemonIdentityProvisioner(), deps.log);
320
193
  creation.onStage?.('checking_identity', {
321
194
  result: guarantee.evidence, guarantee: guarantee.state,
322
195
  });
@@ -333,7 +206,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
333
206
  mkdirSync(agentRoot, { recursive: true });
334
207
  creation.onStage?.('writing_role');
335
208
  const file = join(agentRoot, `${o.name}.yaml`);
336
- writeRoleFile(tx, file, stringify(buildAgentDocument(buildRoleConfig(o, cfg.defaults.harness ?? 'claude-code'))));
209
+ writeRoleFile(tx, file, stringify(agentDefinitionFromSpawn(o)));
337
210
  // `up` materialises the state dir and registers the service. Journal the
338
211
  // dir before it exists so a failure leaves the name genuinely reusable
339
212
  // rather than blocked by a half-built directory.
@@ -402,8 +275,7 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
402
275
  validateSpawnOpts(o);
403
276
  if (o.isolationFile)
404
277
  readIsolationFile(o.isolationFile); // fail before reserving
405
- if (o.missionFile)
406
- readMissionFile(o.missionFile); // fail before reserving
278
+ const prepared = resolvedSpawn(o); // canonical validation before mutation
407
279
  // Retire only supervisors whose recorded owner is definitively stopped. This
408
280
  // bounded pass keeps the active roster clean without deleting old evidence.
409
281
  await reclaimStaleTempState();
@@ -416,63 +288,14 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
416
288
  creation.onStage?.('checking_identity', {
417
289
  result: 'unknown', guarantee: 'unverified',
418
290
  });
419
- return spawnTempInner(o, binPath, launch, tx, creation.onStage);
291
+ return spawnTempInner(o, prepared.role, binPath, launch, tx, creation.onStage);
420
292
  }, creation);
421
293
  }
422
- async function spawnTempInner(o, binPath, launch, tx, onStage) {
294
+ async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
423
295
  const cfg = loadConfig(o.configPath);
424
- const defaultHarness = cfg.defaults.harness;
425
- const fromOpts = buildRoleConfig(o, defaultHarness);
426
- const mergedHarnessOptions = {
427
- ...(cfg.defaults.harness_options ?? {}),
428
- ...(fromOpts.harness_options ?? {}),
429
- };
430
- const harness = o.harness ?? defaultHarness ?? 'claude-code';
431
- const inheritsModelDefaults = harness === (defaultHarness ?? 'claude-code') && o.model !== null;
432
- const tempAuthProxy = resolveAuthProxy(cfg.defaults.auth_proxy, fromOpts.auth_proxy);
433
- // An explicitly requested --model must reach the child, not just the banner
434
- // (src/model-env.ts).
435
- const modelEnv = resolveRoleModelEnv({
436
- harness,
437
- model: resolveRoleModel(o.model, o.harness, cfg.defaults),
438
- modelWasExplicit: o.model !== undefined,
439
- defaultsEnv: (cfg.defaults.env ?? {}),
440
- roleEnv: fromOpts.env,
441
- ...(tempAuthProxy ? { authProxyBaseUrl: tempAuthProxy.base_url } : {}),
442
- });
443
- const adapter = getAdapter(harness);
444
- const brain = adapter.agentSession.resolveBrain({
445
- model: modelEnv.model,
446
- effort: fromOpts.effort,
447
- harnessOptions: Object.keys(mergedHarnessOptions).length ? mergedHarnessOptions : undefined,
448
- });
449
- const model = brain.model ?? undefined;
450
- const session = o.session ?? cfg.defaults.session ?? 'acp';
451
296
  const role = {
452
- ...fromOpts, // includes `isolation` when --isolation-file was given
453
- name: o.name,
454
- harness,
455
- session,
456
- identity: o.identity ?? o.name,
457
- effort: fromOpts.effort,
458
- model,
459
- model_chain: resolveModelChain(model, fromOpts.model_chain ?? (inheritsModelDefaults
460
- ? cfg.defaults.model_chain
461
- : undefined)),
462
- harness_options: brain.harnessOptions,
463
- permissions: resolvePermissions(cfg.defaults.permissions, fromOpts.permissions),
464
- permissionsDeclared: fromOpts.permissions !== undefined || cfg.defaults.permissions !== undefined,
465
- // Temp agents inherit the fleet-wide monitor defaults via the snapshot.
466
- monitor: resolveMonitorConfig(cfg.defaults.monitor, fromOpts.monitor),
467
- owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, fromOpts.owner_channel, session),
468
- worklog: resolveWorklogPolicy(cfg.defaults.worklog, fromOpts.worklog),
469
- auth_proxy: tempAuthProxy,
470
- sourceFile: '(temp)',
471
- roomMemberStartup: o.roomMemberStartup,
297
+ ...preparedRole, sourceFile: '(temp)', loops: undefined, roomMemberStartup: o.roomMemberStartup,
472
298
  };
473
- role.env = modelEnv.env;
474
- if (role.auth_proxy && role.harness !== 'claude-code')
475
- throw new Error('auth_proxy is supported only by claude-code');
476
299
  onStage?.('writing_role');
477
300
  const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
478
301
  const provenance = buildProvenance({
@@ -5,7 +5,7 @@ export declare const TEMP_STOP_REQUEST_FILE = ".temp-stop-request.json";
5
5
  export declare const TEMP_RECLAIM_BATCH = 32;
6
6
  export declare const TEMP_LAUNCH_GRACE_MS = 60000;
7
7
  export type TempSupervisorKind = 'systemd-transient' | 'launchd-transient' | 'detached';
8
- export type TempTerminationReason = 'identity-closed' | 'session-ended' | 'operator-stop' | 'supervisor-signal' | 'startup-failure' | 'stale-supervisor';
8
+ export type TempTerminationReason = 'identity-closed' | 'session-ended' | 'operator-stop' | 'supervisor-signal' | 'supervisor-recycle' | 'startup-failure' | 'stale-supervisor';
9
9
  export interface TempSupervisorRecord {
10
10
  version: 1;
11
11
  role: string;
@@ -1,19 +1,19 @@
1
- import type { FleetConfig, ResolvedRole, SessionBackendId } from '../config.js';
1
+ import type { AgentDefinition, FleetConfig, ResolvedRole } from '../config.js';
2
+ import type { SessionBackendId } from '../config.js';
2
3
  import type { IsolationConfig } from '../isolation/types.js';
3
4
  export interface WatchdogConfig {
4
5
  coordinator?: string;
5
6
  enabled?: boolean;
6
7
  interval?: string;
7
8
  watch?: string[];
8
- harness?: string;
9
- model?: string | null;
10
- session?: string;
9
+ agent?: {
10
+ ref: string;
11
+ } | AgentDefinition;
11
12
  identity?: string;
12
13
  timeout?: string;
13
14
  keep_reports?: number;
14
15
  alert_cooldown?: string;
15
16
  prompt_file?: string;
16
- isolation?: IsolationConfig;
17
17
  }
18
18
  export interface ResolvedWatchdog {
19
19
  name: string;
@@ -22,15 +22,18 @@ export interface ResolvedWatchdog {
22
22
  intervalMs: number;
23
23
  watch: string[];
24
24
  watchExplicit: boolean;
25
+ agentDefinition: AgentDefinition;
26
+ resolvedAgent: ResolvedRole;
27
+ /** Resolved/read-only runtime facts; never independent authoring fields. */
25
28
  harness: string;
26
29
  session: SessionBackendId;
27
30
  model?: string;
31
+ isolation?: IsolationConfig;
28
32
  identity: string;
29
33
  timeoutMs: number;
30
34
  keepReports: number;
31
35
  alertCooldownMs: number;
32
36
  promptFile?: string;
33
- isolation?: IsolationConfig;
34
37
  sourceFile: string;
35
38
  }
36
39
  export declare const WATCHDOG_DEFAULT_INTERVAL_MS = 600000;
@@ -38,7 +41,7 @@ export declare const WATCHDOG_MIN_INTERVAL_MS = 60000;
38
41
  export declare const WATCHDOG_DEFAULT_TIMEOUT_MS = 300000;
39
42
  export declare const WATCHDOG_DEFAULT_KEEP_REPORTS = 50;
40
43
  export declare const WATCHDOG_DEFAULT_COOLDOWN_MS = 3600000;
41
- export declare function resolveWatchdogs(baseDoc: Record<string, unknown>, baseFile: string, roles: ResolvedRole[], vars: Record<string, string>, defaults: Record<string, unknown>): ResolvedWatchdog[];
44
+ export declare function resolveWatchdogs(baseDoc: Record<string, unknown>, baseFile: string, roles: ResolvedRole[], vars: Record<string, string>, agentDefinitions: Record<string, AgentDefinition>, resolveInline: (name: string, definition: AgentDefinition) => ResolvedRole): ResolvedWatchdog[];
42
45
  /**
43
46
  * Split `restart`'s argument names into watchdogs (a release, handled
44
47
  * directly) and roles (handed to restartRoles). Watchdog names can't collide
@@ -1,11 +1,10 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { isAbsolute } from 'node:path';
3
3
  import { parseDuration } from '../duration.js';
4
- import { ConfigError, ROLE_NAME_RE, resolveRoleModel } from '../config.js';
5
- import { validateIsolationConfig } from '../isolation/policy.js';
4
+ import { ConfigError, ROLE_NAME_RE } from '../config.js';
6
5
  const WATCHDOG_KEYS = [
7
- 'coordinator', 'enabled', 'interval', 'watch', 'harness', 'model', 'session',
8
- 'identity', 'timeout', 'keep_reports', 'alert_cooldown', 'prompt_file', 'isolation',
6
+ 'coordinator', 'enabled', 'interval', 'watch', 'agent',
7
+ 'identity', 'timeout', 'keep_reports', 'alert_cooldown', 'prompt_file',
9
8
  ];
10
9
  export const WATCHDOG_DEFAULT_INTERVAL_MS = 600_000;
11
10
  export const WATCHDOG_MIN_INTERVAL_MS = 60_000;
@@ -21,7 +20,7 @@ function deepSub(v, vars) {
21
20
  return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, deepSub(x, vars)]));
22
21
  return v;
23
22
  }
24
- export function resolveWatchdogs(baseDoc, baseFile, roles, vars, defaults) {
23
+ export function resolveWatchdogs(baseDoc, baseFile, roles, vars, agentDefinitions, resolveInline) {
25
24
  const block = baseDoc.watchdogs;
26
25
  if (block === undefined || block === null)
27
26
  return [];
@@ -42,6 +41,9 @@ export function resolveWatchdogs(baseDoc, baseFile, roles, vars, defaults) {
42
41
  if (roleNames.has(name))
43
42
  throw new ConfigError(`${baseFile}: watchdog '${name}' collides with a role name`);
44
43
  const w = deepSub(rawEntry ?? {}, vars);
44
+ const legacy = ['harness', 'model', 'session', 'isolation'].filter(key => Object.hasOwn(w, key));
45
+ if (legacy.length)
46
+ throw new ConfigError(`${where}: E_LEGACY Brain/Agent-owned field(s) ${legacy.join(', ')} are unsupported; select a canonical Agent with agent`);
45
47
  const bad = Object.keys(w).filter(k => !WATCHDOG_KEYS.includes(k));
46
48
  if (bad.length)
47
49
  throw new ConfigError(`${where} has unknown key(s) ${bad.join(', ')}; allowed: ${WATCHDOG_KEYS.join(', ')}`);
@@ -60,23 +62,29 @@ export function resolveWatchdogs(baseDoc, baseFile, roles, vars, defaults) {
60
62
  if (collidingWatchdog !== undefined)
61
63
  throw new ConfigError(`${where}: identity '${identity}' collides with watchdog '${collidingWatchdog}'`);
62
64
  watchdogIdentities.set(identity, name);
63
- const harness = w.harness ?? defaults.harness ?? 'claude-code';
64
- const sessionRaw = w.session ?? defaults.session ?? 'acp';
65
- if (sessionRaw === 'tmux')
66
- throw new ConfigError(`${where}: session 'tmux' is no longer supported; use session: acp`);
67
- if (sessionRaw !== 'acp')
68
- throw new ConfigError(`${where}: session must be 'acp'`);
65
+ if (!w.agent || typeof w.agent !== 'object' || Array.isArray(w.agent))
66
+ throw new ConfigError(`${where}: agent is required (Agent ref or canonical Agent definition)`);
67
+ let agentDefinition;
68
+ let resolvedAgent;
69
+ if ('ref' in w.agent) {
70
+ const ref = w.agent.ref;
71
+ if (typeof ref !== 'string' || !ref)
72
+ throw new ConfigError(`${where}: agent.ref must be a declared Agent ID`);
73
+ agentDefinition = agentDefinitions[ref];
74
+ resolvedAgent = roles.find(role => role.name === ref);
75
+ if (!agentDefinition || !resolvedAgent)
76
+ throw new ConfigError(`${where}: Agent ref '${ref}' was not found`);
77
+ }
78
+ else {
79
+ agentDefinition = structuredClone(w.agent);
80
+ resolvedAgent = resolveInline(name, agentDefinition);
81
+ }
69
82
  if (w.prompt_file !== undefined) {
70
83
  if (typeof w.prompt_file !== 'string' || !isAbsolute(w.prompt_file))
71
84
  throw new ConfigError(`${where}: prompt_file must be an absolute path`);
72
85
  if (!existsSync(w.prompt_file))
73
86
  throw new ConfigError(`${where}: prompt_file not found: ${w.prompt_file}`);
74
87
  }
75
- if (w.isolation !== undefined) {
76
- const problems = validateIsolationConfig(w.isolation);
77
- if (problems.length)
78
- throw new ConfigError(`${where} ${problems.join('; ')}`);
79
- }
80
88
  const dur = (v, key, fallback, minMs) => {
81
89
  if (v === undefined)
82
90
  return fallback;
@@ -96,13 +104,15 @@ export function resolveWatchdogs(baseDoc, baseFile, roles, vars, defaults) {
96
104
  name, coordinator: w.coordinator.trim(),
97
105
  enabled: w.enabled ?? true,
98
106
  intervalMs: dur(w.interval, 'interval', WATCHDOG_DEFAULT_INTERVAL_MS, WATCHDOG_MIN_INTERVAL_MS),
99
- watch, watchExplicit: w.watch !== undefined, harness, session: sessionRaw,
100
- model: resolveRoleModel(w.model, w.harness, defaults),
107
+ watch, watchExplicit: w.watch !== undefined,
108
+ agentDefinition: structuredClone(agentDefinition), resolvedAgent: structuredClone(resolvedAgent),
109
+ harness: resolvedAgent.harness, session: resolvedAgent.session,
110
+ model: resolvedAgent.model, isolation: resolvedAgent.isolation,
101
111
  identity,
102
112
  timeoutMs: dur(w.timeout, 'timeout', WATCHDOG_DEFAULT_TIMEOUT_MS),
103
113
  keepReports,
104
114
  alertCooldownMs: dur(w.alert_cooldown, 'alert_cooldown', WATCHDOG_DEFAULT_COOLDOWN_MS),
105
- promptFile: w.prompt_file, isolation: w.isolation, sourceFile: baseFile,
115
+ promptFile: w.prompt_file, sourceFile: baseFile,
106
116
  });
107
117
  }
108
118
  return out;
@@ -10,7 +10,7 @@ import { applyRole } from '../ops.js';
10
10
  import { daemonIdentityInventoryProvisioner, ensureIdentity, } from '../creation.js';
11
11
  import { runOnce, START_STAGGER_FILE } from '../runner.js';
12
12
  import { agentDir, tmpRoot } from '../paths.js';
13
- import { loadConfig, resolveMonitorConfig, resolveWorklogPolicy, ROLE_NAME_RE, } from '../config.js';
13
+ import { loadConfig, ROLE_NAME_RE, } from '../config.js';
14
14
  import { getAdapter } from '../harness/registry.js';
15
15
  import { redactLogLine } from '../application/log-service.js';
16
16
  import { controlRequest, controlSocketPath } from '../session/control.js';
@@ -84,19 +84,10 @@ function readTail(runDir) {
84
84
  }
85
85
  }
86
86
  /** The temp role every watchdog-family run launches under. Isolation is opt-in. */
87
- function buildWatchdogRole(wd, cfg) {
87
+ function buildWatchdogRole(wd, _cfg) {
88
88
  return {
89
- name: wd.identity, sourceFile: '(watchdog)',
90
- harness: wd.harness, session: wd.session,
91
- identity: wd.identity, model: wd.model,
92
- // Watchdogs are observe-only by contract, but their sanctioned status commands
93
- // must reach host control sockets. Keep approvals/unattended escalation denied
94
- // while disabling the harness's native filesystem/network sandbox.
95
- permissions: { approval: 'deny', filesystem: 'unrestricted', unattended: 'deny' },
96
- permissionsDeclared: true,
97
- monitor: resolveMonitorConfig(cfg.defaults.monitor, undefined),
98
- worklog: resolveWorklogPolicy(cfg.defaults.worklog, undefined),
99
- isolation: wd.isolation,
89
+ ...structuredClone(wd.resolvedAgent),
90
+ name: wd.identity, sourceFile: '(watchdog)', identity: wd.identity,
100
91
  };
101
92
  }
102
93
  /** Discover temporary sessions that are live now, not merely stale dirs on disk. */
@@ -3,6 +3,7 @@ export declare const REDACTED_ENV_VALUE = "__OURS_FLEET_SECRET_REDACTED__";
3
3
  export interface EditableFleetModel {
4
4
  manifest: Record<string, unknown>;
5
5
  agents: Record<string, Record<string, unknown>>;
6
+ agent_templates: Record<string, Record<string, unknown>>;
6
7
  }
7
8
  export interface ConfigReadResult {
8
9
  path: string;