@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,14 +1,15 @@
|
|
|
1
1
|
import { type Socket } from 'node:net';
|
|
2
|
-
import type { ControlFailureKind, SessionEvent,
|
|
2
|
+
import type { AgentSession, ControlFailureKind, SessionEvent, SessionSnapshot } from './types.js';
|
|
3
3
|
import type { OwnerChannelHandle, OwnerChannelManagementRequest } from '../owner-channel/channel.js';
|
|
4
4
|
import type { ScheduledLoopManagerHandle } from '../loops/manager.js';
|
|
5
5
|
import type { SpawnOpts } from '../spawn.js';
|
|
6
6
|
import type { ManagedFleetSpawnResult } from '../fleet-proxy.js';
|
|
7
|
+
import { type FleetAuditPresentation, type FleetCommandOutcomeClass } from '../fleet-command-audit.js';
|
|
7
8
|
export interface ControlRequest {
|
|
8
9
|
version: 1 | 2 | 3;
|
|
9
10
|
id: string;
|
|
10
11
|
token: string;
|
|
11
|
-
command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn';
|
|
12
|
+
command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_finish';
|
|
12
13
|
text?: string;
|
|
13
14
|
permissionId?: string;
|
|
14
15
|
optionId?: string;
|
|
@@ -30,6 +31,16 @@ export interface ControlRequest {
|
|
|
30
31
|
sessionGeneration?: string;
|
|
31
32
|
/** Typed managed-spawn request accepted only over the authenticated role control plane. */
|
|
32
33
|
spawn?: SpawnOpts;
|
|
34
|
+
audit?: {
|
|
35
|
+
requestId?: string;
|
|
36
|
+
argv?: string[];
|
|
37
|
+
correlationId?: string;
|
|
38
|
+
class?: FleetCommandOutcomeClass;
|
|
39
|
+
exitCode?: number;
|
|
40
|
+
effect?: 'not_started' | 'completed' | 'unknown';
|
|
41
|
+
resourceIds?: Record<string, string>;
|
|
42
|
+
presentations?: FleetAuditPresentation[];
|
|
43
|
+
};
|
|
33
44
|
}
|
|
34
45
|
export interface ControlResponse {
|
|
35
46
|
version: 1;
|
|
@@ -48,7 +59,7 @@ export interface RetainedEventPage {
|
|
|
48
59
|
truncated: boolean;
|
|
49
60
|
}
|
|
50
61
|
/** The one retained-range projection shared by polling and live-follow admission. */
|
|
51
|
-
export declare function retainedEventPage(session:
|
|
62
|
+
export declare function retainedEventPage(session: AgentSession, since: number): RetainedEventPage;
|
|
52
63
|
/**
|
|
53
64
|
* One line saying what a control failure does — and does not — prove about the
|
|
54
65
|
* agent. Only `offline` is evidence that it is gone; every other kind used to
|
|
@@ -105,7 +116,8 @@ export declare class RoleControlServer {
|
|
|
105
116
|
private loopManager?;
|
|
106
117
|
private reloadConfig?;
|
|
107
118
|
private fleetSpawner?;
|
|
108
|
-
|
|
119
|
+
private fleetAuditor?;
|
|
120
|
+
constructor(stateDir: string, session: AgentSession, log: (line: string) => void);
|
|
109
121
|
start(): Promise<void>;
|
|
110
122
|
close(): Promise<void>;
|
|
111
123
|
/** Attach only the already-started supervisor-owned channel client. */
|
|
@@ -113,6 +125,7 @@ export declare class RoleControlServer {
|
|
|
113
125
|
setLoopManager(loopManager: ScheduledLoopManagerHandle | undefined): void;
|
|
114
126
|
setConfigReloader(reloadConfig: (() => Promise<unknown>) | undefined): void;
|
|
115
127
|
setFleetSpawner(fleetSpawner: ((options: SpawnOpts) => Promise<ManagedFleetSpawnResult>) | undefined): void;
|
|
128
|
+
setFleetAuditor(auditor: RoleControlServer['fleetAuditor']): void;
|
|
116
129
|
private accept;
|
|
117
130
|
private handle;
|
|
118
131
|
/** Version and capability gate shared by every conversation v3 command. */
|
package/dist/session/control.js
CHANGED
|
@@ -3,6 +3,7 @@ import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node
|
|
|
3
3
|
import { createConnection, createServer } from 'node:net';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { SessionControlError } from './types.js';
|
|
6
|
+
import { validateFleetAuditBegin, validateFleetAuditFinish, } from '../fleet-command-audit.js';
|
|
6
7
|
import { interruptSession, queueSessionPrompt, respondSessionPermission, respondSessionPermissionV2, } from '../application/session-mutations.js';
|
|
7
8
|
const MAX_LINE_BYTES = 64 * 1024;
|
|
8
9
|
/** Commands that require protocol version 3. */
|
|
@@ -119,6 +120,7 @@ export class RoleControlServer {
|
|
|
119
120
|
loopManager;
|
|
120
121
|
reloadConfig;
|
|
121
122
|
fleetSpawner;
|
|
123
|
+
fleetAuditor;
|
|
122
124
|
constructor(stateDir, session, log) {
|
|
123
125
|
this.session = session;
|
|
124
126
|
this.log = log;
|
|
@@ -164,6 +166,7 @@ export class RoleControlServer {
|
|
|
164
166
|
setFleetSpawner(fleetSpawner) {
|
|
165
167
|
this.fleetSpawner = fleetSpawner;
|
|
166
168
|
}
|
|
169
|
+
setFleetAuditor(auditor) { this.fleetAuditor = auditor; }
|
|
167
170
|
accept(socket) {
|
|
168
171
|
this.sockets.add(socket);
|
|
169
172
|
socket.setEncoding('utf8');
|
|
@@ -311,6 +314,38 @@ export class RoleControlServer {
|
|
|
311
314
|
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
312
315
|
return;
|
|
313
316
|
}
|
|
317
|
+
case 'fleet_audit_begin': {
|
|
318
|
+
if (!this.fleetAuditor)
|
|
319
|
+
throw new SessionControlError('rejected', 'fleet auditing is unavailable');
|
|
320
|
+
try {
|
|
321
|
+
validateFleetAuditBegin(request.audit);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
throw new SessionControlError('rejected', 'valid fleet audit begin fields are required');
|
|
325
|
+
}
|
|
326
|
+
const result = await this.fleetAuditor.begin(request.audit.requestId, request.audit.argv);
|
|
327
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
case 'fleet_audit_finish': {
|
|
331
|
+
if (!this.fleetAuditor)
|
|
332
|
+
throw new SessionControlError('rejected', 'fleet auditing is unavailable');
|
|
333
|
+
try {
|
|
334
|
+
validateFleetAuditFinish(request.audit);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
throw new SessionControlError('rejected', 'valid fleet audit finish fields are required');
|
|
338
|
+
}
|
|
339
|
+
const audit = request.audit;
|
|
340
|
+
const result = await this.fleetAuditor.finish({
|
|
341
|
+
correlationId: audit.correlationId, class: audit.class, effect: audit.effect,
|
|
342
|
+
...(audit.exitCode === undefined ? {} : { exitCode: audit.exitCode }),
|
|
343
|
+
...(audit.resourceIds ? { resourceIds: audit.resourceIds } : {}),
|
|
344
|
+
...(audit.presentations ? { presentations: audit.presentations } : {}),
|
|
345
|
+
});
|
|
346
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
314
349
|
case 'owner_channel_manage': {
|
|
315
350
|
if (!request.ownerChannel || typeof request.ownerChannel.action !== 'string')
|
|
316
351
|
throw new SessionControlError('rejected', 'owner-channel management action is required');
|
package/dist/session/types.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export interface InterruptOutcome {
|
|
|
96
96
|
reasonCode?: string;
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
|
-
* What
|
|
99
|
+
* What an `AgentSession.interrupt` implementation may resolve. Before 0.17.1 the
|
|
100
100
|
* contract was `Promise<void>`, and resolving at all meant the cancellation had
|
|
101
101
|
* taken effect cooperatively — so an implementation written against that
|
|
102
102
|
* contract stays valid and keeps its exact meaning. Only in-tree consumers read
|
|
@@ -146,7 +146,7 @@ export interface ExitRecord {
|
|
|
146
146
|
code?: number;
|
|
147
147
|
/** Signal that killed it, when one did. */
|
|
148
148
|
signal?: string;
|
|
149
|
-
/** Raw wait status
|
|
149
|
+
/** Raw wait status retained when migrating a legacy shell-exit record. */
|
|
150
150
|
status?: number;
|
|
151
151
|
at?: string;
|
|
152
152
|
/** One line an operator can read. */
|
|
@@ -194,8 +194,7 @@ export interface SessionSnapshot {
|
|
|
194
194
|
};
|
|
195
195
|
/**
|
|
196
196
|
* Observed agent activity, independent of turn occupancy: the evidence a
|
|
197
|
-
* human-facing surface needs before calling a role idle.
|
|
198
|
-
* that cannot observe the agent at all (tmux), which is itself honest — no
|
|
197
|
+
* human-facing surface needs before calling a role idle. When absent, no
|
|
199
198
|
* evidence is not evidence of inactivity.
|
|
200
199
|
*/
|
|
201
200
|
activity?: SessionActivity;
|
|
@@ -259,7 +258,12 @@ export interface ConversationHandlePage {
|
|
|
259
258
|
hasMore: boolean;
|
|
260
259
|
snapshot: ConversationSnapshot;
|
|
261
260
|
}
|
|
262
|
-
|
|
261
|
+
/**
|
|
262
|
+
* The live, harness-neutral contract between Fleet and one agent session.
|
|
263
|
+
* Harness adapters construct this handle; orchestration code must not depend
|
|
264
|
+
* on the ACP transport which currently implements it.
|
|
265
|
+
*/
|
|
266
|
+
export interface AgentSession {
|
|
263
267
|
readonly backend: SessionBackendId;
|
|
264
268
|
readonly pid: number;
|
|
265
269
|
isAlive(): boolean;
|
|
@@ -301,3 +305,5 @@ export interface SessionHandle {
|
|
|
301
305
|
exitResult(): ExitRecord | null;
|
|
302
306
|
close(): Promise<void>;
|
|
303
307
|
}
|
|
308
|
+
/** @deprecated Public compatibility name; use AgentSession. */
|
|
309
|
+
export type SessionHandle = AgentSession;
|
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,10 +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', 'acp'].includes(o.session))
|
|
127
|
-
throw new Error(`invalid --session '${o.session}'; allowed: tmux, acp`);
|
|
128
82
|
if (o.approval && !['ask', 'auto', 'allow', 'deny'].includes(o.approval))
|
|
129
83
|
throw new Error(`invalid --approval '${o.approval}'; allowed: ask, auto, allow (deprecated alias: deny)`);
|
|
130
84
|
if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
|
|
@@ -135,23 +89,12 @@ export function validateSpawnOpts(o) {
|
|
|
135
89
|
throw new Error(`invalid role name '${o.name}'`);
|
|
136
90
|
if (o.identity && !/^[A-Za-z0-9_-]+$/.test(o.identity))
|
|
137
91
|
throw new Error(`invalid identity name '${o.identity}'`);
|
|
138
|
-
if (o.model && (o.model.length > 128 || /[\0-\x1f\x7f]/.test(o.model)))
|
|
139
|
-
throw new Error('model must be printable and at most 128 characters');
|
|
140
92
|
if (o.monitorConfig) {
|
|
141
93
|
const problems = validateMonitorConfig(o.monitorConfig);
|
|
142
94
|
if (problems.length)
|
|
143
95
|
throw new Error(problems.join('; '));
|
|
144
96
|
}
|
|
145
97
|
}
|
|
146
|
-
/** Read mission text without trimming or newline rewriting. */
|
|
147
|
-
export function readMissionFile(path) {
|
|
148
|
-
try {
|
|
149
|
-
return readFileSync(path, 'utf8');
|
|
150
|
-
}
|
|
151
|
-
catch (e) {
|
|
152
|
-
throw new Error(`--mission-file ${path}: ${e.message}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
98
|
/**
|
|
156
99
|
* Reject names that are already USED. This is a precondition, not a claim: it
|
|
157
100
|
* runs INSIDE the creation transaction, after both names are reserved, so the
|
|
@@ -180,62 +123,13 @@ export function spawnDryRun(o) {
|
|
|
180
123
|
validateSpawnOpts(o);
|
|
181
124
|
if (o.isolationFile)
|
|
182
125
|
readIsolationFile(o.isolationFile);
|
|
183
|
-
if (o.missionFile)
|
|
184
|
-
readMissionFile(o.missionFile);
|
|
185
126
|
assertNameFree(o);
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
const harnessOptions = {
|
|
189
|
-
...(cfg.defaults.harness_options ?? {}),
|
|
190
|
-
...(raw.harness_options ?? {}),
|
|
191
|
-
};
|
|
192
|
-
const harness = raw.harness ?? cfg.defaults.harness ?? 'claude-code';
|
|
193
|
-
const defaultHarness = cfg.defaults.harness ?? 'claude-code';
|
|
194
|
-
const inheritsModelDefaults = harness === defaultHarness && raw.model !== null;
|
|
195
|
-
const authProxy = resolveAuthProxy(cfg.defaults.auth_proxy, raw.auth_proxy);
|
|
196
|
-
// One resolution for the environment and the model it pins (src/model-env.ts).
|
|
197
|
-
const modelEnv = resolveRoleModelEnv({
|
|
198
|
-
harness,
|
|
199
|
-
model: resolveRoleModel(raw.model, raw.harness, cfg.defaults),
|
|
200
|
-
modelWasExplicit: raw.model !== undefined,
|
|
201
|
-
defaultsEnv: (cfg.defaults.env ?? {}),
|
|
202
|
-
roleEnv: raw.env,
|
|
203
|
-
...(authProxy ? { authProxyBaseUrl: authProxy.base_url } : {}),
|
|
204
|
-
});
|
|
205
|
-
const model = modelEnv.model;
|
|
206
|
-
const session = raw.session ?? cfg.defaults.session ?? 'tmux';
|
|
207
|
-
const resolvedRole = {
|
|
208
|
-
...raw,
|
|
209
|
-
name: o.name,
|
|
210
|
-
sourceFile: o.temp ? '(temp dry-run)' : join(fleetDDir(), `${o.name}.yaml`),
|
|
211
|
-
harness,
|
|
212
|
-
session,
|
|
213
|
-
session_options: raw.session_options,
|
|
214
|
-
permissions: resolvePermissions(cfg.defaults.permissions, raw.permissions),
|
|
215
|
-
permissionsDeclared: raw.permissions !== undefined || cfg.defaults.permissions !== undefined,
|
|
216
|
-
identity: effectiveIdentity(o),
|
|
217
|
-
model,
|
|
218
|
-
model_chain: resolveModelChain(model, raw.model_chain ?? (inheritsModelDefaults
|
|
219
|
-
? cfg.defaults.model_chain
|
|
220
|
-
: undefined)),
|
|
221
|
-
harness_options: Object.keys(harnessOptions).length ? harnessOptions : undefined,
|
|
222
|
-
isolation: raw.isolation ?? cfg.defaults.isolation,
|
|
223
|
-
monitor: resolveMonitorConfig(cfg.defaults.monitor, raw.monitor),
|
|
224
|
-
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, raw.owner_channel, session),
|
|
225
|
-
worklog: resolveWorklogPolicy(cfg.defaults.worklog, raw.worklog),
|
|
226
|
-
auth_proxy: authProxy,
|
|
227
|
-
};
|
|
228
|
-
resolvedRole.env = modelEnv.env;
|
|
229
|
-
const adapter = getAdapter(resolvedRole.harness);
|
|
230
|
-
if (resolvedRole.auth_proxy && resolvedRole.harness !== 'claude-code')
|
|
231
|
-
throw new Error('auth_proxy is supported only by claude-code');
|
|
232
|
-
const optionProblems = adapter.validateOptions(resolvedRole.harness_options, resolvedRole);
|
|
233
|
-
if (optionProblems.length)
|
|
234
|
-
throw new Error(optionProblems.map(problem => `${problem.path}: ${problem.message}`).join('; '));
|
|
127
|
+
const resolved = resolvedSpawn(o);
|
|
128
|
+
const resolvedRole = resolved.role;
|
|
235
129
|
return {
|
|
236
130
|
schemaVersion: 1,
|
|
237
131
|
warning: 'collision checks are a snapshot; a real spawn reserves names atomically',
|
|
238
|
-
roleDocument:
|
|
132
|
+
roleDocument: structuredClone(resolved.definition),
|
|
239
133
|
resolvedRole,
|
|
240
134
|
};
|
|
241
135
|
}
|
|
@@ -251,22 +145,15 @@ function provenanceSettings(o, defaults) {
|
|
|
251
145
|
const perms = (defaults.permissions ?? {});
|
|
252
146
|
const callerDefaults = new Set(o.inheritedFromCaller ?? []);
|
|
253
147
|
const tagged = (key, entry) => callerDefaults.has(key) ? { ...entry, source: 'caller-role' } : entry;
|
|
254
|
-
const
|
|
255
|
-
const inheritedModel = resolveRoleModel(undefined, o.harness, defaults);
|
|
148
|
+
const selection = (value) => value && 'ref' in value ? `ref:${value.ref}` : value ? 'inline' : undefined;
|
|
256
149
|
return {
|
|
257
|
-
|
|
258
|
-
|
|
150
|
+
brain: tagged('brain', provenanceOf(selection(o.brain), undefined, undefined)),
|
|
151
|
+
role: tagged('role', provenanceOf(selection(o.role), undefined, undefined)),
|
|
259
152
|
identity: o.identity
|
|
260
153
|
? { value: o.identity, source: 'cli' }
|
|
261
154
|
: { value: o.name, source: 'built-in' }, // defaults to the role name
|
|
262
155
|
cwd: tagged('cwd', provenanceOf(o.cwd, undefined, undefined)),
|
|
263
|
-
model: tagged('model', o.model === null
|
|
264
|
-
? { value: undefined, source: 'cli' }
|
|
265
|
-
: explicitModel
|
|
266
|
-
? { value: explicitModel, source: 'cli' }
|
|
267
|
-
: { value: inheritedModel, source: inheritedModel ? 'fleet-default' : 'built-in' }),
|
|
268
156
|
coordinator: tagged('coordinator', provenanceOf(o.coordinator, undefined, undefined)),
|
|
269
|
-
permission_mode: provenanceOf(o.permissionMode, undefined, undefined),
|
|
270
157
|
approval: tagged('approval', provenanceOf(o.approval, perms.approval, 'ask')),
|
|
271
158
|
filesystem: tagged('filesystem', provenanceOf(o.filesystem, perms.filesystem, 'workspace')),
|
|
272
159
|
unattended: tagged('unattended', provenanceOf(o.unattended, perms.unattended, 'deny')),
|
|
@@ -276,13 +163,12 @@ function provenanceSettings(o, defaults) {
|
|
|
276
163
|
monitor: tagged('monitorConfig', provenanceOf(o.monitorConfig, defaults.monitor, { mode: 'fleet' })),
|
|
277
164
|
};
|
|
278
165
|
}
|
|
279
|
-
/** Permanent spawn: persist
|
|
166
|
+
/** Permanent spawn: persist one bare Agent document under the selected v2 root. */
|
|
280
167
|
export async function spawnPermanent(o, deps, creation = {}) {
|
|
281
168
|
validateSpawnOpts(o);
|
|
282
169
|
if (o.isolationFile)
|
|
283
170
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
284
|
-
|
|
285
|
-
readMissionFile(o.missionFile); // fail before reserving
|
|
171
|
+
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
286
172
|
// Reserve name and identity together before anything is written or started.
|
|
287
173
|
// A loser of the race creates no config, state, or service.
|
|
288
174
|
creation.onStage?.('reserving');
|
|
@@ -292,7 +178,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
292
178
|
// Establish the identity BEFORE the service is enabled, and record
|
|
293
179
|
// what was actually guaranteed so the briefing can say something true.
|
|
294
180
|
creation.onStage?.('checking_identity');
|
|
295
|
-
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);
|
|
296
182
|
creation.onStage?.('checking_identity', {
|
|
297
183
|
result: guarantee.evidence, guarantee: guarantee.state,
|
|
298
184
|
});
|
|
@@ -305,12 +191,11 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
305
191
|
await creation.identityProvisioner?.remove?.(effectiveIdentity(o));
|
|
306
192
|
},
|
|
307
193
|
});
|
|
308
|
-
|
|
194
|
+
const agentRoot = join(splitRootFor(o.configPath ?? defaultConfigPath()), 'agents');
|
|
195
|
+
mkdirSync(agentRoot, { recursive: true });
|
|
309
196
|
creation.onStage?.('writing_role');
|
|
310
|
-
const file = join(
|
|
311
|
-
writeRoleFile(tx, file, stringify(
|
|
312
|
-
roles: { [o.name]: buildRoleConfig(o, cfg.defaults.harness) },
|
|
313
|
-
}));
|
|
197
|
+
const file = join(agentRoot, `${o.name}.yaml`);
|
|
198
|
+
writeRoleFile(tx, file, stringify(agentDefinitionFromSpawn(o)));
|
|
314
199
|
// `up` materialises the state dir and registers the service. Journal the
|
|
315
200
|
// dir before it exists so a failure leaves the name genuinely reusable
|
|
316
201
|
// rather than blocked by a half-built directory.
|
|
@@ -341,6 +226,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
341
226
|
});
|
|
342
227
|
mkdirSync(agentDir(o.name), { recursive: true });
|
|
343
228
|
writeProvenance(agentDir(o.name), provenance);
|
|
229
|
+
recordGeneratedAgentSource(agentDir(o.name), o.configPath ?? defaultConfigPath(), file);
|
|
344
230
|
creation.onStage?.('registering_supervisor');
|
|
345
231
|
await up(loadConfig(o.configPath), [o.name], {
|
|
346
232
|
...deps,
|
|
@@ -378,8 +264,7 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
378
264
|
validateSpawnOpts(o);
|
|
379
265
|
if (o.isolationFile)
|
|
380
266
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
381
|
-
|
|
382
|
-
readMissionFile(o.missionFile); // fail before reserving
|
|
267
|
+
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
383
268
|
// Retire only supervisors whose recorded owner is definitively stopped. This
|
|
384
269
|
// bounded pass keeps the active roster clean without deleting old evidence.
|
|
385
270
|
await reclaimStaleTempState();
|
|
@@ -392,56 +277,14 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
392
277
|
creation.onStage?.('checking_identity', {
|
|
393
278
|
result: 'unknown', guarantee: 'unverified',
|
|
394
279
|
});
|
|
395
|
-
return spawnTempInner(o, binPath, launch, tx, creation.onStage);
|
|
280
|
+
return spawnTempInner(o, prepared.role, binPath, launch, tx, creation.onStage);
|
|
396
281
|
}, creation);
|
|
397
282
|
}
|
|
398
|
-
async function spawnTempInner(o, binPath, launch, tx, onStage) {
|
|
283
|
+
async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
399
284
|
const cfg = loadConfig(o.configPath);
|
|
400
|
-
const defaultHarness = cfg.defaults.harness;
|
|
401
|
-
const fromOpts = buildRoleConfig(o, defaultHarness);
|
|
402
|
-
const mergedHarnessOptions = {
|
|
403
|
-
...(cfg.defaults.harness_options ?? {}),
|
|
404
|
-
...(fromOpts.harness_options ?? {}),
|
|
405
|
-
};
|
|
406
|
-
const harness = o.harness ?? defaultHarness ?? 'claude-code';
|
|
407
|
-
const inheritsModelDefaults = harness === (defaultHarness ?? 'claude-code') && o.model !== null;
|
|
408
|
-
const tempAuthProxy = resolveAuthProxy(cfg.defaults.auth_proxy, fromOpts.auth_proxy);
|
|
409
|
-
// An explicitly requested --model must reach the child, not just the banner
|
|
410
|
-
// (src/model-env.ts).
|
|
411
|
-
const modelEnv = resolveRoleModelEnv({
|
|
412
|
-
harness,
|
|
413
|
-
model: resolveRoleModel(o.model, o.harness, cfg.defaults),
|
|
414
|
-
modelWasExplicit: o.model !== undefined,
|
|
415
|
-
defaultsEnv: (cfg.defaults.env ?? {}),
|
|
416
|
-
roleEnv: fromOpts.env,
|
|
417
|
-
...(tempAuthProxy ? { authProxyBaseUrl: tempAuthProxy.base_url } : {}),
|
|
418
|
-
});
|
|
419
|
-
const model = modelEnv.model;
|
|
420
|
-
const session = o.session ?? cfg.defaults.session ?? 'tmux';
|
|
421
285
|
const role = {
|
|
422
|
-
...
|
|
423
|
-
name: o.name,
|
|
424
|
-
harness,
|
|
425
|
-
session,
|
|
426
|
-
identity: o.identity ?? o.name,
|
|
427
|
-
model,
|
|
428
|
-
model_chain: resolveModelChain(model, fromOpts.model_chain ?? (inheritsModelDefaults
|
|
429
|
-
? cfg.defaults.model_chain
|
|
430
|
-
: undefined)),
|
|
431
|
-
harness_options: Object.keys(mergedHarnessOptions).length ? mergedHarnessOptions : undefined,
|
|
432
|
-
permissions: resolvePermissions(cfg.defaults.permissions, fromOpts.permissions),
|
|
433
|
-
permissionsDeclared: fromOpts.permissions !== undefined || cfg.defaults.permissions !== undefined,
|
|
434
|
-
// Temp agents inherit the fleet-wide monitor defaults via the snapshot.
|
|
435
|
-
monitor: resolveMonitorConfig(cfg.defaults.monitor, fromOpts.monitor),
|
|
436
|
-
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, fromOpts.owner_channel, session),
|
|
437
|
-
worklog: resolveWorklogPolicy(cfg.defaults.worklog, fromOpts.worklog),
|
|
438
|
-
auth_proxy: tempAuthProxy,
|
|
439
|
-
sourceFile: '(temp)',
|
|
440
|
-
roomMemberStartup: o.roomMemberStartup,
|
|
286
|
+
...preparedRole, sourceFile: '(temp)', loops: undefined, roomMemberStartup: o.roomMemberStartup,
|
|
441
287
|
};
|
|
442
|
-
role.env = modelEnv.env;
|
|
443
|
-
if (role.auth_proxy && role.harness !== 'claude-code')
|
|
444
|
-
throw new Error('auth_proxy is supported only by claude-code');
|
|
445
288
|
onStage?.('writing_role');
|
|
446
289
|
const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
|
|
447
290
|
const provenance = buildProvenance({
|
|
@@ -467,10 +310,7 @@ async function spawnTempInner(o, binPath, launch, tx, onStage) {
|
|
|
467
310
|
if (cfg.startStaggerMs > 0)
|
|
468
311
|
writeFileSync(join(dir, START_STAGGER_FILE), String(cfg.startStaggerMs));
|
|
469
312
|
prepareTempSupervisor(dir, o.name);
|
|
470
|
-
// Run the supervisor independently
|
|
471
|
-
// `_run-temp` -> runOnce() creates AND kills the tmux session <name> for the
|
|
472
|
-
// agent itself; a supervisor sharing that session name would SIGHUP its own
|
|
473
|
-
// process before the agent ever launches. On a service-managed host the temp
|
|
313
|
+
// Run the supervisor independently. On a service-managed host the temp
|
|
474
314
|
// runner gets its own transient unit/job, so stopping the coordinator's unit
|
|
475
315
|
// cannot kill a live worker in the coordinator's cgroup.
|
|
476
316
|
onStage?.('starting_temp');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Exec } from '../exec.js';
|
|
2
2
|
import type { SupervisorBackend } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
* No supervision:
|
|
4
|
+
* No service supervision: tmux contains the direct runner process, nothing survives a reboot and
|
|
5
5
|
* nothing restarts on crash. Used for temp agents and CI tests
|
|
6
6
|
* (OURS_FLEET_SUPERVISOR=none).
|
|
7
7
|
*/
|