@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.
- package/README.md +169 -54
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +6 -25
- package/dist/application/role-creation-service.js +34 -72
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +58 -6
- package/dist/application/task-room-service.js +426 -78
- package/dist/briefing.js +74 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +227 -73
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +67 -2
- package/dist/config.js +260 -30
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +171 -66
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +165 -0
- package/dist/fleet-command-audit.js +543 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/fleet-proxy.js +30 -8
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +629 -0
- package/dist/lifecycle-summary.d.ts +118 -0
- package/dist/lifecycle-summary.js +161 -0
- package/dist/loops/config.js +3 -2
- package/dist/model-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +58 -0
- package/dist/owner-channel/channel.js +321 -42
- package/dist/owner-channel/commands.d.ts +6 -1
- package/dist/owner-channel/commands.js +34 -19
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +414 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +4 -0
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +503 -75
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +20 -4
- package/dist/rooms-tasks/config.js +16 -14
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +176 -0
- package/dist/rooms-tasks/member-overrides.d.ts +27 -0
- package/dist/rooms-tasks/member-overrides.js +170 -0
- package/dist/rooms-tasks/provision.js +179 -49
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +94 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +9 -2
- package/dist/runner.js +107 -40
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +38 -0
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +6 -31
- package/dist/spawn.js +57 -234
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web/topology-promote.js +8 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +3 -2
- package/presets/brain-catalog.json +16 -0
- package/presets/fleet/agent_templates/Critic.yaml +4 -0
- package/presets/fleet/agent_templates/Developer.yaml +4 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +19 -0
- package/presets/fleet/roles/Developer.yaml +19 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
- package/presets/fleet/room_templates/pair.yaml +10 -0
- package/presets/fleet/room_templates/single.yaml +7 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
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,
|
|
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
|
|
24
|
-
if (o.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
205
|
-
const
|
|
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:
|
|
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
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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),
|
|
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(
|
|
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
|
-
|
|
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
|
-
...
|
|
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({
|
package/dist/temp-lifecycle.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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>,
|
|
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
|
package/dist/watchdog/config.js
CHANGED
|
@@ -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
|
|
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', '
|
|
8
|
-
'identity', 'timeout', 'keep_reports', 'alert_cooldown', 'prompt_file',
|
|
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,
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
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,
|
|
100
|
-
|
|
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,
|
|
115
|
+
promptFile: w.prompt_file, sourceFile: baseFile,
|
|
106
116
|
});
|
|
107
117
|
}
|
|
108
118
|
return out;
|
package/dist/watchdog/run.js
CHANGED
|
@@ -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,
|
|
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,
|
|
87
|
+
function buildWatchdogRole(wd, _cfg) {
|
|
88
88
|
return {
|
|
89
|
-
|
|
90
|
-
|
|
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;
|