@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
@@ -0,0 +1,118 @@
1
+ import type { AgentSelection, ApprovalMode, FilesystemMode, FleetPermissionMode, MonitorInterrupt, MonitorMode, ResolvedRole, SessionBackendId, UnattendedMode } from './config.js';
2
+ import type { IsolationBackendId, NetworkMode, OnUnavailable } from './isolation/types.js';
3
+ /**
4
+ * Operator-facing launch configuration captured at the launch boundary from
5
+ * the exact ResolvedRole that was spawned. It is the single source for every
6
+ * Fleet lifecycle/report surface (CLI and owner-channel/Messenger).
7
+ *
8
+ * Security boundary: this object is a strict whitelist. It must never carry
9
+ * env, cwd or any local path, harness_options, session_options, owner_channel,
10
+ * or auth_proxy content. Hashes are secondary provenance only, never the
11
+ * primary description.
12
+ */
13
+ export interface AgentLaunchConfiguration {
14
+ version: 1;
15
+ /** Agent template / launch definition label when one was selected. */
16
+ template?: string;
17
+ role: SelectionOrigin;
18
+ brain: SelectionOrigin;
19
+ harness: string;
20
+ session: SessionBackendId;
21
+ /**
22
+ * Model observed effective at launch. `null` reports runtime behavior — the
23
+ * harness ran its own default — not whether the author wrote `model: null`
24
+ * or omitted the field; effectiveRoleModel cannot distinguish those.
25
+ */
26
+ model: string | null;
27
+ effort?: string;
28
+ /** Concise first-line mission label (or Cowork-role fallback), never the full mission. */
29
+ mission?: string;
30
+ approval: ApprovalMode;
31
+ filesystem: FilesystemMode;
32
+ unattended: UnattendedMode;
33
+ /** Portable policy plus the exact native runtime mode after harness_options. */
34
+ permissionMode: {
35
+ fleetMode: FleetPermissionMode;
36
+ nativeMode: string;
37
+ };
38
+ monitor: {
39
+ mode: MonitorMode;
40
+ interrupt: MonitorInterrupt;
41
+ };
42
+ /** Requested isolation policy only — never resolved runtime facts or host paths. */
43
+ isolation?: {
44
+ requested: IsolationBackendId;
45
+ on_unavailable?: OnUnavailable;
46
+ network?: NetworkMode;
47
+ read_mounts?: number;
48
+ write_mounts?: number;
49
+ };
50
+ }
51
+ /** Where a Brain/Role selection came from; labels are human, hashes secondary. */
52
+ export type SelectionOrigin = {
53
+ kind: 'named';
54
+ ref: string;
55
+ } | {
56
+ kind: 'inline';
57
+ fingerprint?: string;
58
+ } | {
59
+ kind: 'unknown';
60
+ };
61
+ export declare const MISSION_LABEL_MAX = 80;
62
+ /**
63
+ * The inline fingerprint is always computed here from the canonical inline
64
+ * body; provenance can never be an arbitrary caller string.
65
+ */
66
+ export declare function selectionOrigin(selection: AgentSelection | undefined): SelectionOrigin;
67
+ export declare function missionLabel(mission: string | undefined): string | undefined;
68
+ /**
69
+ * Build the presentation from the exact resolved launch state. `origins`
70
+ * carries the authoring-time selections and template label because a merged
71
+ * ResolvedRole no longer distinguishes preset references from inline bodies.
72
+ * `permissionMode` is required at capture time: every supported harness
73
+ * adapter reports it, and the managed-spawn receiver gets it over the wire
74
+ * and must never re-resolve mutable configuration.
75
+ */
76
+ export declare function summarizeResolvedLaunch(role: ResolvedRole, origins: {
77
+ role: SelectionOrigin;
78
+ brain: SelectionOrigin;
79
+ template?: string;
80
+ permissionMode: {
81
+ fleetMode: FleetPermissionMode;
82
+ nativeMode: string;
83
+ };
84
+ /** Shown when the resolved role has no mission text (e.g. Cowork role name). */
85
+ missionFallback?: string;
86
+ }): AgentLaunchConfiguration;
87
+ /**
88
+ * Component budget for one rendered agent line. The complete mandatory
89
+ * rendering must always fit — that is a validity condition of a current-v1
90
+ * configuration, enforced by mandatoryConfigurationFits at the builder AND
91
+ * the wire boundary — while optional components (monitor, isolation) are
92
+ * dropped whole with a visible `…` marker past this budget.
93
+ */
94
+ export declare const AGENT_LINE_MAX_CODE_POINTS = 1500;
95
+ export declare const AGENT_LINE_MAX_BYTES = 5000;
96
+ /**
97
+ * Validity condition of a current-v1 configuration: its complete mandatory
98
+ * rendering fits the per-line budget with the optional-omission suffix
99
+ * reserved. Escaping and code-fence growth (a value made of backticks can
100
+ * more than triple its code span) make this depend on rendered size, not raw
101
+ * field lengths, so both summarizeResolvedLaunch and the wire validator call
102
+ * this instead of trusting per-field caps.
103
+ */
104
+ export declare function mandatoryConfigurationFits(configuration: AgentLaunchConfiguration): boolean;
105
+ /**
106
+ * Render one agent's configuration as a single escaped Markdown line segment.
107
+ * This is the only escaping boundary: callers pass raw values and must not
108
+ * escape the result again (it is safe as a markdownItems entry and inside
109
+ * owner-channel lifecycle messages). Every mandatory component always
110
+ * renders — mandatoryConfigurationFits guarantees the fit for every produced
111
+ * or wire-accepted configuration; only optional components may be dropped,
112
+ * whole (never splitting a Markdown span), with a visible `…`.
113
+ */
114
+ export declare function renderAgentConfiguration(configuration: AgentLaunchConfiguration | undefined, fallback?: {
115
+ role?: string;
116
+ brain?: string;
117
+ permissions?: string;
118
+ }): string;
@@ -0,0 +1,161 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { Buffer } from 'node:buffer';
3
+ import { canonicalJson } from './canonical-json.js';
4
+ import { effectiveRoleModel } from './model-env.js';
5
+ import { markdownCode, markdownProse } from './rooms-tasks/markdown.js';
6
+ const FINGERPRINT_HEX = 12;
7
+ export const MISSION_LABEL_MAX = 80;
8
+ /**
9
+ * The inline fingerprint is always computed here from the canonical inline
10
+ * body; provenance can never be an arbitrary caller string.
11
+ */
12
+ export function selectionOrigin(selection) {
13
+ if (!selection)
14
+ return { kind: 'unknown' };
15
+ if ('ref' in selection)
16
+ return { kind: 'named', ref: selection.ref };
17
+ const fingerprint = createHash('sha256').update(canonicalJson(selection.inline)).digest('hex');
18
+ return { kind: 'inline', fingerprint: fingerprint.slice(0, FINGERPRINT_HEX) };
19
+ }
20
+ export function missionLabel(mission) {
21
+ const line = mission?.trim().split('\n', 1)[0]?.trim();
22
+ if (!line)
23
+ return undefined;
24
+ const points = Array.from(line);
25
+ return points.length > MISSION_LABEL_MAX
26
+ ? `${points.slice(0, MISSION_LABEL_MAX - 1).join('')}…` : line;
27
+ }
28
+ /**
29
+ * Build the presentation from the exact resolved launch state. `origins`
30
+ * carries the authoring-time selections and template label because a merged
31
+ * ResolvedRole no longer distinguishes preset references from inline bodies.
32
+ * `permissionMode` is required at capture time: every supported harness
33
+ * adapter reports it, and the managed-spawn receiver gets it over the wire
34
+ * and must never re-resolve mutable configuration.
35
+ */
36
+ export function summarizeResolvedLaunch(role, origins) {
37
+ const mission = missionLabel(role.mission) ?? missionLabel(origins.missionFallback);
38
+ const fs = role.isolation?.fs;
39
+ const configuration = {
40
+ version: 1,
41
+ ...(origins.template ? { template: origins.template } : {}),
42
+ role: origins.role,
43
+ brain: origins.brain,
44
+ harness: role.harness,
45
+ session: role.session,
46
+ model: effectiveRoleModel(role) ?? null,
47
+ ...(role.effort ? { effort: role.effort } : {}),
48
+ ...(mission ? { mission } : {}),
49
+ approval: role.permissions.approval,
50
+ filesystem: role.permissions.filesystem,
51
+ unattended: role.permissions.unattended,
52
+ permissionMode: origins.permissionMode,
53
+ monitor: { mode: role.monitor.mode, interrupt: role.monitor.interrupt },
54
+ ...(role.isolation?.backend ? { isolation: {
55
+ requested: role.isolation.backend,
56
+ ...(role.isolation.on_unavailable ? { on_unavailable: role.isolation.on_unavailable } : {}),
57
+ ...(role.isolation.network ? { network: role.isolation.network } : {}),
58
+ ...(fs?.read?.length ? { read_mounts: fs.read.length } : {}),
59
+ ...(fs?.write?.length ? { write_mounts: fs.write.length } : {}),
60
+ } } : {}),
61
+ };
62
+ // A configuration whose complete mandatory rendering cannot fit must never
63
+ // be produced; real resolved values sit far below the budget, so this is a
64
+ // capture defect, not an expected path.
65
+ if (!mandatoryConfigurationFits(configuration))
66
+ throw new Error('agent launch presentation exceeds the mandatory rendering budget');
67
+ return configuration;
68
+ }
69
+ /**
70
+ * Component budget for one rendered agent line. The complete mandatory
71
+ * rendering must always fit — that is a validity condition of a current-v1
72
+ * configuration, enforced by mandatoryConfigurationFits at the builder AND
73
+ * the wire boundary — while optional components (monitor, isolation) are
74
+ * dropped whole with a visible `…` marker past this budget.
75
+ */
76
+ export const AGENT_LINE_MAX_CODE_POINTS = 1_500;
77
+ export const AGENT_LINE_MAX_BYTES = 5_000;
78
+ const codePoints = (value) => Array.from(value).length;
79
+ const utf8 = (value) => Buffer.byteLength(value, 'utf8');
80
+ const OPTIONAL_OMISSION_SUFFIX = '; …';
81
+ function selectionComponent(kind, value) {
82
+ if (value.kind === 'named')
83
+ return `${kind} preset ${markdownCode(value.ref)}`;
84
+ if (value.kind === 'inline')
85
+ return `inline ${kind}${value.fingerprint ? ` (def ${markdownCode(value.fingerprint)})` : ''}`;
86
+ return `${kind} unknown`;
87
+ }
88
+ /**
89
+ * The complete mandatory rendering: template, origins, mission, harness,
90
+ * model, effort, portable policy triple, and fleet/native permission mode.
91
+ * These must never be omitted from an operator-facing line.
92
+ */
93
+ function mandatoryComponents(configuration) {
94
+ return [
95
+ configuration.template ? `template ${markdownCode(configuration.template)}` : undefined,
96
+ selectionComponent('Role', configuration.role),
97
+ configuration.mission ? `mission “${markdownProse(configuration.mission)}”` : undefined,
98
+ selectionComponent('Brain', configuration.brain),
99
+ `harness ${markdownCode(configuration.harness)}`,
100
+ `model ${configuration.model === null ? 'harness-default' : markdownCode(configuration.model)}`,
101
+ configuration.effort ? `effort ${markdownProse(configuration.effort)}` : undefined,
102
+ `approval=${configuration.approval}, filesystem=${configuration.filesystem}, `
103
+ + `unattended=${configuration.unattended}`,
104
+ `mode ${configuration.permissionMode.fleetMode}/${markdownProse(configuration.permissionMode.nativeMode)}`,
105
+ ].filter((part) => Boolean(part));
106
+ }
107
+ function optionalComponents(configuration) {
108
+ return [
109
+ `monitor ${configuration.monitor.mode}/${String(configuration.monitor.interrupt)}`,
110
+ configuration.isolation
111
+ ? `isolation requested ${configuration.isolation.requested}`
112
+ + `${configuration.isolation.network ? `, net ${configuration.isolation.network}` : ''}`
113
+ + `${configuration.isolation.on_unavailable ? `, on-unavailable ${configuration.isolation.on_unavailable}` : ''}`
114
+ + `${configuration.isolation.read_mounts || configuration.isolation.write_mounts
115
+ ? `, mounts +${configuration.isolation.read_mounts ?? 0}ro/+${configuration.isolation.write_mounts ?? 0}rw` : ''}`
116
+ : undefined,
117
+ ].filter((part) => Boolean(part));
118
+ }
119
+ /**
120
+ * Validity condition of a current-v1 configuration: its complete mandatory
121
+ * rendering fits the per-line budget with the optional-omission suffix
122
+ * reserved. Escaping and code-fence growth (a value made of backticks can
123
+ * more than triple its code span) make this depend on rendered size, not raw
124
+ * field lengths, so both summarizeResolvedLaunch and the wire validator call
125
+ * this instead of trusting per-field caps.
126
+ */
127
+ export function mandatoryConfigurationFits(configuration) {
128
+ const reserved = `${mandatoryComponents(configuration).join('; ')}${OPTIONAL_OMISSION_SUFFIX}`;
129
+ return codePoints(reserved) <= AGENT_LINE_MAX_CODE_POINTS && utf8(reserved) <= AGENT_LINE_MAX_BYTES;
130
+ }
131
+ /**
132
+ * Render one agent's configuration as a single escaped Markdown line segment.
133
+ * This is the only escaping boundary: callers pass raw values and must not
134
+ * escape the result again (it is safe as a markdownItems entry and inside
135
+ * owner-channel lifecycle messages). Every mandatory component always
136
+ * renders — mandatoryConfigurationFits guarantees the fit for every produced
137
+ * or wire-accepted configuration; only optional components may be dropped,
138
+ * whole (never splitting a Markdown span), with a visible `…`.
139
+ */
140
+ export function renderAgentConfiguration(configuration, fallback) {
141
+ if (!configuration) {
142
+ const labels = [
143
+ fallback?.role ? `Role ${markdownProse(fallback.role)}` : undefined,
144
+ fallback?.brain ? `Brain ${markdownProse(fallback.brain)}` : undefined,
145
+ fallback?.permissions ? `permissions ${markdownProse(fallback.permissions)}` : undefined,
146
+ ].filter(Boolean).join('; ');
147
+ return `legacy launch; resolved details unavailable${labels ? ` (${labels})` : ''}`;
148
+ }
149
+ let line = mandatoryComponents(configuration).join('; ');
150
+ let omitted = false;
151
+ for (const part of optionalComponents(configuration)) {
152
+ const next = `${line}; ${part}`;
153
+ if (codePoints(`${next}${OPTIONAL_OMISSION_SUFFIX}`) > AGENT_LINE_MAX_CODE_POINTS
154
+ || utf8(`${next}${OPTIONAL_OMISSION_SUFFIX}`) > AGENT_LINE_MAX_BYTES) {
155
+ omitted = true;
156
+ continue;
157
+ }
158
+ line = next;
159
+ }
160
+ return omitted ? `${line}${OPTIONAL_OMISSION_SUFFIX}` : line;
161
+ }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { lstatSync } from 'node:fs';
3
3
  import { ConfigError, ROLE_NAME_RE } from '../config.js';
