@ours.network/fleet 1.1.0-nightly.1 → 1.1.0-nightly.3
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 +76 -93
- package/dist/application/role-creation-service.d.ts +2 -21
- package/dist/application/role-creation-service.js +15 -64
- package/dist/application/role-removal-service.js +6 -3
- package/dist/application/task-room-service.js +7 -2
- package/dist/application/types.d.ts +1 -1
- package/dist/briefing.js +5 -5
- package/dist/build-info.json +4 -4
- package/dist/cli.js +48 -38
- package/dist/config.d.ts +65 -2
- package/dist/config.js +337 -25
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +2 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +48 -97
- package/dist/doctor.js +4 -4
- package/dist/fleet-proxy.js +30 -8
- package/dist/generated-agent-source.d.ts +9 -0
- package/dist/generated-agent-source.js +53 -0
- package/dist/harness/agent-session.d.ts +13 -0
- package/dist/harness/claude-code-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +4 -0
- package/dist/harness/claude-code.js +10 -0
- package/dist/harness/codex-session.d.ts +4 -0
- package/dist/harness/codex-session.js +4 -0
- package/dist/harness/codex.js +14 -0
- package/dist/harness/registry.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init-guidance.d.ts +1 -0
- package/dist/init-guidance.js +5 -0
- package/dist/model-env.d.ts +2 -3
- package/dist/model-env.js +3 -5
- package/dist/ops.js +4 -2
- package/dist/owner-channel/commands.js +2 -1
- package/dist/resolved-plan.d.ts +3 -2
- package/dist/resolved-plan.js +21 -5
- package/dist/rooms-tasks/cli.js +2 -1
- package/dist/rooms-tasks/config.js +24 -9
- package/dist/rooms-tasks/provision.js +71 -36
- package/dist/rooms-tasks/templates.js +6 -6
- package/dist/rooms-tasks/types.d.ts +8 -15
- package/dist/rooms-tasks/types.js +1 -4
- package/dist/sensitive-config.d.ts +2 -0
- package/dist/sensitive-config.js +4 -0
- package/dist/spawn.d.ts +8 -33
- package/dist/spawn.js +52 -211
- 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 +11 -8
- package/dist/web/fleet-config-service.js +383 -183
- package/dist/web/topology-promote.js +25 -15
- package/dist/web/topology.js +2 -2
- package/dist/web/yaml-document-edit.js +2 -0
- package/dist/web-app/assets/index-BbTc5KtZ.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/examples/fleet/agents/Alice.yaml +11 -0
- package/examples/fleet/agents/FleetCoordinator.yaml +7 -0
- package/examples/fleet/brains/claude-default.yaml +5 -0
- package/examples/fleet/roles/coordinator.yaml +5 -0
- package/examples/fleet/roles/developer.yaml +3 -0
- package/examples/fleet.yaml +74 -0
- package/package.json +2 -1
- package/dist/web-app/assets/index-DhMDVRU1.js +0 -10
|
@@ -100,6 +100,9 @@ export interface RoomMemberLaunchState {
|
|
|
100
100
|
/** Expected authenticated proxy caller while adopting a post-spawn crash. */
|
|
101
101
|
caller_role?: string;
|
|
102
102
|
mission_sha256?: string;
|
|
103
|
+
/** Redacted, deterministic effective Agent launch definition retained for retry inspection. */
|
|
104
|
+
agent_definition?: Record<string, unknown>;
|
|
105
|
+
agent_fingerprint?: string;
|
|
103
106
|
launch_id?: string;
|
|
104
107
|
updated_at: string;
|
|
105
108
|
error?: string;
|
|
@@ -189,19 +192,10 @@ export interface TemplateMemberSlot {
|
|
|
189
192
|
slot: string;
|
|
190
193
|
role: string;
|
|
191
194
|
count: number;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
harness?: string;
|
|
197
|
-
model?: string;
|
|
198
|
-
model_chain?: string[];
|
|
199
|
-
permissions?: Record<string, unknown>;
|
|
200
|
-
isolation?: Record<string, unknown>;
|
|
201
|
-
cwd?: string;
|
|
202
|
-
env?: Record<string, string>;
|
|
203
|
-
persona?: string;
|
|
204
|
-
mission?: string;
|
|
195
|
+
/** Canonical Agent definition, or stable reference to a declared Agent. */
|
|
196
|
+
agent: import('../config.js').AgentDefinition | {
|
|
197
|
+
ref: string;
|
|
198
|
+
};
|
|
205
199
|
}
|
|
206
200
|
export interface TemplateRoomConfig {
|
|
207
201
|
quiet_membership?: boolean;
|
|
@@ -252,5 +246,4 @@ export declare const ROOMS_COWORK_KEYS: readonly ["config"];
|
|
|
252
246
|
export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
|
|
253
247
|
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
|
|
254
248
|
export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
|
|
255
|
-
export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "
|
|
256
|
-
export declare const TEMPLATE_OVERRIDE_KEYS: readonly ["harness", "model", "model_chain", "permissions", "isolation", "cwd", "env", "persona", "mission"];
|
|
249
|
+
export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "agent"];
|
|
@@ -14,7 +14,4 @@ export const ROOMS_COWORK_KEYS = ['config'];
|
|
|
14
14
|
export const ROOMS_DEFAULTS_KEYS = ['template', 'attach_owner', 'close_when_task_done'];
|
|
15
15
|
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for'];
|
|
16
16
|
export const TEMPLATE_KEYS = ['version', 'description', 'room', 'contract', 'members', 'override_builtin'];
|
|
17
|
-
export const TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', '
|
|
18
|
-
export const TEMPLATE_OVERRIDE_KEYS = [
|
|
19
|
-
'harness', 'model', 'model_chain', 'permissions', 'isolation', 'cwd', 'env', 'persona', 'mission',
|
|
20
|
-
];
|
|
17
|
+
export const TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', 'agent'];
|
package/dist/spawn.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IsolationConfig } from './isolation/types.js';
|
|
2
|
-
import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type
|
|
2
|
+
import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type MonitorConfig, type UnattendedMode, type RoomMemberStartup, type AgentDefinition, type AgentSelection } from './config.js';
|
|
3
3
|
import { type OpsDeps } from './ops.js';
|
|
4
4
|
import { type CreationDeps, type CreationProvenance } from './creation.js';
|
|
5
5
|
import './harness/claude-code.js';
|
|
@@ -13,34 +13,18 @@ export declare let lastProvenance: CreationProvenance | undefined;
|
|
|
13
13
|
export interface SpawnOpts {
|
|
14
14
|
name: string;
|
|
15
15
|
temp?: boolean;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
brain?: AgentSelection;
|
|
17
|
+
role?: AgentSelection;
|
|
18
|
+
/** Trusted canonical definition used by room provisioning; never a second schema. */
|
|
19
|
+
agentDefinition?: AgentDefinition;
|
|
20
20
|
identity?: string;
|
|
21
21
|
cwd?: string;
|
|
22
22
|
coordinator?: string;
|
|
23
|
-
/** null explicitly selects the harness default; undefined retains normal fleet inheritance. */
|
|
24
|
-
model?: string | null;
|
|
25
|
-
permissionMode?: string;
|
|
26
23
|
approval?: ApprovalMode;
|
|
27
24
|
filesystem?: FilesystemMode;
|
|
28
25
|
unattended?: UnattendedMode;
|
|
29
|
-
sandbox?: string;
|
|
30
|
-
profile?: string;
|
|
31
|
-
launcher?: string;
|
|
32
|
-
search?: boolean;
|
|
33
|
-
codexConfig?: Record<string, string | number | boolean>;
|
|
34
|
-
reasoningEffort?: string | null;
|
|
35
|
-
addDirs?: string[];
|
|
36
|
-
monitor?: boolean;
|
|
37
26
|
/** Typed external monitor configuration used by trusted creation surfaces. */
|
|
38
27
|
monitorConfig?: Partial<MonitorConfig>;
|
|
39
|
-
bioFile?: string;
|
|
40
|
-
personaFile?: string;
|
|
41
|
-
/** Inline profile values for trusted typed callers such as the local web service. */
|
|
42
|
-
bio?: string;
|
|
43
|
-
persona?: string;
|
|
44
28
|
/** Internal, non-sensitive provenance correlation for typed presentation layers. */
|
|
45
29
|
surface?: 'cli' | 'web' | 'agent';
|
|
46
30
|
creationActionId?: string;
|
|
@@ -61,12 +45,7 @@ export interface SpawnOpts {
|
|
|
61
45
|
dryRun?: boolean;
|
|
62
46
|
json?: boolean;
|
|
63
47
|
}
|
|
64
|
-
export declare function
|
|
65
|
-
bio?: string;
|
|
66
|
-
persona?: string;
|
|
67
|
-
};
|
|
68
|
-
/** Pure option-to-role mapping shared by CLI and application services. */
|
|
69
|
-
export declare function buildRoleConfig(o: SpawnOpts, defaultHarness?: string): RoleConfig;
|
|
48
|
+
export declare function agentDefinitionFromSpawn(o: SpawnOpts): AgentDefinition;
|
|
70
49
|
/**
|
|
71
50
|
* Read and validate an `--isolation-file`. The file is the existing
|
|
72
51
|
* `isolation:` mapping and nothing else — the same schema, the same validator
|
|
@@ -78,16 +57,12 @@ export declare function buildRoleConfig(o: SpawnOpts, defaultHarness?: string):
|
|
|
78
57
|
*/
|
|
79
58
|
export declare function readIsolationFile(path: string): IsolationConfig;
|
|
80
59
|
export declare function validateSpawnOpts(o: SpawnOpts): void;
|
|
81
|
-
/** Read mission text without trimming or newline rewriting. */
|
|
82
|
-
export declare function readMissionFile(path: string): string;
|
|
83
60
|
/** The ours identity a spawn will bind: explicit, else the role name. */
|
|
84
61
|
export declare const effectiveIdentity: (o: SpawnOpts) => string;
|
|
85
62
|
export interface SpawnDryRun {
|
|
86
63
|
schemaVersion: 1;
|
|
87
64
|
warning: string;
|
|
88
|
-
roleDocument:
|
|
89
|
-
roles: Record<string, RoleConfig>;
|
|
90
|
-
};
|
|
65
|
+
roleDocument: Record<string, unknown>;
|
|
91
66
|
resolvedRole: ResolvedRole;
|
|
92
67
|
}
|
|
93
68
|
/**
|
|
@@ -95,7 +70,7 @@ export interface SpawnDryRun {
|
|
|
95
70
|
* or writing state. Collision checks are necessarily a point-in-time snapshot.
|
|
96
71
|
*/
|
|
97
72
|
export declare function spawnDryRun(o: SpawnOpts): SpawnDryRun;
|
|
98
|
-
/** Permanent spawn: persist
|
|
73
|
+
/** Permanent spawn: persist one bare Agent document under the selected v2 root. */
|
|
99
74
|
export declare function spawnPermanent(o: SpawnOpts, deps: OpsDeps, creation?: CreationDeps): Promise<string>;
|
|
100
75
|
export type { SupervisorLauncher } from './temp-lifecycle.js';
|
|
101
76
|
/** Temp spawn: live state under ~/.ours-fleet/tmp, independent transient supervision. */
|
package/dist/spawn.js
CHANGED
|
@@ -2,98 +2,56 @@ import { spawn as spawnChild } from 'node:child_process';
|
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { parse, stringify } from 'yaml';
|
|
5
|
-
import { agentDir,
|
|
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';
|
|
12
11
|
import { VERSION } from './version.js';
|
|
12
|
+
import { recordGeneratedAgentSource } from './generated-agent-source.js';
|
|
13
13
|
import './harness/claude-code.js';
|
|
14
14
|
import './harness/codex.js';
|
|
15
|
-
import { getAdapter } from './harness/registry.js';
|
|
16
15
|
import { archiveTempState, makeTempSupervisorLauncher, prepareTempSupervisor, reclaimStaleTempState, } from './temp-lifecycle.js';
|
|
17
16
|
/**
|
|
18
17
|
* The provenance record written by the most recent spawn in this process, so
|
|
19
18
|
* the CLI can print the same summary it persisted rather than rebuilding it.
|
|
20
19
|
*/
|
|
21
20
|
export let lastProvenance;
|
|
22
|
-
export function
|
|
23
|
-
if (o.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
27
41
|
return {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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) } : {}),
|
|
32
49
|
};
|
|
33
50
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
r.harness = o.harness;
|
|
39
|
-
if (o.session)
|
|
40
|
-
r.session = o.session;
|
|
41
|
-
if (o.identity)
|
|
42
|
-
r.identity = o.identity;
|
|
43
|
-
if (o.cwd)
|
|
44
|
-
r.cwd = o.cwd;
|
|
45
|
-
if (o.coordinator)
|
|
46
|
-
r.coordinator = o.coordinator;
|
|
47
|
-
if (o.missionFile)
|
|
48
|
-
r.mission = readMissionFile(o.missionFile);
|
|
49
|
-
else if (o.mission !== undefined)
|
|
50
|
-
r.mission = o.mission;
|
|
51
|
-
if (o.model === null)
|
|
52
|
-
r.model = null;
|
|
53
|
-
else if (o.model?.trim())
|
|
54
|
-
r.model = o.model.trim();
|
|
55
|
-
const harness = o.harness ?? defaultHarness;
|
|
56
|
-
const harnessOptions = {};
|
|
57
|
-
if (o.permissionMode)
|
|
58
|
-
harnessOptions[harness === 'claude-code' ? 'permission_mode' : 'approval'] = o.permissionMode;
|
|
59
|
-
if (o.sandbox)
|
|
60
|
-
harnessOptions.sandbox = o.sandbox;
|
|
61
|
-
if (o.profile)
|
|
62
|
-
harnessOptions.profile = o.profile;
|
|
63
|
-
if (o.launcher)
|
|
64
|
-
harnessOptions.launcher = o.launcher;
|
|
65
|
-
if (o.search === true)
|
|
66
|
-
harnessOptions.search = true;
|
|
67
|
-
const codexConfig = { ...(o.codexConfig ?? {}) };
|
|
68
|
-
if (o.reasoningEffort && harness === 'codex')
|
|
69
|
-
codexConfig.model_reasoning_effort = o.reasoningEffort;
|
|
70
|
-
if (Object.keys(codexConfig).length)
|
|
71
|
-
harnessOptions.config = codexConfig;
|
|
72
|
-
if (o.reasoningEffort && harness === 'claude-code')
|
|
73
|
-
harnessOptions.effort = o.reasoningEffort;
|
|
74
|
-
if (o.addDirs?.length)
|
|
75
|
-
harnessOptions.add_dirs = o.addDirs;
|
|
76
|
-
if (o.monitor === true)
|
|
77
|
-
harnessOptions.monitor = true;
|
|
78
|
-
if (Object.keys(harnessOptions).length)
|
|
79
|
-
r.harness_options = harnessOptions;
|
|
80
|
-
if (o.approval || o.filesystem || o.unattended) {
|
|
81
|
-
r.permissions = {
|
|
82
|
-
...(o.approval ? { approval: o.approval } : {}),
|
|
83
|
-
...(o.filesystem ? { filesystem: o.filesystem } : {}),
|
|
84
|
-
...(o.unattended ? { unattended: o.unattended } : {}),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
const profile = profileValues(o);
|
|
88
|
-
if (profile.bio)
|
|
89
|
-
r.bio = profile.bio;
|
|
90
|
-
if (profile.persona)
|
|
91
|
-
r.persona = profile.persona;
|
|
92
|
-
if (o.isolationFile)
|
|
93
|
-
r.isolation = readIsolationFile(o.isolationFile);
|
|
94
|
-
if (o.monitorConfig)
|
|
95
|
-
r.monitor = { ...o.monitorConfig };
|
|
96
|
-
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) };
|
|
97
55
|
}
|
|
98
56
|
/**
|
|
99
57
|
* Read and validate an `--isolation-file`. The file is the existing
|
|
@@ -121,12 +79,6 @@ export function readIsolationFile(path) {
|
|
|
121
79
|
return cfg;
|
|
122
80
|
}
|
|
123
81
|
export function validateSpawnOpts(o) {
|
|
124
|
-
if (o.mission !== undefined && o.missionFile)
|
|
125
|
-
throw new Error('--mission and --mission-file are mutually exclusive');
|
|
126
|
-
if (o.session === 'tmux')
|
|
127
|
-
throw new Error("invalid --session 'tmux'; tmux is no longer supported; use --session acp");
|
|
128
|
-
if (o.session && o.session !== 'acp')
|
|
129
|
-
throw new Error(`invalid --session '${o.session}'; allowed: acp`);
|
|
130
82
|
if (o.approval && !['ask', 'auto', 'allow', 'deny'].includes(o.approval))
|
|
131
83
|
throw new Error(`invalid --approval '${o.approval}'; allowed: ask, auto, allow (deprecated alias: deny)`);
|
|
132
84
|
if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
|
|
@@ -137,23 +89,12 @@ export function validateSpawnOpts(o) {
|
|
|
137
89
|
throw new Error(`invalid role name '${o.name}'`);
|
|
138
90
|
if (o.identity && !/^[A-Za-z0-9_-]+$/.test(o.identity))
|
|
139
91
|
throw new Error(`invalid identity name '${o.identity}'`);
|
|
140
|
-
if (o.model && (o.model.length > 128 || /[\0-\x1f\x7f]/.test(o.model)))
|
|
141
|
-
throw new Error('model must be printable and at most 128 characters');
|
|
142
92
|
if (o.monitorConfig) {
|
|
143
93
|
const problems = validateMonitorConfig(o.monitorConfig);
|
|
144
94
|
if (problems.length)
|
|
145
95
|
throw new Error(problems.join('; '));
|
|
146
96
|
}
|
|
147
97
|
}
|
|
148
|
-
/** Read mission text without trimming or newline rewriting. */
|
|
149
|
-
export function readMissionFile(path) {
|
|
150
|
-
try {
|
|
151
|
-
return readFileSync(path, 'utf8');
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
throw new Error(`--mission-file ${path}: ${e.message}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
98
|
/**
|
|
158
99
|
* Reject names that are already USED. This is a precondition, not a claim: it
|
|
159
100
|
* runs INSIDE the creation transaction, after both names are reserved, so the
|
|
@@ -182,62 +123,13 @@ export function spawnDryRun(o) {
|
|
|
182
123
|
validateSpawnOpts(o);
|
|
183
124
|
if (o.isolationFile)
|
|
184
125
|
readIsolationFile(o.isolationFile);
|
|
185
|
-
if (o.missionFile)
|
|
186
|
-
readMissionFile(o.missionFile);
|
|
187
126
|
assertNameFree(o);
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
const harnessOptions = {
|
|
191
|
-
...(cfg.defaults.harness_options ?? {}),
|
|
192
|
-
...(raw.harness_options ?? {}),
|
|
193
|
-
};
|
|
194
|
-
const harness = raw.harness ?? cfg.defaults.harness ?? 'claude-code';
|
|
195
|
-
const defaultHarness = cfg.defaults.harness ?? 'claude-code';
|
|
196
|
-
const inheritsModelDefaults = harness === defaultHarness && raw.model !== null;
|
|
197
|
-
const authProxy = resolveAuthProxy(cfg.defaults.auth_proxy, raw.auth_proxy);
|
|
198
|
-
// One resolution for the environment and the model it pins (src/model-env.ts).
|
|
199
|
-
const modelEnv = resolveRoleModelEnv({
|
|
200
|
-
harness,
|
|
201
|
-
model: resolveRoleModel(raw.model, raw.harness, cfg.defaults),
|
|
202
|
-
modelWasExplicit: raw.model !== undefined,
|
|
203
|
-
defaultsEnv: (cfg.defaults.env ?? {}),
|
|
204
|
-
roleEnv: raw.env,
|
|
205
|
-
...(authProxy ? { authProxyBaseUrl: authProxy.base_url } : {}),
|
|
206
|
-
});
|
|
207
|
-
const model = modelEnv.model;
|
|
208
|
-
const session = raw.session ?? cfg.defaults.session ?? 'acp';
|
|
209
|
-
const resolvedRole = {
|
|
210
|
-
...raw,
|
|
211
|
-
name: o.name,
|
|
212
|
-
sourceFile: o.temp ? '(temp dry-run)' : join(fleetDDir(), `${o.name}.yaml`),
|
|
213
|
-
harness,
|
|
214
|
-
session,
|
|
215
|
-
session_options: raw.session_options,
|
|
216
|
-
permissions: resolvePermissions(cfg.defaults.permissions, raw.permissions),
|
|
217
|
-
permissionsDeclared: raw.permissions !== undefined || cfg.defaults.permissions !== undefined,
|
|
218
|
-
identity: effectiveIdentity(o),
|
|
219
|
-
model,
|
|
220
|
-
model_chain: resolveModelChain(model, raw.model_chain ?? (inheritsModelDefaults
|
|
221
|
-
? cfg.defaults.model_chain
|
|
222
|
-
: undefined)),
|
|
223
|
-
harness_options: Object.keys(harnessOptions).length ? harnessOptions : undefined,
|
|
224
|
-
isolation: raw.isolation ?? cfg.defaults.isolation,
|
|
225
|
-
monitor: resolveMonitorConfig(cfg.defaults.monitor, raw.monitor),
|
|
226
|
-
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, raw.owner_channel, session),
|
|
227
|
-
worklog: resolveWorklogPolicy(cfg.defaults.worklog, raw.worklog),
|
|
228
|
-
auth_proxy: authProxy,
|
|
229
|
-
};
|
|
230
|
-
resolvedRole.env = modelEnv.env;
|
|
231
|
-
const adapter = getAdapter(resolvedRole.harness);
|
|
232
|
-
if (resolvedRole.auth_proxy && resolvedRole.harness !== 'claude-code')
|
|
233
|
-
throw new Error('auth_proxy is supported only by claude-code');
|
|
234
|
-
const optionProblems = adapter.validateOptions(resolvedRole.harness_options, resolvedRole);
|
|
235
|
-
if (optionProblems.length)
|
|
236
|
-
throw new Error(optionProblems.map(problem => `${problem.path}: ${problem.message}`).join('; '));
|
|
127
|
+
const resolved = resolvedSpawn(o);
|
|
128
|
+
const resolvedRole = resolved.role;
|
|
237
129
|
return {
|
|
238
130
|
schemaVersion: 1,
|
|
239
131
|
warning: 'collision checks are a snapshot; a real spawn reserves names atomically',
|
|
240
|
-
roleDocument:
|
|
132
|
+
roleDocument: structuredClone(resolved.definition),
|
|
241
133
|
resolvedRole,
|
|
242
134
|
};
|
|
243
135
|
}
|
|
@@ -253,22 +145,15 @@ function provenanceSettings(o, defaults) {
|
|
|
253
145
|
const perms = (defaults.permissions ?? {});
|
|
254
146
|
const callerDefaults = new Set(o.inheritedFromCaller ?? []);
|
|
255
147
|
const tagged = (key, entry) => callerDefaults.has(key) ? { ...entry, source: 'caller-role' } : entry;
|
|
256
|
-
const
|
|
257
|
-
const inheritedModel = resolveRoleModel(undefined, o.harness, defaults);
|
|
148
|
+
const selection = (value) => value && 'ref' in value ? `ref:${value.ref}` : value ? 'inline' : undefined;
|
|
258
149
|
return {
|
|
259
|
-
|
|
260
|
-
|
|
150
|
+
brain: tagged('brain', provenanceOf(selection(o.brain), undefined, undefined)),
|
|
151
|
+
role: tagged('role', provenanceOf(selection(o.role), undefined, undefined)),
|
|
261
152
|
identity: o.identity
|
|
262
153
|
? { value: o.identity, source: 'cli' }
|
|
263
154
|
: { value: o.name, source: 'built-in' }, // defaults to the role name
|
|
264
155
|
cwd: tagged('cwd', provenanceOf(o.cwd, undefined, undefined)),
|
|
265
|
-
model: tagged('model', o.model === null
|
|
266
|
-
? { value: undefined, source: 'cli' }
|
|
267
|
-
: explicitModel
|
|
268
|
-
? { value: explicitModel, source: 'cli' }
|
|
269
|
-
: { value: inheritedModel, source: inheritedModel ? 'fleet-default' : 'built-in' }),
|
|
270
156
|
coordinator: tagged('coordinator', provenanceOf(o.coordinator, undefined, undefined)),
|
|
271
|
-
permission_mode: provenanceOf(o.permissionMode, undefined, undefined),
|
|
272
157
|
approval: tagged('approval', provenanceOf(o.approval, perms.approval, 'ask')),
|
|
273
158
|
filesystem: tagged('filesystem', provenanceOf(o.filesystem, perms.filesystem, 'workspace')),
|
|
274
159
|
unattended: tagged('unattended', provenanceOf(o.unattended, perms.unattended, 'deny')),
|
|
@@ -278,13 +163,12 @@ function provenanceSettings(o, defaults) {
|
|
|
278
163
|
monitor: tagged('monitorConfig', provenanceOf(o.monitorConfig, defaults.monitor, { mode: 'fleet' })),
|
|
279
164
|
};
|
|
280
165
|
}
|
|
281
|
-
/** Permanent spawn: persist
|
|
166
|
+
/** Permanent spawn: persist one bare Agent document under the selected v2 root. */
|
|
282
167
|
export async function spawnPermanent(o, deps, creation = {}) {
|
|
283
168
|
validateSpawnOpts(o);
|
|
284
169
|
if (o.isolationFile)
|
|
285
170
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
286
|
-
|
|
287
|
-
readMissionFile(o.missionFile); // fail before reserving
|
|
171
|
+
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
288
172
|
// Reserve name and identity together before anything is written or started.
|
|
289
173
|
// A loser of the race creates no config, state, or service.
|
|
290
174
|
creation.onStage?.('reserving');
|
|
@@ -294,7 +178,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
294
178
|
// Establish the identity BEFORE the service is enabled, and record
|
|
295
179
|
// what was actually guaranteed so the briefing can say something true.
|
|
296
180
|
creation.onStage?.('checking_identity');
|
|
297
|
-
const guarantee = await ensureIdentity(effectiveIdentity(o),
|
|
181
|
+
const guarantee = await ensureIdentity(effectiveIdentity(o), { bio: prepared.role.bio, persona: prepared.role.persona }, creation.identityProvisioner ?? deps.identityProvisioner ?? daemonIdentityProvisioner(), deps.log);
|
|
298
182
|
creation.onStage?.('checking_identity', {
|
|
299
183
|
result: guarantee.evidence, guarantee: guarantee.state,
|
|
300
184
|
});
|
|
@@ -307,12 +191,11 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
307
191
|
await creation.identityProvisioner?.remove?.(effectiveIdentity(o));
|
|
308
192
|
},
|
|
309
193
|
});
|
|
310
|
-
|
|
194
|
+
const agentRoot = join(splitRootFor(o.configPath ?? defaultConfigPath()), 'agents');
|
|
195
|
+
mkdirSync(agentRoot, { recursive: true });
|
|
311
196
|
creation.onStage?.('writing_role');
|
|
312
|
-
const file = join(
|
|
313
|
-
writeRoleFile(tx, file, stringify(
|
|
314
|
-
roles: { [o.name]: buildRoleConfig(o, cfg.defaults.harness) },
|
|
315
|
-
}));
|
|
197
|
+
const file = join(agentRoot, `${o.name}.yaml`);
|
|
198
|
+
writeRoleFile(tx, file, stringify(agentDefinitionFromSpawn(o)));
|
|
316
199
|
// `up` materialises the state dir and registers the service. Journal the
|
|
317
200
|
// dir before it exists so a failure leaves the name genuinely reusable
|
|
318
201
|
// rather than blocked by a half-built directory.
|
|
@@ -343,6 +226,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
343
226
|
});
|
|
344
227
|
mkdirSync(agentDir(o.name), { recursive: true });
|
|
345
228
|
writeProvenance(agentDir(o.name), provenance);
|
|
229
|
+
recordGeneratedAgentSource(agentDir(o.name), o.configPath ?? defaultConfigPath(), file);
|
|
346
230
|
creation.onStage?.('registering_supervisor');
|
|
347
231
|
await up(loadConfig(o.configPath), [o.name], {
|
|
348
232
|
...deps,
|
|
@@ -380,8 +264,7 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
380
264
|
validateSpawnOpts(o);
|
|
381
265
|
if (o.isolationFile)
|
|
382
266
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
383
|
-
|
|
384
|
-
readMissionFile(o.missionFile); // fail before reserving
|
|
267
|
+
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
385
268
|
// Retire only supervisors whose recorded owner is definitively stopped. This
|
|
386
269
|
// bounded pass keeps the active roster clean without deleting old evidence.
|
|
387
270
|
await reclaimStaleTempState();
|
|
@@ -394,56 +277,14 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
394
277
|
creation.onStage?.('checking_identity', {
|
|
395
278
|
result: 'unknown', guarantee: 'unverified',
|
|
396
279
|
});
|
|
397
|
-
return spawnTempInner(o, binPath, launch, tx, creation.onStage);
|
|
280
|
+
return spawnTempInner(o, prepared.role, binPath, launch, tx, creation.onStage);
|
|
398
281
|
}, creation);
|
|
399
282
|
}
|
|
400
|
-
async function spawnTempInner(o, binPath, launch, tx, onStage) {
|
|
283
|
+
async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
401
284
|
const cfg = loadConfig(o.configPath);
|
|
402
|
-
const defaultHarness = cfg.defaults.harness;
|
|
403
|
-
const fromOpts = buildRoleConfig(o, defaultHarness);
|
|
404
|
-
const mergedHarnessOptions = {
|
|
405
|
-
...(cfg.defaults.harness_options ?? {}),
|
|
406
|
-
...(fromOpts.harness_options ?? {}),
|
|
407
|
-
};
|
|
408
|
-
const harness = o.harness ?? defaultHarness ?? 'claude-code';
|
|
409
|
-
const inheritsModelDefaults = harness === (defaultHarness ?? 'claude-code') && o.model !== null;
|
|
410
|
-
const tempAuthProxy = resolveAuthProxy(cfg.defaults.auth_proxy, fromOpts.auth_proxy);
|
|
411
|
-
// An explicitly requested --model must reach the child, not just the banner
|
|
412
|
-
// (src/model-env.ts).
|
|
413
|
-
const modelEnv = resolveRoleModelEnv({
|
|
414
|
-
harness,
|
|
415
|
-
model: resolveRoleModel(o.model, o.harness, cfg.defaults),
|
|
416
|
-
modelWasExplicit: o.model !== undefined,
|
|
417
|
-
defaultsEnv: (cfg.defaults.env ?? {}),
|
|
418
|
-
roleEnv: fromOpts.env,
|
|
419
|
-
...(tempAuthProxy ? { authProxyBaseUrl: tempAuthProxy.base_url } : {}),
|
|
420
|
-
});
|
|
421
|
-
const model = modelEnv.model;
|
|
422
|
-
const session = o.session ?? cfg.defaults.session ?? 'acp';
|
|
423
285
|
const role = {
|
|
424
|
-
...
|
|
425
|
-
name: o.name,
|
|
426
|
-
harness,
|
|
427
|
-
session,
|
|
428
|
-
identity: o.identity ?? o.name,
|
|
429
|
-
model,
|
|
430
|
-
model_chain: resolveModelChain(model, fromOpts.model_chain ?? (inheritsModelDefaults
|
|
431
|
-
? cfg.defaults.model_chain
|
|
432
|
-
: undefined)),
|
|
433
|
-
harness_options: Object.keys(mergedHarnessOptions).length ? mergedHarnessOptions : undefined,
|
|
434
|
-
permissions: resolvePermissions(cfg.defaults.permissions, fromOpts.permissions),
|
|
435
|
-
permissionsDeclared: fromOpts.permissions !== undefined || cfg.defaults.permissions !== undefined,
|
|
436
|
-
// Temp agents inherit the fleet-wide monitor defaults via the snapshot.
|
|
437
|
-
monitor: resolveMonitorConfig(cfg.defaults.monitor, fromOpts.monitor),
|
|
438
|
-
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, fromOpts.owner_channel, session),
|
|
439
|
-
worklog: resolveWorklogPolicy(cfg.defaults.worklog, fromOpts.worklog),
|
|
440
|
-
auth_proxy: tempAuthProxy,
|
|
441
|
-
sourceFile: '(temp)',
|
|
442
|
-
roomMemberStartup: o.roomMemberStartup,
|
|
286
|
+
...preparedRole, sourceFile: '(temp)', loops: undefined, roomMemberStartup: o.roomMemberStartup,
|
|
443
287
|
};
|
|
444
|
-
role.env = modelEnv.env;
|
|
445
|
-
if (role.auth_proxy && role.harness !== 'claude-code')
|
|
446
|
-
throw new Error('auth_proxy is supported only by claude-code');
|
|
447
288
|
onStage?.('writing_role');
|
|
448
289
|
const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
|
|
449
290
|
const provenance = buildProvenance({
|
|
@@ -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
|