@ours.network/fleet 1.0.5 → 1.1.0-nightly.10
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 +122 -133
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.d.ts +0 -1
- package/dist/application/capabilities.js +3 -13
- package/dist/application/fleet-query-service.d.ts +0 -3
- package/dist/application/fleet-query-service.js +14 -25
- package/dist/application/role-creation-service.d.ts +5 -24
- package/dist/application/role-creation-service.js +27 -67
- package/dist/application/role-removal-service.js +6 -3
- package/dist/application/role-repository.d.ts +1 -2
- package/dist/application/role-repository.js +7 -7
- package/dist/application/session-control.d.ts +0 -12
- package/dist/application/session-control.js +0 -42
- package/dist/application/session-mutations.d.ts +5 -5
- package/dist/application/task-room-service.d.ts +1 -1
- package/dist/application/task-room-service.js +154 -16
- package/dist/application/types.d.ts +1 -8
- package/dist/briefing.js +12 -11
- package/dist/build-info.json +5 -5
- package/dist/cli.js +143 -63
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +66 -6
- package/dist/config.js +398 -43
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +2 -2
- 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 +103 -127
- package/dist/doctor.js +18 -11
- package/dist/fleet-command-audit.d.ts +160 -0
- package/dist/fleet-command-audit.js +433 -0
- package/dist/fleet-proxy.d.ts +3 -1
- 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/acp-session-transport.d.ts +8 -0
- package/dist/harness/acp-session-transport.js +3 -0
- package/dist/harness/agent-session.d.ts +51 -0
- package/dist/harness/agent-session.js +1 -0
- package/dist/harness/claude-code-session.d.ts +24 -0
- package/dist/harness/claude-code-session.js +36 -0
- package/dist/harness/claude-code.d.ts +2 -1
- package/dist/harness/claude-code.js +41 -81
- package/dist/harness/codex-session.d.ts +24 -0
- package/dist/harness/codex-session.js +36 -0
- package/dist/harness/codex.d.ts +2 -1
- package/dist/harness/codex.js +55 -45
- package/dist/harness/registry.js +4 -0
- package/dist/harness/types.d.ts +6 -38
- package/dist/index.d.ts +4 -6
- package/dist/index.js +2 -4
- package/dist/init-guidance.d.ts +1 -0
- package/dist/init-guidance.js +6 -0
- package/dist/isolation/resources.d.ts +3 -7
- package/dist/isolation/resources.js +3 -7
- package/dist/model-env.d.ts +4 -6
- package/dist/model-env.js +5 -8
- package/dist/monitor.d.ts +1 -24
- package/dist/monitor.js +5 -140
- package/dist/ops.js +4 -2
- package/dist/owner-channel/channel.d.ts +58 -2
- package/dist/owner-channel/channel.js +269 -29
- package/dist/owner-channel/commands.d.ts +2 -0
- package/dist/owner-channel/commands.js +31 -13
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -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.d.ts +3 -2
- package/dist/resolved-plan.js +21 -5
- package/dist/rooms-tasks/cli.js +225 -40
- package/dist/rooms-tasks/config.js +24 -13
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/provision.js +98 -36
- package/dist/rooms-tasks/templates.d.ts +0 -1
- package/dist/rooms-tasks/templates.js +9 -76
- package/dist/rooms-tasks/types.d.ts +10 -16
- package/dist/rooms-tasks/types.js +1 -4
- package/dist/runner.d.ts +13 -16
- package/dist/runner.js +110 -122
- package/dist/sensitive-config.d.ts +2 -0
- package/dist/sensitive-config.js +4 -0
- package/dist/session/acp.d.ts +7 -2
- package/dist/session/acp.js +29 -6
- package/dist/session/activity.d.ts +2 -3
- package/dist/session/activity.js +2 -3
- package/dist/session/arbiter.d.ts +4 -4
- package/dist/session/control.d.ts +17 -4
- package/dist/session/control.js +35 -0
- package/dist/session/types.d.ts +11 -5
- package/dist/spawn.d.ts +8 -33
- package/dist/spawn.js +53 -213
- package/dist/supervisor/none.d.ts +1 -1
- package/dist/supervisor/none.js +2 -2
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/tmux.d.ts +1 -14
- package/dist/tmux.js +1 -51
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +30 -18
- package/dist/watchdog/run.js +12 -36
- package/dist/web/auth.d.ts +1 -1
- package/dist/web/fleet-config-service.d.ts +11 -8
- package/dist/web/fleet-config-service.js +384 -183
- package/dist/web/runtime.js +4 -15
- package/dist/web/server.d.ts +1 -3
- package/dist/web/server.js +3 -14
- package/dist/web/topology-promote.js +26 -19
- package/dist/web/topology.js +2 -2
- package/dist/web/yaml-document-edit.js +2 -0
- package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
- package/dist/web-app/assets/index-BbTc5KtZ.js +10 -0
- package/dist/web-app/index.html +2 -2
- 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 +5 -8
- package/presets/fleet/agents/Agent.yaml +3 -0
- package/presets/fleet/agents/Architect.yaml +3 -0
- package/presets/fleet/agents/Critic.yaml +3 -0
- package/presets/fleet/agents/Developer.yaml +3 -0
- package/presets/fleet/agents/Secretary.yaml +3 -0
- package/presets/fleet/agents/Tester.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/roles/Agent.yaml +6 -0
- package/presets/fleet/roles/Architect.yaml +6 -0
- package/presets/fleet/roles/Critic.yaml +6 -0
- package/presets/fleet/roles/Developer.yaml +6 -0
- package/presets/fleet/roles/Secretary.yaml +6 -0
- package/presets/fleet/roles/Tester.yaml +6 -0
- package/presets/fleet/room_templates/pair.yaml +11 -0
- package/presets/fleet/room_templates/single.yaml +8 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +5 -0
- package/dist/session/tmux.d.ts +0 -28
- package/dist/session/tmux.js +0 -80
- package/dist/web/terminal/bridge.d.ts +0 -27
- package/dist/web/terminal/bridge.js +0 -317
- package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
- package/dist/web-app/assets/index-BCBK78hw.js +0 -10
- package/dist/web-app/assets/index-DuC-xnX4.css +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
3
|
import { parse } from 'yaml';
|
|
4
4
|
import { advanceSaga, setSagaError, updateMemberSeats, updateMemberStartup, activateRoom, getRoomRecord, } from './room-state.js';
|
|
5
5
|
import { activateTask, updateTaskMembers, blockTask, unblockTask, getTask, } from './task-state.js';
|
|
6
6
|
import { spawnTemp } from '../spawn.js';
|
|
7
|
-
import { findRole } from '../config.js';
|
|
7
|
+
import { findRole, loadConfig } from '../config.js';
|
|
8
8
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from '../fleet-proxy.js';
|
|
9
9
|
import { controlRequest } from '../session/control.js';
|
|
10
10
|
import { SessionControlError } from '../session/types.js';
|
|
@@ -13,6 +13,7 @@ import { buildRoomMemberTask, sha256Text } from './member-startup.js';
|
|
|
13
13
|
import { agentDir } from '../paths.js';
|
|
14
14
|
import { readProvenance } from '../creation.js';
|
|
15
15
|
import { readTempSupervisor, secureStoppedTempArchive, tempArchiveForCreationAction, tempSupervisorLiveness, } from '../temp-lifecycle.js';
|
|
16
|
+
import { recordFleetAuditPresentation } from '../fleet-command-audit.js';
|
|
16
17
|
export function getBinPath() {
|
|
17
18
|
try {
|
|
18
19
|
return realpathSync(process.argv[1]);
|
|
@@ -21,6 +22,45 @@ export function getBinPath() {
|
|
|
21
22
|
return process.argv[1];
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
function canonicalJson(value) {
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
28
|
+
if (value && typeof value === 'object')
|
|
29
|
+
return `{${Object.entries(value)
|
|
30
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
31
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
|
|
32
|
+
return JSON.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
function selectionSummary(selection) {
|
|
35
|
+
if ('ref' in selection)
|
|
36
|
+
return `ref:${selection.ref} (reference)`;
|
|
37
|
+
return `inline:sha256:${createHash('sha256').update(canonicalJson(selection.inline)).digest('hex').slice(0, 16)} (inline)`;
|
|
38
|
+
}
|
|
39
|
+
function permissionSummary(definition) {
|
|
40
|
+
const permissions = definition.permissions;
|
|
41
|
+
if (!permissions)
|
|
42
|
+
return undefined;
|
|
43
|
+
return `approval=${permissions.approval},filesystem=${permissions.filesystem},unattended=${permissions.unattended}`;
|
|
44
|
+
}
|
|
45
|
+
function launchDefinition(definition) {
|
|
46
|
+
const selectionEvidence = (selection) => 'ref' in selection ? { kind: 'ref', id: selection.ref } : {
|
|
47
|
+
kind: 'inline', fingerprint: `sha256:${createHash('sha256')
|
|
48
|
+
.update(canonicalJson(selection.inline)).digest('hex').slice(0, 16)}`,
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
projection: {
|
|
52
|
+
brain: selectionEvidence(definition.brain), role: selectionEvidence(definition.role),
|
|
53
|
+
...(definition.permissions ? { permissions: {
|
|
54
|
+
approval: definition.permissions.approval,
|
|
55
|
+
filesystem: definition.permissions.filesystem,
|
|
56
|
+
unattended: definition.permissions.unattended,
|
|
57
|
+
} } : {}),
|
|
58
|
+
operationalFields: Object.keys(definition)
|
|
59
|
+
.filter(key => key !== 'brain' && key !== 'role').sort(),
|
|
60
|
+
},
|
|
61
|
+
fingerprint: createHash('sha256').update(canonicalJson(definition)).digest('hex'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
24
64
|
async function spawnRoomMember(options, binPath) {
|
|
25
65
|
const stateDir = process.env[FLEET_PROXY_STATE_DIR_ENV];
|
|
26
66
|
if (!stateDir)
|
|
@@ -52,28 +92,31 @@ function expandMembers(template, prefix) {
|
|
|
52
92
|
name: `${prefix}-${slot.slot}-${i}`,
|
|
53
93
|
slot: slot.slot,
|
|
54
94
|
coworkRole: slot.role,
|
|
55
|
-
|
|
56
|
-
overrides: slot.overrides,
|
|
95
|
+
agent: structuredClone(slot.agent),
|
|
57
96
|
});
|
|
58
97
|
}
|
|
59
98
|
}
|
|
60
99
|
return result;
|
|
61
100
|
}
|
|
62
101
|
function settingsFor(member, cfg) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
if (!('ref' in member.agent)) {
|
|
103
|
+
const role = member.agent.role;
|
|
104
|
+
if (cfg.files[0] && existsSync(cfg.files[0]) && statSync(cfg.files[0]).isFile())
|
|
105
|
+
loadConfig(cfg.files[0], { additionalAgent: {
|
|
106
|
+
id: `RoomPreflight${member.name.replace(/[^A-Za-z0-9_-]/g, '')}`.slice(0, 64),
|
|
107
|
+
definition: member.agent,
|
|
108
|
+
} });
|
|
109
|
+
return { definition: structuredClone(member.agent),
|
|
110
|
+
...('inline' in role && typeof role.inline.persona === 'string'
|
|
111
|
+
? { persona: role.inline.persona } : {}) };
|
|
66
112
|
}
|
|
67
|
-
|
|
68
|
-
const
|
|
113
|
+
const refRole = findRole(cfg, member.agent.ref);
|
|
114
|
+
const definition = cfg.agentDefinitions?.[member.agent.ref];
|
|
115
|
+
if (!definition)
|
|
116
|
+
throw new Error(`Agent '${member.agent.ref}' has no canonical definition`);
|
|
69
117
|
return {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
cwd: member.overrides?.cwd ?? refRole?.cwd,
|
|
73
|
-
persona: member.overrides?.persona ?? refRole?.persona,
|
|
74
|
-
approval: permissions?.approval,
|
|
75
|
-
filesystem: permissions?.filesystem,
|
|
76
|
-
unattended: permissions?.unattended,
|
|
118
|
+
definition: structuredClone(definition),
|
|
119
|
+
persona: refRole.persona,
|
|
77
120
|
};
|
|
78
121
|
}
|
|
79
122
|
function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCid) {
|
|
@@ -104,13 +147,11 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
|
|
|
104
147
|
const role = parse(readFileSync(`${dir}/role.yaml`, 'utf8'));
|
|
105
148
|
const startup = role.roomMemberStartup;
|
|
106
149
|
return role.identity === member.name
|
|
107
|
-
&& typeof role.mission === 'string'
|
|
108
|
-
&& sha256Text(role.mission) === taskSha
|
|
109
150
|
&& startup?.room_id === roomId
|
|
110
151
|
&& startup.room_identity_cid === roomIdentityCid
|
|
111
152
|
&& startup.identity_name === member.name
|
|
112
153
|
&& startup.role === member.coworkRole
|
|
113
|
-
&& startup.task ===
|
|
154
|
+
&& sha256Text(startup.task ?? '') === taskSha
|
|
114
155
|
&& (expectedInviteId === undefined || startup.invite_id === expectedInviteId)
|
|
115
156
|
&& typeof startup.invite === 'string'
|
|
116
157
|
&& startup.invite.length > 0;
|
|
@@ -199,10 +240,13 @@ async function launchMember(input) {
|
|
|
199
240
|
let effectiveActionId = actionId;
|
|
200
241
|
const attempt = (seat.launch?.attempt ?? 0) + 1;
|
|
201
242
|
const taskSha = sha256Text(startup.task);
|
|
243
|
+
const effectiveAgentDefinition = structuredClone(settings.definition);
|
|
244
|
+
const { projection: agentDefinition, fingerprint: agentFingerprint } = launchDefinition(effectiveAgentDefinition);
|
|
202
245
|
const proxyCaller = process.env[FLEET_PROXY_STATE_DIR_ENV]
|
|
203
246
|
? process.env[FLEET_PROXY_CALLER_ENV] : undefined;
|
|
204
247
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
205
248
|
state: 'intent', attempt, action_id: actionId, mission_sha256: taskSha,
|
|
249
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
206
250
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
207
251
|
updated_at: new Date().toISOString(),
|
|
208
252
|
} });
|
|
@@ -211,13 +255,7 @@ async function launchMember(input) {
|
|
|
211
255
|
name: member.name,
|
|
212
256
|
temp: true,
|
|
213
257
|
identity: member.name,
|
|
214
|
-
|
|
215
|
-
model: settings.model,
|
|
216
|
-
harness: settings.harness,
|
|
217
|
-
cwd: settings.cwd,
|
|
218
|
-
approval: settings.approval,
|
|
219
|
-
filesystem: settings.filesystem,
|
|
220
|
-
unattended: settings.unattended,
|
|
258
|
+
agentDefinition: settings.definition,
|
|
221
259
|
surface: 'agent',
|
|
222
260
|
creationActionId: actionId,
|
|
223
261
|
roomMemberStartup: startup,
|
|
@@ -227,7 +265,8 @@ async function launchMember(input) {
|
|
|
227
265
|
if (launched.creationActionId !== actionId) {
|
|
228
266
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
229
267
|
state: 'intent', attempt, action_id: launched.creationActionId,
|
|
230
|
-
mission_sha256: taskSha,
|
|
268
|
+
mission_sha256: taskSha, agent_definition: agentDefinition,
|
|
269
|
+
agent_fingerprint: agentFingerprint, updated_at: new Date().toISOString(),
|
|
231
270
|
...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
|
|
232
271
|
} });
|
|
233
272
|
}
|
|
@@ -237,17 +276,26 @@ async function launchMember(input) {
|
|
|
237
276
|
}
|
|
238
277
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
239
278
|
state: 'launched', attempt, action_id: launched.creationActionId, mission_sha256: taskSha,
|
|
279
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
240
280
|
...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
|
|
241
281
|
launch_id: supervisor.launchId, updated_at: new Date().toISOString(),
|
|
242
282
|
} });
|
|
283
|
+
recordFleetAuditPresentation({ kind: 'agent_started', id: member.name, name: member.name,
|
|
284
|
+
lifetime: 'temporary', brain: selectionSummary(settings.definition.brain),
|
|
285
|
+
role: selectionSummary(settings.definition.role), harness: 'resolved', session: 'acp',
|
|
286
|
+
permissions: permissionSummary(settings.definition), parent: provision.roomId,
|
|
287
|
+
actionId: launched.creationActionId, inherited: [] });
|
|
243
288
|
}
|
|
244
289
|
catch (error) {
|
|
245
290
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
246
291
|
state: 'failed', attempt, action_id: effectiveActionId, mission_sha256: taskSha,
|
|
292
|
+
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
247
293
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
248
294
|
updated_at: new Date().toISOString(),
|
|
249
295
|
error: error instanceof Error ? error.message : String(error),
|
|
250
296
|
} });
|
|
297
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Agent', id: member.name,
|
|
298
|
+
state: 'failed', category: 'readiness_failed', eventId: effectiveActionId });
|
|
251
299
|
throw error;
|
|
252
300
|
}
|
|
253
301
|
}
|
|
@@ -289,6 +337,8 @@ export async function provisionMembers(input) {
|
|
|
289
337
|
const { cfg, cowork, roomId, taskId, template } = input;
|
|
290
338
|
const prefix = taskId ? shortId(taskId) : `room-${shortId(roomId)}`;
|
|
291
339
|
const members = expandMembers(template, prefix);
|
|
340
|
+
// Resolve every Agent before persisting launch intent or touching Cowork membership.
|
|
341
|
+
const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg)]));
|
|
292
342
|
const existing = getRoomRecord(roomId);
|
|
293
343
|
if (!existing?.room_identity_cid)
|
|
294
344
|
throw new Error(`room ${roomId} has no pinned room identity CID`);
|
|
@@ -299,15 +349,27 @@ export async function provisionMembers(input) {
|
|
|
299
349
|
&& members.every(member => persistedNames.has(member.name));
|
|
300
350
|
if (!resuming) {
|
|
301
351
|
advanceSaga(roomId, 'create_members', 3);
|
|
302
|
-
updateMemberSeats(roomId, members.map(member =>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
352
|
+
updateMemberSeats(roomId, members.map(member => {
|
|
353
|
+
const evidence = launchDefinition(settings.get(member.name).definition);
|
|
354
|
+
return ({
|
|
355
|
+
role_name: member.name,
|
|
356
|
+
slot: member.slot,
|
|
357
|
+
cowork_role: member.coworkRole,
|
|
358
|
+
seat_state: 'pending',
|
|
359
|
+
launch: { state: 'pending', attempt: 0,
|
|
360
|
+
agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
|
|
361
|
+
updated_at: new Date().toISOString() },
|
|
362
|
+
});
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
for (const member of members) {
|
|
367
|
+
const seat = existing.member_seats.find(candidate => candidate.role_name === member.name);
|
|
368
|
+
const evidence = launchDefinition(settings.get(member.name).definition);
|
|
369
|
+
if (!seat.launch?.agent_fingerprint || seat.launch.agent_fingerprint !== evidence.fingerprint)
|
|
370
|
+
throw new Error(`Agent definition drift for ${member.name}; durable launch intent does not match current configuration`);
|
|
371
|
+
}
|
|
309
372
|
}
|
|
310
|
-
const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg)]));
|
|
311
373
|
const tasks = new Map(members.map(member => [member.name, roomTask(input, member, settings.get(member.name), members, roomIdentityCid, ownerSeatCid)]));
|
|
312
374
|
const policy = {
|
|
313
375
|
timeoutMs: input.startupWait?.timeoutMs ?? 60_000,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { TemplateDefinition, TemplateSnapshot } from './types.js';
|
|
2
|
-
export declare const BUILTIN_TEMPLATES: readonly TemplateDefinition[];
|
|
3
2
|
export declare function hashTemplate(t: TemplateDefinition): string;
|
|
4
3
|
export declare function snapshotTemplate(t: TemplateDefinition): TemplateSnapshot;
|
|
5
4
|
export declare function resolveTemplate(name: string, customTemplates: Record<string, TemplateDefinition>): TemplateDefinition | undefined;
|
|
@@ -1,54 +1,4 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
const TEAM = {
|
|
3
|
-
name: 'team',
|
|
4
|
-
version: 1,
|
|
5
|
-
builtin: true,
|
|
6
|
-
description: 'Phased task pipeline: Architect specifies, Developer implements, Tester verifies',
|
|
7
|
-
room: { quiet_membership: false, anonymous: false },
|
|
8
|
-
contract: [
|
|
9
|
-
'Architect produces a spec and posts it to the room. Work pauses for owner approval.',
|
|
10
|
-
'Developer implements per approved spec.',
|
|
11
|
-
'Tester verifies spec conformance and test coverage.',
|
|
12
|
-
'Completion requires tester sign-off.',
|
|
13
|
-
].join('\n'),
|
|
14
|
-
members: [
|
|
15
|
-
{ slot: 'architect', role: 'Architect', count: 1, role_ref: 'Architect' },
|
|
16
|
-
{ slot: 'developer', role: 'Developer', count: 1, role_ref: 'Developer' },
|
|
17
|
-
{ slot: 'tester', role: 'Tester', count: 1, role_ref: 'Tester' },
|
|
18
|
-
],
|
|
19
|
-
};
|
|
20
|
-
const PAIR = {
|
|
21
|
-
name: 'pair',
|
|
22
|
-
version: 1,
|
|
23
|
-
builtin: true,
|
|
24
|
-
description: 'Deliberation pair: Secretary writes code, Critic reviews — every decision deliberated together',
|
|
25
|
-
room: { quiet_membership: false, anonymous: false },
|
|
26
|
-
contract: [
|
|
27
|
-
'Secretary and Critic deliberate every decision together before acting.',
|
|
28
|
-
'Secretary writes code; Critic reviews and challenges.',
|
|
29
|
-
'No material change lands without both agreeing.',
|
|
30
|
-
'Completion requires joint sign-off.',
|
|
31
|
-
].join('\n'),
|
|
32
|
-
members: [
|
|
33
|
-
{ slot: 'secretary', role: 'Secretary', count: 1, role_ref: 'Secretary' },
|
|
34
|
-
{ slot: 'critic', role: 'Critic', count: 1, role_ref: 'Critic' },
|
|
35
|
-
],
|
|
36
|
-
};
|
|
37
|
-
const SINGLE = {
|
|
38
|
-
name: 'single',
|
|
39
|
-
version: 1,
|
|
40
|
-
builtin: true,
|
|
41
|
-
description: 'Solo agent: one general-purpose agent works the task with owner oversight',
|
|
42
|
-
room: { quiet_membership: false, anonymous: false },
|
|
43
|
-
contract: [
|
|
44
|
-
'Agent works the task autonomously, consulting the owner when blocked.',
|
|
45
|
-
'Owner reviews and approves completion.',
|
|
46
|
-
].join('\n'),
|
|
47
|
-
members: [
|
|
48
|
-
{ slot: 'agent', role: 'Agent', count: 1, role_ref: 'Agent' },
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
|
-
export const BUILTIN_TEMPLATES = [TEAM, PAIR, SINGLE];
|
|
52
2
|
export function hashTemplate(t) {
|
|
53
3
|
const canonical = JSON.stringify({
|
|
54
4
|
name: t.name, version: t.version, description: t.description,
|
|
@@ -57,38 +7,21 @@ export function hashTemplate(t) {
|
|
|
57
7
|
return createHash('sha256').update(canonical).digest('hex');
|
|
58
8
|
}
|
|
59
9
|
export function snapshotTemplate(t) {
|
|
60
|
-
|
|
10
|
+
const { sourceFile: _, ...semantic } = t;
|
|
11
|
+
return { ...semantic, content_hash: hashTemplate(t) };
|
|
61
12
|
}
|
|
62
13
|
export function resolveTemplate(name, customTemplates) {
|
|
63
14
|
const atIdx = name.lastIndexOf('@');
|
|
64
15
|
const baseName = atIdx > 0 ? name.slice(0, atIdx) : name;
|
|
65
16
|
const versionStr = atIdx > 0 ? name.slice(atIdx + 1) : undefined;
|
|
66
17
|
const version = versionStr ? parseInt(versionStr, 10) : undefined;
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return custom;
|
|
72
|
-
}
|
|
73
|
-
const builtin = BUILTIN_TEMPLATES.find(b => b.name === baseName);
|
|
74
|
-
if (builtin) {
|
|
75
|
-
if (version !== undefined && builtin.version !== version)
|
|
76
|
-
return undefined;
|
|
77
|
-
return builtin;
|
|
78
|
-
}
|
|
79
|
-
return undefined;
|
|
18
|
+
const template = customTemplates[baseName];
|
|
19
|
+
if (!template || (version !== undefined && template.version !== version))
|
|
20
|
+
return undefined;
|
|
21
|
+
return template;
|
|
80
22
|
}
|
|
81
23
|
export function listTemplates(customTemplates) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
result.push({ ...t, name });
|
|
86
|
-
if (BUILTIN_TEMPLATES.some(b => b.name === name))
|
|
87
|
-
overridden.add(name);
|
|
88
|
-
}
|
|
89
|
-
for (const b of BUILTIN_TEMPLATES) {
|
|
90
|
-
if (!overridden.has(b.name))
|
|
91
|
-
result.push(b);
|
|
92
|
-
}
|
|
93
|
-
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
24
|
+
return Object.entries(customTemplates)
|
|
25
|
+
.map(([name, template]) => ({ ...template, name }))
|
|
26
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
94
27
|
}
|
|
@@ -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;
|
|
@@ -211,7 +205,8 @@ export interface TemplateDefinition {
|
|
|
211
205
|
name: string;
|
|
212
206
|
version: number;
|
|
213
207
|
description: string;
|
|
214
|
-
|
|
208
|
+
/** Authoring provenance only; excluded from snapshots and semantic hashes. */
|
|
209
|
+
sourceFile?: string;
|
|
215
210
|
room?: TemplateRoomConfig;
|
|
216
211
|
contract?: string;
|
|
217
212
|
members: TemplateMemberSlot[];
|
|
@@ -252,5 +247,4 @@ export declare const ROOMS_COWORK_KEYS: readonly ["config"];
|
|
|
252
247
|
export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
|
|
253
248
|
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
|
|
254
249
|
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"];
|
|
250
|
+
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/runner.d.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { type ResolvedRole } from './config.js';
|
|
2
|
-
import type { Launch } from './harness/types.js';
|
|
3
|
-
import { Tmux } from './tmux.js';
|
|
4
2
|
import { type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
|
|
3
|
+
import { type DaemonGenerationProbe } from './daemon-recovery.js';
|
|
5
4
|
import { type Exec } from './exec.js';
|
|
6
|
-
import { type AcpSessionOptions } from './session/acp.js';
|
|
7
5
|
import { RoleControlServer } from './session/control.js';
|
|
8
|
-
import type {
|
|
6
|
+
import type { AgentSession, ExitRecord, TurnResult } from './session/types.js';
|
|
7
|
+
import type { AgentSessionAdapter, AgentSessionStartOptions } from './harness/agent-session.js';
|
|
9
8
|
import { type OwnerChannelHandle, type OwnerChannelOptions } from './owner-channel/channel.js';
|
|
10
9
|
import { type OwnerBinderLease } from './owner-channel/binder.js';
|
|
11
10
|
export interface RunnerDeps {
|
|
12
|
-
tmux: Tmux;
|
|
13
11
|
exec: Exec;
|
|
14
12
|
cpuDelegated(): boolean;
|
|
15
13
|
isAlive(pid: number): boolean;
|
|
@@ -18,14 +16,16 @@ export interface RunnerDeps {
|
|
|
18
16
|
log(line: string): void;
|
|
19
17
|
/** HTTP transport for the monitor's daemon long-poll (injectable for tests). */
|
|
20
18
|
fetch: FetchLike;
|
|
19
|
+
probeGeneration(env: NodeJS.ProcessEnv): Promise<DaemonGenerationProbe>;
|
|
21
20
|
/** Construct the supervisor mail monitor (injectable so tests stub it out). */
|
|
22
21
|
createMonitor(opts: MonitorOpts): MonitorHandle;
|
|
23
22
|
/** Construct trusted owner ingress (injectable for lifecycle tests). */
|
|
24
23
|
createOwnerChannel(opts: OwnerChannelOptions): OwnerChannelHandle;
|
|
25
24
|
/** Start the ACP transport (injectable for deterministic runner lifecycle tests). */
|
|
26
|
-
|
|
25
|
+
/** Neutral construction seam for deterministic runner lifecycle tests. */
|
|
26
|
+
startAgentSession(adapter: AgentSessionAdapter, options: AgentSessionStartOptions): Promise<AgentSession>;
|
|
27
27
|
/** Construct the authenticated role control route (injectable where sockets are unavailable). */
|
|
28
|
-
createControlServer(stateDir: string, session:
|
|
28
|
+
createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'>;
|
|
29
29
|
/** Acquire the cross-process owner-channel binder lease before replacing the control socket. */
|
|
30
30
|
acquireOwnerBinder(stateDir: string, role: string, identity: string): Promise<OwnerBinderLease>;
|
|
31
31
|
/** Ask the still-authenticated predecessor to emit the fixed recovery notice. */
|
|
@@ -33,6 +33,11 @@ export interface RunnerDeps {
|
|
|
33
33
|
/** Lets a test (or a shutdown path) end the supervised restart loop. */
|
|
34
34
|
shouldStop?(): boolean;
|
|
35
35
|
}
|
|
36
|
+
export declare const SUPERVISOR_RECYCLE_REQUIRED = "OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED";
|
|
37
|
+
export declare class SupervisorRecycleRequiredError extends Error {
|
|
38
|
+
readonly code = "OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED";
|
|
39
|
+
constructor();
|
|
40
|
+
}
|
|
36
41
|
/** Environment injected only into the managed harness process. */
|
|
37
42
|
export declare function managedFleetProxyEnv(role: ResolvedRole, stateDir: string): Record<string, string>;
|
|
38
43
|
/**
|
|
@@ -51,14 +56,6 @@ export declare function harnessChildEnv(role: ResolvedRole, launchEnv: Record<st
|
|
|
51
56
|
* responsible for.
|
|
52
57
|
*/
|
|
53
58
|
export declare function recordMonitorOwner(dir: string, owner: 'fleet' | 'native'): boolean;
|
|
54
|
-
/**
|
|
55
|
-
* Compose the tmux pane shell command: env prefix + argv + exit-status capture.
|
|
56
|
-
* `paneArgv` defaults to `launch.argv`; when isolation is active the caller passes
|
|
57
|
-
* the sandbox-wrapped argv (e.g. `bwrap … -- claude …`). The `env` prefix and the
|
|
58
|
-
* `echo $? > exitfile` capture stay host-side, outside the sandbox, so the runner
|
|
59
|
-
* still sees the real exit code.
|
|
60
|
-
*/
|
|
61
|
-
export declare function buildPaneCommand(launch: Launch, roleEnv: Record<string, string> | undefined, exitStatusPath: string, paneArgv?: string[]): string;
|
|
62
59
|
/**
|
|
63
60
|
* Read the pane's `.exit-status`. Three shapes are accepted: the structured
|
|
64
61
|
* record written above, a bare number left by a pre-upgrade pane (so an
|
|
@@ -186,4 +183,4 @@ export declare function runSupervised(name: string, opts?: {
|
|
|
186
183
|
allowResumeRotation?: boolean;
|
|
187
184
|
}, d: Partial<RunnerDeps>) => Promise<AttemptResult>): Promise<RestartLedger>;
|
|
188
185
|
/** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
|
|
189
|
-
export declare function runTemp(name: string, deps?: Partial<RunnerDeps
|
|
186
|
+
export declare function runTemp(name: string, deps?: Partial<RunnerDeps>, attempt?: typeof runOnce): Promise<void>;
|