4
+ import { sessionBackendCapabilities } from '../session/types.js';
4
5
  import { parseDuration } from '../duration.js';
5
6
  const LOOP_KEYS = ['roles', 'interval', 'prompt', 'enabled', 'initial_delay', 'jitter'];
6
7
  const MIN_INTERVAL_MS = 60_000;
@@ -90,8 +91,8 @@ export function resolveLoops(block, baseFile, roles, vars) {
90
91
  const prompt = normalizePrompt(value.prompt, `${where}: prompt`);
91
92
  if (enabled)
92
93
  for (const role of selected) {
93
- if (role.session !== 'acp')
94
- throw new ConfigError(`${where} selects role '${role.name}' with session '${role.session}'; scheduled loops require session: acp`);
94
+ if (!sessionBackendCapabilities(role.session, role.harness).promptInput)
95
+ throw new ConfigError(`${where} selects role '${role.name}' with session '${role.session}'; scheduled loops require managed prompt input`);
95
96
  }
96
97
  const promptHash = digest(prompt);
97
98
  const resolved = {
@@ -16,7 +16,7 @@ export interface RoleModelEnvInput {
16
16
  harness: string;
17
17
  /** Already resolved by `resolveRoleModel` — may come from the fleet default. */
18
18
  model: string | undefined;
19
- /** True when the role (or `--model`) named a model, including `model: null`. */
19
+ /** True when the resolved Brain named a model, including `model: null`. */
20
20
  modelWasExplicit: boolean;
21
21
  defaultsEnv?: Record<string, string>;
22
22
  roleEnv?: Record<string, string>;
@@ -35,7 +35,7 @@ export interface RoleModelEnv {
35
35
  * never disagree.
36
36
  *
37
37
  * Precedence, highest first:
38
- * 1. an explicit `model:` / `--model` on the role
38
+ * 1. an explicit `model:` on the resolved Brain
39
39
  * 2. the role's own `env:` pin
40
40
  * 3. the fleet `defaults.model`
41
41
  * 4. the fleet `defaults.env` pin
package/dist/model-env.js CHANGED
@@ -19,7 +19,7 @@ export function modelEnvVar(harness) {
19
19
  * never disagree.
20
20
  *
21
21
  * Precedence, highest first:
22
- * 1. an explicit `model:` / `--model` on the role
22
+ * 1. an explicit `model:` on the resolved Brain
23
23
  * 2. the role's own `env:` pin
24
24
  * 3. the fleet `defaults.model`
25
25
  * 4. the fleet `defaults.env` pin
@@ -5,7 +5,7 @@ export interface FailureEvidence {
5
5
  httpStatus?: number;
6
6
  model?: string;
7
7
  confidence: 'low' | 'medium' | 'high';
8
- source: 'claude-pane' | 'acp' | 'auth-proxy';
8
+ source: 'claude-pane' | 'acp' | 'codex-app-server' | 'auth-proxy';
9
9
  observedAt: string;
10
10
  }
11
11
  export interface ModelRecoveryLedger {
package/dist/ops.js CHANGED
@@ -179,23 +179,8 @@ async function reconcileWatchdogScheduler(cfg, deps, configPath) {
179
179
  }
180
180
  }
181
181
  export async function down(cfg, names, deps) {
182
- const roles = names.length ? names.map(name => cfg.roles.find(role => role.name === name)) : cfg.roles;
183
- for (let index = 0; index < roles.length; index++) {
184
- const role = roles[index];
185
- const requested = names[index];
186
- if (!role && requested && /^[A-Za-z0-9_-]+$/.test(requested)
187
- && existsSync(agentDir(requested, true))) {
188
- try {
189
- const outcome = await stopTempSupervisor(requested, { exec: deps.exec ?? realExec });
190
- deps.log(`■ ${outcome === 'stopped' ? 'stopping' : 'stopped'} temporary role ${requested}`);
191
- }
192
- catch (e) {
193
- deps.log(` ! could not stop temporary role ${requested}: ${e instanceof Error ? e.message : String(e)}`);
194
- }
195
- continue;
196
- }
197
- if (!role)
198
- throw new Error(`no such role '${requested}'`);
182
+ const roles = selectRoles(cfg, names);
183
+ for (const role of roles) {
199
184
  // Never swallow the backend's reason. "maybe not running" hid real stop
200
185
  // failures — a wedged unit, an unreachable user bus — behind a guess.
201
186
  try {
@@ -2,6 +2,7 @@ import { type OwnerChannelConfig } from '../config.js';
2
2
  import { type AgentSession } from '../session/types.js';
3
3
  import { type OwnerFleetOps } from './commands.js';
4
4
  import type { ManagedFleetSpawnResult } from '../fleet-proxy.js';
5
+ import { type FleetAuditAttempt, type FleetAuditPresentation, type FleetCommandOutcomeClass } from '../fleet-command-audit.js';
5
6
  import { type OursOps } from './ours-client.js';
6
7
  import { type OwnerUpdatePhase } from './notices.js';
7
8
  import { type OwnerEntry } from './state.js';
@@ -27,14 +28,44 @@ export interface OwnerChannelOptions {
27
28
  binderDeps?: OwnerBinderDeps;
28
29
  /** Pre-acquired by the runner so the predecessor control socket remains reachable while waiting. */
29
30
  binderLease?: OwnerBinderLease;
31
+ recoveryDeps?: {
32
+ now(): number;
33
+ setTimer(fn: () => void, ms: number): ReturnType<typeof setTimeout>;
34
+ clearTimer(timer: ReturnType<typeof setTimeout>): void;
35
+ deadlineMs?: number;
36
+ };
30
37
  }
31
38
  export interface OwnerChannelHandle {
32
39
  start(): Promise<void>;
33
40
  drain(): Promise<void>;
34
41
  close(): Promise<void>;
42
+ /** Reattach this same supervisor-owned channel after a daemon generation change. */
43
+ recover?(epoch: string): Promise<void>;
44
+ /** False when shutdown skipped client disposal because quiescence was unproven. */
45
+ binderReleaseSafe?(): boolean;
35
46
  manage(request: OwnerChannelManagementRequest): Promise<OwnerChannelManagementResult>;
36
47
  /** Fleet-owned deterministic lifecycle notice; absent on legacy test doubles. */
37
48
  notifyFleetSpawn?(event: ManagedFleetSpawnResult): Promise<void>;
49
+ beginFleetCommandAudit?(requestId: string, argv: string[]): Promise<FleetAuditAttempt>;
50
+ finishFleetCommandAudit?(input: {
51
+ correlationId: string;
52
+ class: FleetCommandOutcomeClass;
53
+ exitCode?: number;
54
+ effect: 'not_started' | 'completed' | 'unknown';
55
+ resourceIds?: Record<string, string>;
56
+ presentations?: FleetAuditPresentation[];
57
+ }): Promise<FleetAuditAttempt>;
58
+ }
59
+ export declare const OWNER_RECOVERY_QUEUE_CAPACITY = 32;
60
+ export declare const OWNER_RECOVERY_DEGRADED = "OWNER_RECOVERY_DEGRADED";
61
+ export declare class OwnerRecoveryDegradedError extends Error {
62
+ readonly code = "OWNER_RECOVERY_DEGRADED";
63
+ constructor();
64
+ }
65
+ export declare class OwnerRecoveryTimeoutError extends Error {
66
+ readonly stage: string;
67
+ readonly code = "OWNER_RECOVERY_DEGRADED";
68
+ constructor(stage: string);
38
69
  }
39
70
  export type OwnerChannelManagementRequest = {
40
71
  action: 'contact_list';
@@ -162,17 +193,42 @@ export declare class OwnerChannel implements OwnerChannelHandle {
162
193
  private readonly completionTasks;
163
194
  private readonly activeRequests;
164
195
  private managementTail;
196
+ private recoveryEpoch?;
197
+ private recoveryTask?;
198
+ private recoveryToken;
199
+ private recoveryQueued;
200
+ private watchGeneration;
201
+ /** A timed-out client operation that must settle before replacement is safe. */
202
+ private recoveryQuiescence?;
203
+ private releaseSafe;
165
204
  private ready;
205
+ private startedOnce;
206
+ private shutdownDegraded;
166
207
  private binder?;
167
208
  private binderOwnedInternally;
168
209
  private readonly fleetOps;
169
210
  private readonly prepareRestart;
211
+ private readonly commandAudits;
170
212
  constructor(options: OwnerChannelOptions);
171
213
  start(): Promise<void>;
172
214
  drain(): Promise<void>;
173
215
  close(): Promise<void>;
216
+ binderReleaseSafe(): boolean;
174
217
  manage(request: OwnerChannelManagementRequest): Promise<OwnerChannelManagementResult>;
175
218
  notifyFleetSpawn(event: ManagedFleetSpawnResult): Promise<void>;
219
+ beginFleetCommandAudit(requestId: string, argv: string[]): Promise<FleetAuditAttempt>;
220
+ finishFleetCommandAudit(input: {
221
+ correlationId: string;
222
+ class: FleetCommandOutcomeClass;
223
+ exitCode?: number;
224
+ effect: 'not_started' | 'completed' | 'unknown';
225
+ resourceIds?: Record<string, string>;
226
+ presentations?: FleetAuditPresentation[];
227
+ }): Promise<FleetAuditAttempt>;
228
+ recover(epoch: string): Promise<void>;
229
+ private recoveryStage;
230
+ private writeShutdownState;
231
+ private queueManagement;
176
232
  private manageNow;
177
233
  /**
178
234
  * The daemon reports established contacts and pending introductions as two
@@ -228,6 +284,8 @@ export declare class OwnerChannel implements OwnerChannelHandle {
228
284
  private recoverRoomFromOwner;
229
285
  /** Carry a task terminal request through a worker that survives this role. */
230
286
  private recoverTaskFromOwner;
287
+ /** Accept a permanent any-state deletion, acknowledge it, then hand cleanup to a durable worker. */
288
+ private deleteTaskFromOwner;
231
289
  private terminalTaskFromOwner;
232
290
  /** Code-point-safe tail of the worklog, or undefined when there is none. */
233
291
  private readWorklogTail;