@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,30 +1,20 @@
|
|
|
1
1
|
export function roleCapabilities(role, status, context = {}) {
|
|
2
|
-
const backend = role.configuredBackend ?? (role.detectedBackend === 'acp'
|
|
2
|
+
const backend = role.configuredBackend ?? (role.detectedBackend === 'acp' ? 'acp' : undefined);
|
|
3
3
|
const online = status.session.reachability === 'online';
|
|
4
4
|
const acp = backend === 'acp';
|
|
5
|
-
const tmux = backend === 'tmux';
|
|
6
5
|
const protocolVersion = context.controlProtocolVersion ?? 1;
|
|
7
6
|
const inactive = status.overall === 'offline'
|
|
8
7
|
|| (!online && status.supervisor.liveness === 'stopped');
|
|
9
|
-
const terminalAvailable = tmux && online && context.terminalPtyAvailable === true;
|
|
10
|
-
const terminalReason = terminalAvailable ? undefined
|
|
11
|
-
: !tmux ? 'wrong_backend'
|
|
12
|
-
: !online ? 'offline'
|
|
13
|
-
: 'pty_unavailable';
|
|
14
8
|
return {
|
|
15
9
|
protocolVersion,
|
|
16
10
|
inventory: true,
|
|
17
11
|
status: true,
|
|
18
12
|
output: {
|
|
19
|
-
recent: acp
|
|
13
|
+
recent: acp, stream: acp && protocolVersion >= 2,
|
|
20
14
|
structured: acp, replayCursor: acp && protocolVersion >= 2,
|
|
21
15
|
},
|
|
22
|
-
input: { text: online &&
|
|
16
|
+
input: { text: online && acp, interrupt: online && acp, steering: false },
|
|
23
17
|
permissions: { observe: acp, respond: acp && online },
|
|
24
|
-
terminal: {
|
|
25
|
-
available: terminalAvailable, reason: terminalReason,
|
|
26
|
-
multiViewer: terminalAvailable, writerLease: terminalAvailable,
|
|
27
|
-
},
|
|
28
18
|
lifecycle: {
|
|
29
19
|
start: role.lifetime === 'permanent' && status.supervisor.liveness !== 'running',
|
|
30
20
|
stop: role.lifetime === 'permanent' && !inactive && status.supervisor.liveness !== 'stopped',
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { SupervisorBackend } from '../supervisor/types.js';
|
|
2
2
|
import { controlRequest } from '../session/control.js';
|
|
3
|
-
import { Tmux } from '../tmux.js';
|
|
4
3
|
import { type CapabilityContext } from './capabilities.js';
|
|
5
4
|
import { RoleRepository } from './role-repository.js';
|
|
6
5
|
import type { RoleCapabilities, RoleRecord, RoleStatus } from './types.js';
|
|
7
6
|
export interface FleetQueryOptions {
|
|
8
7
|
repository: RoleRepository;
|
|
9
8
|
supervisor: SupervisorBackend;
|
|
10
|
-
tmux?: Tmux;
|
|
11
9
|
control?: typeof controlRequest;
|
|
12
10
|
capabilityContext?: CapabilityContext;
|
|
13
11
|
/**
|
|
@@ -24,7 +22,6 @@ export interface FleetQueryOptions {
|
|
|
24
22
|
}
|
|
25
23
|
export declare class FleetQueryService {
|
|
26
24
|
private readonly options;
|
|
27
|
-
private readonly tmux;
|
|
28
25
|
private readonly control;
|
|
29
26
|
constructor(options: FleetQueryOptions);
|
|
30
27
|
list(): Promise<Array<{
|
|
@@ -4,7 +4,7 @@ import { classifyActivity } from '../session/activity.js';
|
|
|
4
4
|
import { controlRequest } from '../session/control.js';
|
|
5
5
|
import { SessionControlError } from '../session/types.js';
|
|
6
6
|
import { readExitRecord, readRestartLedger } from '../runner.js';
|
|
7
|
-
import {
|
|
7
|
+
import { readDaemonRecoveryStatus } from '../daemon-recovery.js';
|
|
8
8
|
import { roleCapabilities } from './capabilities.js';
|
|
9
9
|
import { FleetError } from './errors.js';
|
|
10
10
|
const clean = (value, max = 512) => value.replace(/[\0-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').trim().slice(0, max);
|
|
@@ -47,10 +47,11 @@ function readIsolation(dir) {
|
|
|
47
47
|
function sessionOverall(supervisor, session, restart, monitor, isolation, problems) {
|
|
48
48
|
if (restart.circuit === 'open' || monitor.health === 'failed' || monitor.health === 'degraded'
|
|
49
49
|
|| isolation.degraded
|
|
50
|
-
|| problems.some(problem => problem.severity === 'error'
|
|
50
|
+
|| problems.some(problem => problem.severity === 'error'
|
|
51
|
+
|| problem.source === 'watchdog' || problem.source === 'daemon-recovery')
|
|
51
52
|
|| session.readiness === 'failed')
|
|
52
53
|
return 'attention';
|
|
53
|
-
// A reachable
|
|
54
|
+
// A reachable agent session is the user's live interaction surface. Its
|
|
54
55
|
// evidence remains authoritative even when a service manager no longer owns
|
|
55
56
|
// the process (for example, a detached ACP session with an inactive unit).
|
|
56
57
|
if (session.reachability === 'online'
|
|
@@ -69,11 +70,9 @@ function sessionOverall(supervisor, session, restart, monitor, isolation, proble
|
|
|
69
70
|
}
|
|
70
71
|
export class FleetQueryService {
|
|
71
72
|
options;
|
|
72
|
-
tmux;
|
|
73
73
|
control;
|
|
74
74
|
constructor(options) {
|
|
75
75
|
this.options = options;
|
|
76
|
-
this.tmux = options.tmux ?? new Tmux();
|
|
77
76
|
this.control = options.control ?? controlRequest;
|
|
78
77
|
}
|
|
79
78
|
async list() {
|
|
@@ -106,6 +105,16 @@ export class FleetQueryService {
|
|
|
106
105
|
const lastExit = dir ? readExitRecord(join(dir, '.exit-status')) ?? undefined : undefined;
|
|
107
106
|
const session = await this.session(role, dir, live.state);
|
|
108
107
|
const problems = [...role.problems];
|
|
108
|
+
const recovery = dir ? readDaemonRecoveryStatus(dir) : undefined;
|
|
109
|
+
if (recovery && recovery.state !== 'recovered') {
|
|
110
|
+
const paths = ['agent', 'owner']
|
|
111
|
+
.filter(name => recovery.paths[name].state !== 'recovered')
|
|
112
|
+
.map(name => `${name}:${recovery.paths[name].state}`);
|
|
113
|
+
problems.push({
|
|
114
|
+
code: 'daemon_recovery', severity: 'warning', source: 'daemon-recovery',
|
|
115
|
+
detail: `${recovery.state}; ${paths.join(', ') || 'no degraded path'}; epoch ${recovery.epoch || 'unavailable'}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
109
118
|
if (live.state === 'running' && session.reachability !== 'online')
|
|
110
119
|
problems.push({
|
|
111
120
|
code: 'supervisor_session_disagreement', severity: 'warning',
|
|
@@ -174,26 +183,6 @@ export class FleetQueryService {
|
|
|
174
183
|
};
|
|
175
184
|
}
|
|
176
185
|
}
|
|
177
|
-
if (intended === 'tmux' || role.detectedBackend === 'tmux') {
|
|
178
|
-
try {
|
|
179
|
-
const has = await this.tmux.has(role.id);
|
|
180
|
-
return {
|
|
181
|
-
backend: 'tmux', reachability: has ? 'online' : supervisor === 'stopped' ? 'offline' : 'unknown',
|
|
182
|
-
readiness: has ? 'idle' : supervisor === 'running' ? 'starting' : 'failed',
|
|
183
|
-
evidence: 'inferred',
|
|
184
|
-
// tmux exposes no agent-side evidence at all, and `readiness: idle`
|
|
185
|
-
// here is a pane-liveness inference, not an activity claim.
|
|
186
|
-
activity: { state: 'unobservable' },
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
return {
|
|
191
|
-
backend: 'tmux', reachability: 'unknown', readiness: 'unknown',
|
|
192
|
-
evidence: 'inferred', lastError: clean(error.message),
|
|
193
|
-
activity: { state: 'unobservable' },
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
186
|
return {
|
|
198
187
|
backend: 'unknown', reachability: supervisor === 'stopped' ? 'offline' : 'unknown',
|
|
199
188
|
readiness: supervisor === 'stopped' ? 'failed' : 'unknown', evidence: 'inferred',
|
|
@@ -5,21 +5,14 @@ import type { OpsDeps } from '../ops.js';
|
|
|
5
5
|
import type { ResolvedRole } from '../config.js';
|
|
6
6
|
import { FleetError } from './errors.js';
|
|
7
7
|
import { type ManagedFleetSpawnResult } from '../fleet-proxy.js';
|
|
8
|
-
import { type HarnessModelCatalog, type HarnessModelOption } from './model-catalog.js';
|
|
9
8
|
export interface CreateRoleSessionRequest {
|
|
10
9
|
name: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
model?: string | null;
|
|
14
|
-
reasoningEffort?: string | null;
|
|
15
|
-
session: 'acp' | 'tmux';
|
|
10
|
+
brain: import('../config.js').AgentSelection;
|
|
11
|
+
role: import('../config.js').AgentSelection;
|
|
16
12
|
cwd?: string;
|
|
17
13
|
lifetime: 'permanent' | 'temporary';
|
|
18
|
-
mission?: string;
|
|
19
14
|
coordinator?: string;
|
|
20
15
|
permissions: CommonPermissions;
|
|
21
|
-
bio?: string;
|
|
22
|
-
persona?: string;
|
|
23
16
|
monitor?: WebCreationMonitor;
|
|
24
17
|
openAfterCreate: boolean;
|
|
25
18
|
highRiskAcknowledged?: boolean;
|
|
@@ -38,17 +31,6 @@ export type WebCreationMonitor = {
|
|
|
38
31
|
export interface CreationCapabilities {
|
|
39
32
|
available: boolean;
|
|
40
33
|
reasons: string[];
|
|
41
|
-
harnesses: Array<{
|
|
42
|
-
id: 'codex' | 'claude-code';
|
|
43
|
-
available: boolean;
|
|
44
|
-
sessions: Array<'acp' | 'tmux'>;
|
|
45
|
-
defaultModel?: string;
|
|
46
|
-
models: string[];
|
|
47
|
-
modelOptions: HarnessModelOption[];
|
|
48
|
-
catalogSource: string;
|
|
49
|
-
customModelAllowed: true;
|
|
50
|
-
warnings: string[];
|
|
51
|
-
}>;
|
|
52
34
|
lifetimes: Array<'permanent' | 'temporary'>;
|
|
53
35
|
identityBootstrap: {
|
|
54
36
|
mode: 'current-fleet-first-boot';
|
|
@@ -71,7 +53,7 @@ export interface CreationPreview {
|
|
|
71
53
|
name: string;
|
|
72
54
|
identity: string;
|
|
73
55
|
harness: string;
|
|
74
|
-
session: 'acp'
|
|
56
|
+
session: 'acp';
|
|
75
57
|
model?: string;
|
|
76
58
|
reasoningEffort?: string;
|
|
77
59
|
cwd?: string;
|
|
@@ -95,7 +77,7 @@ export interface CreationAction {
|
|
|
95
77
|
actionId: string;
|
|
96
78
|
requestHash: string;
|
|
97
79
|
roleId: string;
|
|
98
|
-
session: 'acp'
|
|
80
|
+
session: 'acp';
|
|
99
81
|
lifetime: 'permanent' | 'temporary';
|
|
100
82
|
state: CreationStage;
|
|
101
83
|
stages: Array<{
|
|
@@ -121,9 +103,8 @@ export interface RoleCreationServiceOptions {
|
|
|
121
103
|
tempLauncher?: SupervisorLauncher;
|
|
122
104
|
allowedCwdRoots?: string[];
|
|
123
105
|
journalDir?: string;
|
|
124
|
-
probeReady?: (name: string, session: 'acp'
|
|
106
|
+
probeReady?: (name: string, session: 'acp') => Promise<'ready' | 'attention' | 'unknown'>;
|
|
125
107
|
onProgress?: (action: CreationAction) => void;
|
|
126
|
-
modelCatalogs?: Partial<Record<'codex' | 'claude-code', () => HarnessModelCatalog>>;
|
|
127
108
|
/** Direct/managed callers must not create or restore the web action journal. */
|
|
128
109
|
journal?: boolean;
|
|
129
110
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
3
3
|
import { isAbsolute, relative } from 'node:path';
|
|
4
|
-
import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig,
|
|
4
|
+
import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig, resolvePermissions, ROLE_NAME_RE, validateMonitorConfig, } from '../config.js';
|
|
5
5
|
import { daemonIdentityProvisioner, } from '../creation.js';
|
|
6
6
|
import { replaceFileAtomically } from '../atomic-file.js';
|
|
7
7
|
import { stateRoot } from '../paths.js';
|
|
8
|
-
import {
|
|
8
|
+
import { spawnDryRun, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
|
|
9
9
|
import { FleetError, normalizeError } from './errors.js';
|
|
10
10
|
import { inheritCallerSpawnDefaults } from '../fleet-proxy.js';
|
|
11
11
|
import { effectivePermissionMode } from '../permissions.js';
|
|
12
12
|
import { effectiveRoleModel } from '../model-env.js';
|
|
13
|
-
import { claudeModelCatalog, codexModelCatalog, } from './model-catalog.js';
|
|
14
13
|
const canonical = (value) => {
|
|
15
14
|
if (Array.isArray(value))
|
|
16
15
|
return `[${value.map(canonical).join(',')}]`;
|
|
@@ -62,6 +61,16 @@ export class RoleCreationService {
|
|
|
62
61
|
const creationActionId = randomUUID();
|
|
63
62
|
plan.options.creationActionId = creationActionId;
|
|
64
63
|
const statePath = await this.launchSync(plan.options);
|
|
64
|
+
const selectionSummary = (kind) => {
|
|
65
|
+
const value = plan.options[kind];
|
|
66
|
+
const provenance = plan.inherited.includes(kind) ? 'inherited' : 'explicit';
|
|
67
|
+
if (!value)
|
|
68
|
+
return `unresolved (${provenance})`;
|
|
69
|
+
if ('ref' in value)
|
|
70
|
+
return `ref:${value.ref} (${provenance})`;
|
|
71
|
+
const fingerprint = hash(value.inline).slice(0, 16);
|
|
72
|
+
return `inline:sha256:${fingerprint} (${provenance})`;
|
|
73
|
+
};
|
|
65
74
|
return {
|
|
66
75
|
caller: plan.caller, role: plan.options.name,
|
|
67
76
|
lifetime: plan.options.temp ? 'temporary' : 'permanent', statePath,
|
|
@@ -70,6 +79,7 @@ export class RoleCreationService {
|
|
|
70
79
|
monitor: { mode: plan.preview.resolvedRole.monitor.mode, interrupt: plan.preview.resolvedRole.monitor.interrupt },
|
|
71
80
|
permissionMode: effectivePermissionMode(plan.preview.resolvedRole), inherited: plan.inherited,
|
|
72
81
|
creationActionId,
|
|
82
|
+
brainSummary: selectionSummary('brain'), roleSummary: selectionSummary('role'),
|
|
73
83
|
};
|
|
74
84
|
}
|
|
75
85
|
launchSync(options, creation) {
|
|
@@ -80,47 +90,16 @@ export class RoleCreationService {
|
|
|
80
90
|
async capabilities() {
|
|
81
91
|
const reasons = [];
|
|
82
92
|
let defaults = {};
|
|
83
|
-
let roles = [];
|
|
84
93
|
try {
|
|
85
94
|
const config = loadConfig(this.options.configPath);
|
|
86
95
|
defaults = config.defaults;
|
|
87
|
-
roles = config.roles;
|
|
88
96
|
}
|
|
89
97
|
catch (error) {
|
|
90
98
|
reasons.push(`configuration is invalid: ${error.message}`);
|
|
91
99
|
}
|
|
92
|
-
const modelsFor = (harness, catalog) => {
|
|
93
|
-
const configured = roles.filter(role => role.harness === harness)
|
|
94
|
-
.flatMap(role => [role.model, ...(role.model_chain ?? [])])
|
|
95
|
-
.filter((model) => Boolean(model));
|
|
96
|
-
const inherited = resolveRoleModel(undefined, harness, defaults);
|
|
97
|
-
const configuredOptions = [...new Set([...(inherited ? [inherited] : []), ...configured])]
|
|
98
|
-
.filter(id => !catalog.models.some(model => model.id === id))
|
|
99
|
-
.map(id => ({ id, label: `${id} (configured)`, reasoningEfforts: [], source: 'fleet-config' }));
|
|
100
|
-
const modelOptions = [...configuredOptions, ...catalog.models];
|
|
101
|
-
return { modelOptions, models: modelOptions.map(model => model.id) };
|
|
102
|
-
};
|
|
103
|
-
const codexCatalog = this.options.modelCatalogs?.codex?.() ?? codexModelCatalog();
|
|
104
|
-
const claudeCatalog = this.options.modelCatalogs?.['claude-code']?.() ?? claudeModelCatalog();
|
|
105
|
-
const codexModels = modelsFor('codex', codexCatalog);
|
|
106
|
-
const claudeModels = modelsFor('claude-code', claudeCatalog);
|
|
107
100
|
return {
|
|
108
101
|
available: reasons.length === 0,
|
|
109
102
|
reasons,
|
|
110
|
-
harnesses: [
|
|
111
|
-
{
|
|
112
|
-
id: 'codex', available: true, sessions: ['acp', 'tmux'],
|
|
113
|
-
defaultModel: resolveRoleModel(undefined, 'codex', defaults),
|
|
114
|
-
...codexModels, catalogSource: 'codex-runtime-catalog', customModelAllowed: true,
|
|
115
|
-
warnings: codexCatalog.warnings,
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
id: 'claude-code', available: true, sessions: ['acp', 'tmux'],
|
|
119
|
-
defaultModel: resolveRoleModel(undefined, 'claude-code', defaults),
|
|
120
|
-
...claudeModels, catalogSource: 'claude-adapter-2.1', customModelAllowed: true,
|
|
121
|
-
warnings: claudeCatalog.warnings,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
103
|
lifetimes: ['permanent', 'temporary'],
|
|
125
104
|
identityBootstrap: {
|
|
126
105
|
mode: 'current-fleet-first-boot',
|
|
@@ -144,14 +123,12 @@ export class RoleCreationService {
|
|
|
144
123
|
const defaults = cfg.defaults;
|
|
145
124
|
const opts = this.spawnOptions(request);
|
|
146
125
|
validateSpawnOpts(opts);
|
|
147
|
-
|
|
126
|
+
const resolved = spawnDryRun(opts).resolvedRole;
|
|
148
127
|
const cwd = request.cwd ? this.resolveCwd(request.cwd) : undefined;
|
|
149
128
|
const effective = {
|
|
150
129
|
name: request.name, identity: request.name,
|
|
151
|
-
harness:
|
|
152
|
-
|
|
153
|
-
model: resolveRoleModel(request.model, request.harness, defaults),
|
|
154
|
-
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
130
|
+
harness: resolved.harness, session: resolved.session, model: resolved.model,
|
|
131
|
+
reasoningEffort: resolved.effort,
|
|
155
132
|
cwd, lifetime: request.lifetime,
|
|
156
133
|
permissions: resolvePermissions(defaults.permissions, request.permissions),
|
|
157
134
|
monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
|
|
@@ -183,9 +160,7 @@ export class RoleCreationService {
|
|
|
183
160
|
if (existingIdentity === 'unknown' && !request.unverifiedIdentityAcknowledged)
|
|
184
161
|
prerequisites.push('confirm creation with an unverified identity preflight');
|
|
185
162
|
const provenance = {
|
|
186
|
-
|
|
187
|
-
model: request.model !== undefined ? 'request'
|
|
188
|
-
: resolveRoleModel(undefined, request.harness, defaults) ? 'fleet-default' : 'built-in',
|
|
163
|
+
brain: 'request', role: 'request', identity: 'built-in',
|
|
189
164
|
cwd: request.cwd ? 'request' : 'built-in', permissions: 'request',
|
|
190
165
|
monitor: request.monitor ? 'request' : defaults.monitor ? 'fleet-default' : 'built-in',
|
|
191
166
|
};
|
|
@@ -264,9 +239,7 @@ export class RoleCreationService {
|
|
|
264
239
|
this.stage(action, 'waiting_for_session');
|
|
265
240
|
const ready = await this.waitForReady(action.roleId, action.session);
|
|
266
241
|
if (ready === 'ready') {
|
|
267
|
-
action.openPath = action.
|
|
268
|
-
? `/roles/${encodeURIComponent(action.roleId)}/activity`
|
|
269
|
-
: `/roles/${encodeURIComponent(action.roleId)}/terminal`;
|
|
242
|
+
action.openPath = `/roles/${encodeURIComponent(action.roleId)}/activity`;
|
|
270
243
|
this.stage(action, 'session_reachable', 'session is reachable; identity binding has no structured evidence in this version');
|
|
271
244
|
}
|
|
272
245
|
else if (ready === 'attention') {
|
|
@@ -298,8 +271,8 @@ export class RoleCreationService {
|
|
|
298
271
|
}
|
|
299
272
|
validate(input) {
|
|
300
273
|
const allowed = new Set([
|
|
301
|
-
'name', '
|
|
302
|
-
'coordinator', 'permissions', '
|
|
274
|
+
'name', 'brain', 'role', 'cwd', 'lifetime',
|
|
275
|
+
'coordinator', 'permissions', 'monitor', 'openAfterCreate',
|
|
303
276
|
'highRiskAcknowledged', 'reuseExistingIdentityAcknowledged',
|
|
304
277
|
'unverifiedIdentityAcknowledged',
|
|
305
278
|
]);
|
|
@@ -309,20 +282,11 @@ export class RoleCreationService {
|
|
|
309
282
|
throw new FleetError('invalid_request', `unsupported web creation field: ${unsupported[0]}`);
|
|
310
283
|
if (!ROLE_NAME_RE.test(input.name))
|
|
311
284
|
throw new FleetError('invalid_request', 'invalid role name');
|
|
312
|
-
if (!
|
|
313
|
-
throw new FleetError('invalid_request', '
|
|
314
|
-
if (!['acp', 'tmux'].includes(input.session))
|
|
315
|
-
throw new FleetError('invalid_request', 'unsupported session backend');
|
|
285
|
+
if (!input.brain || !input.role)
|
|
286
|
+
throw new FleetError('invalid_request', 'brain and role selections are required');
|
|
316
287
|
if (!['permanent', 'temporary'].includes(input.lifetime))
|
|
317
288
|
throw new FleetError('invalid_request', 'unsupported lifetime');
|
|
318
|
-
bounded(input.model ?? undefined, 'model', 128);
|
|
319
|
-
bounded(input.reasoningEffort ?? undefined, 'reasoning effort', 16);
|
|
320
|
-
if (input.reasoningEffort != null && !['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(input.reasoningEffort))
|
|
321
|
-
throw new FleetError('invalid_request', 'unsupported reasoning effort');
|
|
322
|
-
bounded(input.mission, 'mission', 4_096);
|
|
323
289
|
bounded(input.coordinator, 'coordinator', 128);
|
|
324
|
-
bounded(input.bio, 'bio', 8_192);
|
|
325
|
-
bounded(input.persona, 'persona', 16_384);
|
|
326
290
|
resolvePermissions(undefined, input.permissions);
|
|
327
291
|
if (input.monitor) {
|
|
328
292
|
const problems = validateMonitorConfig(input.monitor);
|
|
@@ -332,10 +296,8 @@ export class RoleCreationService {
|
|
|
332
296
|
throw new FleetError('invalid_request', 'web creation supports monitor.inject=notification only');
|
|
333
297
|
}
|
|
334
298
|
return {
|
|
335
|
-
...input,
|
|
336
|
-
|
|
337
|
-
mission: input.mission?.trim() || undefined, coordinator: input.coordinator?.trim() || undefined,
|
|
338
|
-
bio: input.bio?.trim() || undefined, persona: input.persona?.trim() || undefined,
|
|
299
|
+
...input, brain: structuredClone(input.brain), role: structuredClone(input.role),
|
|
300
|
+
coordinator: input.coordinator?.trim() || undefined,
|
|
339
301
|
monitor: input.monitor ? structuredClone(input.monitor) : undefined,
|
|
340
302
|
};
|
|
341
303
|
}
|
|
@@ -362,12 +324,10 @@ export class RoleCreationService {
|
|
|
362
324
|
spawnOptions(request, creationActionId) {
|
|
363
325
|
return {
|
|
364
326
|
name: request.name, temp: request.lifetime === 'temporary',
|
|
365
|
-
identity: request.name,
|
|
366
|
-
|
|
367
|
-
reasoningEffort: request.reasoningEffort,
|
|
368
|
-
mission: request.mission, coordinator: request.coordinator,
|
|
327
|
+
identity: request.name, brain: request.brain, role: request.role, cwd: request.cwd,
|
|
328
|
+
coordinator: request.coordinator,
|
|
369
329
|
approval: request.permissions.approval, filesystem: request.permissions.filesystem,
|
|
370
|
-
unattended: request.permissions.unattended,
|
|
330
|
+
unattended: request.permissions.unattended,
|
|
371
331
|
monitorConfig: request.monitor,
|
|
372
332
|
configPath: this.options.configPath,
|
|
373
333
|
surface: creationActionId ? 'web' : undefined, creationActionId,
|
|
@@ -4,6 +4,7 @@ import { loadConfig, ROLE_NAME_RE } from '../config.js';
|
|
|
4
4
|
import { agentDir, stateRoot } from '../paths.js';
|
|
5
5
|
import { rmRole } from '../ops.js';
|
|
6
6
|
import { FleetError } from './errors.js';
|
|
7
|
+
import { provesGeneratedAgentSource } from '../generated-agent-source.js';
|
|
7
8
|
export class RoleRemovalService {
|
|
8
9
|
options;
|
|
9
10
|
constructor(options) {
|
|
@@ -45,8 +46,9 @@ export class RoleRemovalService {
|
|
|
45
46
|
`Stop and uninstall the exact backend registration for '${requested}'.`,
|
|
46
47
|
`Archive then remove ${lifetime === 'temporary' ? temporaryState : permanentState}.`,
|
|
47
48
|
];
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const generatedSource = role && provesGeneratedAgentSource(agentDir(requested), cfg.files[0], role.sourceFile);
|
|
50
|
+
if (generatedSource)
|
|
51
|
+
effects.push(`Remove generated Agent configuration ${role.sourceFile}.`);
|
|
50
52
|
else if (role)
|
|
51
53
|
effects.push(`Keep hand-written configuration ${role.sourceFile}; the role can be recreated from it.`);
|
|
52
54
|
else
|
|
@@ -76,7 +78,8 @@ export class RoleRemovalService {
|
|
|
76
78
|
mkdirSync(recoveryPath, { recursive: true, mode: 0o700 });
|
|
77
79
|
if (existsSync(sourceState))
|
|
78
80
|
cpSync(sourceState, join(recoveryPath, 'state'), { recursive: true });
|
|
79
|
-
if (role
|
|
81
|
+
if (role && provesGeneratedAgentSource(agentDir(preview.role), cfg.files[0], role.sourceFile)
|
|
82
|
+
&& existsSync(role.sourceFile))
|
|
80
83
|
cpSync(role.sourceFile, join(recoveryPath, basename(role.sourceFile)));
|
|
81
84
|
if (role || preview.lifetime === 'temporary')
|
|
82
85
|
await rmRole(cfg, preview.role, this.options.ops);
|
|
@@ -3,9 +3,8 @@ export interface RoleRepositoryOptions {
|
|
|
3
3
|
configPath?: string;
|
|
4
4
|
permanentRoot?: string;
|
|
5
5
|
temporaryRoot?: string;
|
|
6
|
-
probeBackend?: (name: string, intended?: 'acp'
|
|
6
|
+
probeBackend?: (name: string, intended?: 'acp') => Promise<{
|
|
7
7
|
acp: boolean;
|
|
8
|
-
tmux: boolean;
|
|
9
8
|
}>;
|
|
10
9
|
concurrency?: number;
|
|
11
10
|
timeoutMs?: number;
|
|
@@ -42,7 +42,11 @@ function snapshot(path, name) {
|
|
|
42
42
|
const raw = parse(readFileSync(path, 'utf8'));
|
|
43
43
|
if (!raw || typeof raw !== 'object')
|
|
44
44
|
throw new Error('snapshot must be a mapping');
|
|
45
|
-
|
|
45
|
+
if (raw.session === 'tmux')
|
|
46
|
+
throw new Error('session: tmux is no longer supported; use session: acp with the Codex or Claude Code adapter');
|
|
47
|
+
if (raw.session !== undefined && raw.session !== 'acp')
|
|
48
|
+
throw new Error(`unsupported session '${String(raw.session)}'; expected session: acp`);
|
|
49
|
+
const session = 'acp';
|
|
46
50
|
return {
|
|
47
51
|
role: {
|
|
48
52
|
...raw, name, harness: typeof raw.harness === 'string' ? raw.harness : 'claude-code',
|
|
@@ -167,14 +171,10 @@ export class RoleRepository {
|
|
|
167
171
|
const timeoutMs = this.options.timeoutMs ?? 2_000;
|
|
168
172
|
const result = await Promise.race([
|
|
169
173
|
this.options.probeBackend(name, intended),
|
|
170
|
-
new Promise(resolve => setTimeout(() => resolve({ acp: false
|
|
171
|
-
]).catch(() => ({ acp: false
|
|
172
|
-
if (result.acp && result.tmux)
|
|
173
|
-
return 'ambiguous';
|
|
174
|
+
new Promise(resolve => setTimeout(() => resolve({ acp: false }), timeoutMs)),
|
|
175
|
+
]).catch(() => ({ acp: false }));
|
|
174
176
|
if (result.acp)
|
|
175
177
|
return 'acp';
|
|
176
|
-
if (result.tmux)
|
|
177
|
-
return 'tmux';
|
|
178
178
|
return 'none';
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { controlRequest } from '../session/control.js';
|
|
2
2
|
import type { ConversationEventV1, ConversationSnapshot, PromptReceipt } from '../session/conversation-types.js';
|
|
3
3
|
import type { SessionSnapshot } from '../session/types.js';
|
|
4
|
-
import { Tmux } from '../tmux.js';
|
|
5
4
|
import type { OutputPage, SendReceipt, SessionDescriptor } from './types.js';
|
|
6
5
|
export interface ConversationPageView {
|
|
7
6
|
events: ConversationEventV1[];
|
|
@@ -120,14 +119,3 @@ export declare class AcpRoleSessionAdapter implements RoleSessionControl {
|
|
|
120
119
|
}>;
|
|
121
120
|
private call;
|
|
122
121
|
}
|
|
123
|
-
export declare class TmuxRoleSessionAdapter implements RoleSessionControl {
|
|
124
|
-
private readonly roleId;
|
|
125
|
-
private readonly tmux;
|
|
126
|
-
constructor(roleId: string, tmux?: Tmux);
|
|
127
|
-
describe(): Promise<SessionDescriptor>;
|
|
128
|
-
snapshot(): Promise<SessionSnapshot>;
|
|
129
|
-
recentOutput(request?: {
|
|
130
|
-
limit?: number;
|
|
131
|
-
}): Promise<OutputPage>;
|
|
132
|
-
sendText(text: string): Promise<SendReceipt>;
|
|
133
|
-
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { controlRequest, followConversation } from '../session/control.js';
|
|
3
|
-
import { Tmux } from '../tmux.js';
|
|
4
2
|
import { FleetError, normalizeError } from './errors.js';
|
|
5
3
|
const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
|
|
6
4
|
export class AcpRoleSessionAdapter {
|
|
@@ -149,43 +147,3 @@ export class AcpRoleSessionAdapter {
|
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
149
|
}
|
|
152
|
-
export class TmuxRoleSessionAdapter {
|
|
153
|
-
roleId;
|
|
154
|
-
tmux;
|
|
155
|
-
constructor(roleId, tmux = new Tmux()) {
|
|
156
|
-
this.roleId = roleId;
|
|
157
|
-
this.tmux = tmux;
|
|
158
|
-
}
|
|
159
|
-
async describe() {
|
|
160
|
-
return { backend: 'tmux', protocolVersion: 1, features: ['text', 'capture'] };
|
|
161
|
-
}
|
|
162
|
-
async snapshot() {
|
|
163
|
-
const alive = await this.tmux.has(this.roleId);
|
|
164
|
-
return { backend: 'tmux', alive, readiness: alive ? 'idle' : 'failed' };
|
|
165
|
-
}
|
|
166
|
-
async recentOutput(request = {}) {
|
|
167
|
-
try {
|
|
168
|
-
const text = await this.tmux.capture(this.roleId, Math.min(request.limit ?? 100, 500));
|
|
169
|
-
return { events: [], text, truncated: false };
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
throw normalizeError(error);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async sendText(text) {
|
|
176
|
-
if (!text.trim())
|
|
177
|
-
throw new FleetError('invalid_request', 'text is required');
|
|
178
|
-
if (Buffer.byteLength(text) > 32 * 1024)
|
|
179
|
-
throw new FleetError('invalid_request', 'text exceeds 32 KiB');
|
|
180
|
-
try {
|
|
181
|
-
await this.tmux.sendText(this.roleId, text);
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
throw normalizeError(error);
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
accepted: true, promptId: randomUUID(), queuedBehind: 0, terminalOutcomeKnown: false,
|
|
188
|
-
detail: 'literal text and Enter sent; terminal outcome is unknown',
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { InterruptOutcome, QueuedPrompt,
|
|
1
|
+
import type { AgentSession, InterruptOutcome, QueuedPrompt, SubmitPromptOptions, TurnCancellationSource } from '../session/types.js';
|
|
2
2
|
/** Thin in-process session mutation kernel. Callers retain every policy decision. */
|
|
3
|
-
export declare const queueSessionPrompt: (session:
|
|
4
|
-
export declare const interruptSession: (session:
|
|
5
|
-
export declare const respondSessionPermission: (session:
|
|
3
|
+
export declare const queueSessionPrompt: (session: AgentSession, text: string, options?: SubmitPromptOptions) => Promise<QueuedPrompt>;
|
|
4
|
+
export declare const interruptSession: (session: AgentSession, source: TurnCancellationSource) => Promise<InterruptOutcome>;
|
|
5
|
+
export declare const respondSessionPermission: (session: AgentSession, permissionId: string, optionId: string) => boolean;
|
|
6
6
|
export type GenerationPermissionResult = 'accepted' | 'stale' | 'unavailable';
|
|
7
|
-
export declare const respondSessionPermissionV2: (session:
|
|
7
|
+
export declare const respondSessionPermissionV2: (session: AgentSession, permissionId: string, optionId: string, sessionGeneration: string) => GenerationPermissionResult;
|
|
@@ -179,7 +179,7 @@ export declare class TaskRoomApplicationService {
|
|
|
179
179
|
name: string;
|
|
180
180
|
version: number;
|
|
181
181
|
description: string;
|
|
182
|
-
|
|
182
|
+
sourceFile?: string;
|
|
183
183
|
room?: import("../rooms-tasks/types.js").TemplateRoomConfig;
|
|
184
184
|
contract?: string;
|
|
185
185
|
members: import("../rooms-tasks/types.js").TemplateMemberSlot[];
|