@ours.network/fleet 1.0.5 → 1.1.0-nightly.2
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 +90 -112
- 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 +1 -24
- package/dist/application/role-creation-service.d.ts +5 -5
- package/dist/application/role-creation-service.js +14 -9
- 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/types.d.ts +1 -8
- package/dist/briefing.js +3 -6
- package/dist/build-info.json +5 -5
- package/dist/cli.js +17 -28
- package/dist/config.d.ts +29 -6
- package/dist/config.js +320 -40
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +2 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +51 -95
- package/dist/doctor.js +1 -8
- package/dist/fleet-proxy.d.ts +1 -1
- 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 +46 -0
- package/dist/harness/agent-session.js +1 -0
- package/dist/harness/claude-code-session.d.ts +22 -0
- package/dist/harness/claude-code-session.js +32 -0
- package/dist/harness/claude-code.d.ts +2 -1
- package/dist/harness/claude-code.js +39 -81
- package/dist/harness/codex-session.d.ts +22 -0
- package/dist/harness/codex-session.js +32 -0
- package/dist/harness/codex.d.ts +2 -1
- package/dist/harness/codex.js +49 -45
- package/dist/harness/registry.js +4 -0
- package/dist/harness/types.d.ts +6 -38
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1 -3
- package/dist/init-guidance.d.ts +1 -0
- package/dist/init-guidance.js +5 -0
- package/dist/isolation/resources.d.ts +3 -7
- package/dist/isolation/resources.js +3 -7
- package/dist/model-env.d.ts +2 -4
- package/dist/model-env.js +4 -7
- 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 +2 -2
- package/dist/resolved-plan.d.ts +3 -2
- package/dist/resolved-plan.js +21 -5
- package/dist/runner.d.ts +5 -15
- package/dist/runner.js +26 -102
- package/dist/sensitive-config.d.ts +2 -0
- package/dist/sensitive-config.js +4 -0
- package/dist/session/acp.d.ts +2 -2
- package/dist/session/acp.js +2 -3
- 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 +3 -3
- package/dist/session/types.d.ts +11 -5
- package/dist/spawn.d.ts +4 -4
- package/dist/spawn.js +60 -32
- package/dist/supervisor/none.d.ts +1 -1
- package/dist/supervisor/none.js +2 -2
- package/dist/tmux.d.ts +1 -14
- package/dist/tmux.js +1 -51
- package/dist/watchdog/config.js +6 -4
- package/dist/watchdog/run.js +8 -23
- 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 +383 -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 +18 -18
- 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-BbE9EX6n.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 +4 -8
- 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
package/dist/spawn.js
CHANGED
|
@@ -2,14 +2,15 @@ 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, resolveAuthProxy, resolveModelChain, resolveMonitorConfig, resolveOwnerChannelConfig, resolvePermissions, resolveRoleModel, resolveWorklogPolicy, validateMonitorConfig, } from './config.js';
|
|
7
|
+
import { loadConfig, resolveAuthProxy, resolveModelChain, resolveMonitorConfig, resolveOwnerChannelConfig, resolvePermissions, resolveRoleModel, resolveWorklogPolicy, splitRootFor, validateMonitorConfig, } from './config.js';
|
|
8
8
|
import { resolveRoleModelEnv } from './model-env.js';
|
|
9
9
|
import { applyRole, up } from './ops.js';
|
|
10
10
|
import { START_STAGGER_FILE } from './runner.js';
|
|
11
11
|
import { buildProvenance, daemonIdentityProvisioner, ensureIdentity, provenanceOf, withCreationTransaction, writeProvenance, writeRoleFile, } from './creation.js';
|
|
12
12
|
import { VERSION } from './version.js';
|
|
13
|
+
import { recordGeneratedAgentSource } from './generated-agent-source.js';
|
|
13
14
|
import './harness/claude-code.js';
|
|
14
15
|
import './harness/codex.js';
|
|
15
16
|
import { getAdapter } from './harness/registry.js';
|
|
@@ -34,8 +35,9 @@ export function profileValues(o) {
|
|
|
34
35
|
/** Pure option-to-role mapping shared by CLI and application services. */
|
|
35
36
|
export function buildRoleConfig(o, defaultHarness) {
|
|
36
37
|
const r = {};
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const harness = o.harness ?? defaultHarness;
|
|
39
|
+
if (harness)
|
|
40
|
+
r.harness = harness;
|
|
39
41
|
if (o.session)
|
|
40
42
|
r.session = o.session;
|
|
41
43
|
if (o.identity)
|
|
@@ -52,7 +54,8 @@ export function buildRoleConfig(o, defaultHarness) {
|
|
|
52
54
|
r.model = null;
|
|
53
55
|
else if (o.model?.trim())
|
|
54
56
|
r.model = o.model.trim();
|
|
55
|
-
|
|
57
|
+
if (o.reasoningEffort?.trim())
|
|
58
|
+
r.effort = o.reasoningEffort.trim();
|
|
56
59
|
const harnessOptions = {};
|
|
57
60
|
if (o.permissionMode)
|
|
58
61
|
harnessOptions[harness === 'claude-code' ? 'permission_mode' : 'approval'] = o.permissionMode;
|
|
@@ -65,12 +68,8 @@ export function buildRoleConfig(o, defaultHarness) {
|
|
|
65
68
|
if (o.search === true)
|
|
66
69
|
harnessOptions.search = true;
|
|
67
70
|
const codexConfig = { ...(o.codexConfig ?? {}) };
|
|
68
|
-
if (o.reasoningEffort && harness === 'codex')
|
|
69
|
-
codexConfig.model_reasoning_effort = o.reasoningEffort;
|
|
70
71
|
if (Object.keys(codexConfig).length)
|
|
71
72
|
harnessOptions.config = codexConfig;
|
|
72
|
-
if (o.reasoningEffort && harness === 'claude-code')
|
|
73
|
-
harnessOptions.effort = o.reasoningEffort;
|
|
74
73
|
if (o.addDirs?.length)
|
|
75
74
|
harnessOptions.add_dirs = o.addDirs;
|
|
76
75
|
if (o.monitor === true)
|
|
@@ -123,8 +122,10 @@ export function readIsolationFile(path) {
|
|
|
123
122
|
export function validateSpawnOpts(o) {
|
|
124
123
|
if (o.mission !== undefined && o.missionFile)
|
|
125
124
|
throw new Error('--mission and --mission-file are mutually exclusive');
|
|
126
|
-
if (o.session
|
|
127
|
-
throw new Error(
|
|
125
|
+
if (o.session === 'tmux')
|
|
126
|
+
throw new Error("invalid --session 'tmux'; tmux is no longer supported; use --session acp");
|
|
127
|
+
if (o.session && o.session !== 'acp')
|
|
128
|
+
throw new Error(`invalid --session '${o.session}'; allowed: acp`);
|
|
128
129
|
if (o.approval && !['ask', 'auto', 'allow', 'deny'].includes(o.approval))
|
|
129
130
|
throw new Error(`invalid --approval '${o.approval}'; allowed: ask, auto, allow (deprecated alias: deny)`);
|
|
130
131
|
if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
|
|
@@ -172,6 +173,23 @@ function assertNameFree(o) {
|
|
|
172
173
|
}
|
|
173
174
|
/** The ours identity a spawn will bind: explicit, else the role name. */
|
|
174
175
|
export const effectiveIdentity = (o) => o.identity ?? o.name;
|
|
176
|
+
/** Bare Agent document written by v2 spawn: inline Role × inline Brain + operations. */
|
|
177
|
+
export function buildAgentDocument(raw) {
|
|
178
|
+
const role = Object.fromEntries(['mission', 'persona', 'bio', 'briefing_file']
|
|
179
|
+
.filter(key => raw[key] !== undefined)
|
|
180
|
+
.map(key => [key, raw[key]]));
|
|
181
|
+
const brain = Object.fromEntries([
|
|
182
|
+
'harness', 'session', 'session_options', 'model', 'model_chain', 'max_tokens',
|
|
183
|
+
'autocompact_pct', 'harness_options', 'effort',
|
|
184
|
+
].filter(key => raw[key] !== undefined)
|
|
185
|
+
.map(key => [key, raw[key]]));
|
|
186
|
+
const operational = Object.fromEntries([
|
|
187
|
+
'permissions', 'identity', 'cwd', 'coordinator', 'env', 'oversee', 'isolation',
|
|
188
|
+
'monitor', 'owner_channel', 'worklog', 'auth_proxy',
|
|
189
|
+
].filter(key => raw[key] !== undefined)
|
|
190
|
+
.map(key => [key, raw[key]]));
|
|
191
|
+
return { role: { inline: role }, brain: { inline: brain }, ...operational };
|
|
192
|
+
}
|
|
175
193
|
/**
|
|
176
194
|
* Validate and resolve a spawn without reserving names, contacting the daemon,
|
|
177
195
|
* or writing state. Collision checks are necessarily a point-in-time snapshot.
|
|
@@ -184,7 +202,7 @@ export function spawnDryRun(o) {
|
|
|
184
202
|
readMissionFile(o.missionFile);
|
|
185
203
|
assertNameFree(o);
|
|
186
204
|
const cfg = loadConfig(o.configPath);
|
|
187
|
-
const raw = buildRoleConfig(o, cfg.defaults.harness);
|
|
205
|
+
const raw = buildRoleConfig(o, cfg.defaults.harness ?? 'claude-code');
|
|
188
206
|
const harnessOptions = {
|
|
189
207
|
...(cfg.defaults.harness_options ?? {}),
|
|
190
208
|
...(raw.harness_options ?? {}),
|
|
@@ -202,23 +220,30 @@ export function spawnDryRun(o) {
|
|
|
202
220
|
roleEnv: raw.env,
|
|
203
221
|
...(authProxy ? { authProxyBaseUrl: authProxy.base_url } : {}),
|
|
204
222
|
});
|
|
205
|
-
const
|
|
206
|
-
const
|
|
223
|
+
const adapter = getAdapter(harness);
|
|
224
|
+
const brain = adapter.agentSession.resolveBrain({
|
|
225
|
+
model: modelEnv.model,
|
|
226
|
+
effort: raw.effort,
|
|
227
|
+
harnessOptions: Object.keys(harnessOptions).length ? harnessOptions : undefined,
|
|
228
|
+
});
|
|
229
|
+
const model = brain.model ?? undefined;
|
|
230
|
+
const session = raw.session ?? cfg.defaults.session ?? 'acp';
|
|
207
231
|
const resolvedRole = {
|
|
208
232
|
...raw,
|
|
209
233
|
name: o.name,
|
|
210
|
-
sourceFile: o.temp ? '(temp dry-run)' : join(
|
|
234
|
+
sourceFile: o.temp ? '(temp dry-run)' : join(splitRootFor(o.configPath ?? defaultConfigPath()), 'agents', `${o.name}.yaml`),
|
|
211
235
|
harness,
|
|
212
236
|
session,
|
|
213
237
|
session_options: raw.session_options,
|
|
214
238
|
permissions: resolvePermissions(cfg.defaults.permissions, raw.permissions),
|
|
215
239
|
permissionsDeclared: raw.permissions !== undefined || cfg.defaults.permissions !== undefined,
|
|
216
240
|
identity: effectiveIdentity(o),
|
|
241
|
+
effort: raw.effort,
|
|
217
242
|
model,
|
|
218
243
|
model_chain: resolveModelChain(model, raw.model_chain ?? (inheritsModelDefaults
|
|
219
244
|
? cfg.defaults.model_chain
|
|
220
245
|
: undefined)),
|
|
221
|
-
harness_options:
|
|
246
|
+
harness_options: brain.harnessOptions,
|
|
222
247
|
isolation: raw.isolation ?? cfg.defaults.isolation,
|
|
223
248
|
monitor: resolveMonitorConfig(cfg.defaults.monitor, raw.monitor),
|
|
224
249
|
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, raw.owner_channel, session),
|
|
@@ -226,7 +251,6 @@ export function spawnDryRun(o) {
|
|
|
226
251
|
auth_proxy: authProxy,
|
|
227
252
|
};
|
|
228
253
|
resolvedRole.env = modelEnv.env;
|
|
229
|
-
const adapter = getAdapter(resolvedRole.harness);
|
|
230
254
|
if (resolvedRole.auth_proxy && resolvedRole.harness !== 'claude-code')
|
|
231
255
|
throw new Error('auth_proxy is supported only by claude-code');
|
|
232
256
|
const optionProblems = adapter.validateOptions(resolvedRole.harness_options, resolvedRole);
|
|
@@ -235,7 +259,7 @@ export function spawnDryRun(o) {
|
|
|
235
259
|
return {
|
|
236
260
|
schemaVersion: 1,
|
|
237
261
|
warning: 'collision checks are a snapshot; a real spawn reserves names atomically',
|
|
238
|
-
roleDocument:
|
|
262
|
+
roleDocument: buildAgentDocument(raw),
|
|
239
263
|
resolvedRole,
|
|
240
264
|
};
|
|
241
265
|
}
|
|
@@ -255,7 +279,7 @@ function provenanceSettings(o, defaults) {
|
|
|
255
279
|
const inheritedModel = resolveRoleModel(undefined, o.harness, defaults);
|
|
256
280
|
return {
|
|
257
281
|
harness: tagged('harness', provenanceOf(o.harness, defaults.harness, 'claude-code')),
|
|
258
|
-
session: tagged('session', provenanceOf(o.session, defaults.session, '
|
|
282
|
+
session: tagged('session', provenanceOf(o.session, defaults.session, 'acp')),
|
|
259
283
|
identity: o.identity
|
|
260
284
|
? { value: o.identity, source: 'cli' }
|
|
261
285
|
: { value: o.name, source: 'built-in' }, // defaults to the role name
|
|
@@ -276,7 +300,7 @@ function provenanceSettings(o, defaults) {
|
|
|
276
300
|
monitor: tagged('monitorConfig', provenanceOf(o.monitorConfig, defaults.monitor, { mode: 'fleet' })),
|
|
277
301
|
};
|
|
278
302
|
}
|
|
279
|
-
/** Permanent spawn: persist
|
|
303
|
+
/** Permanent spawn: persist one bare Agent document under the selected v2 root. */
|
|
280
304
|
export async function spawnPermanent(o, deps, creation = {}) {
|
|
281
305
|
validateSpawnOpts(o);
|
|
282
306
|
if (o.isolationFile)
|
|
@@ -305,12 +329,11 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
305
329
|
await creation.identityProvisioner?.remove?.(effectiveIdentity(o));
|
|
306
330
|
},
|
|
307
331
|
});
|
|
308
|
-
|
|
332
|
+
const agentRoot = join(splitRootFor(o.configPath ?? defaultConfigPath()), 'agents');
|
|
333
|
+
mkdirSync(agentRoot, { recursive: true });
|
|
309
334
|
creation.onStage?.('writing_role');
|
|
310
|
-
const file = join(
|
|
311
|
-
writeRoleFile(tx, file, stringify(
|
|
312
|
-
roles: { [o.name]: buildRoleConfig(o, cfg.defaults.harness) },
|
|
313
|
-
}));
|
|
335
|
+
const file = join(agentRoot, `${o.name}.yaml`);
|
|
336
|
+
writeRoleFile(tx, file, stringify(buildAgentDocument(buildRoleConfig(o, cfg.defaults.harness ?? 'claude-code'))));
|
|
314
337
|
// `up` materialises the state dir and registers the service. Journal the
|
|
315
338
|
// dir before it exists so a failure leaves the name genuinely reusable
|
|
316
339
|
// rather than blocked by a half-built directory.
|
|
@@ -341,6 +364,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
341
364
|
});
|
|
342
365
|
mkdirSync(agentDir(o.name), { recursive: true });
|
|
343
366
|
writeProvenance(agentDir(o.name), provenance);
|
|
367
|
+
recordGeneratedAgentSource(agentDir(o.name), o.configPath ?? defaultConfigPath(), file);
|
|
344
368
|
creation.onStage?.('registering_supervisor');
|
|
345
369
|
await up(loadConfig(o.configPath), [o.name], {
|
|
346
370
|
...deps,
|
|
@@ -416,19 +440,26 @@ async function spawnTempInner(o, binPath, launch, tx, onStage) {
|
|
|
416
440
|
roleEnv: fromOpts.env,
|
|
417
441
|
...(tempAuthProxy ? { authProxyBaseUrl: tempAuthProxy.base_url } : {}),
|
|
418
442
|
});
|
|
419
|
-
const
|
|
420
|
-
const
|
|
443
|
+
const adapter = getAdapter(harness);
|
|
444
|
+
const brain = adapter.agentSession.resolveBrain({
|
|
445
|
+
model: modelEnv.model,
|
|
446
|
+
effort: fromOpts.effort,
|
|
447
|
+
harnessOptions: Object.keys(mergedHarnessOptions).length ? mergedHarnessOptions : undefined,
|
|
448
|
+
});
|
|
449
|
+
const model = brain.model ?? undefined;
|
|
450
|
+
const session = o.session ?? cfg.defaults.session ?? 'acp';
|
|
421
451
|
const role = {
|
|
422
452
|
...fromOpts, // includes `isolation` when --isolation-file was given
|
|
423
453
|
name: o.name,
|
|
424
454
|
harness,
|
|
425
455
|
session,
|
|
426
456
|
identity: o.identity ?? o.name,
|
|
457
|
+
effort: fromOpts.effort,
|
|
427
458
|
model,
|
|
428
459
|
model_chain: resolveModelChain(model, fromOpts.model_chain ?? (inheritsModelDefaults
|
|
429
460
|
? cfg.defaults.model_chain
|
|
430
461
|
: undefined)),
|
|
431
|
-
harness_options:
|
|
462
|
+
harness_options: brain.harnessOptions,
|
|
432
463
|
permissions: resolvePermissions(cfg.defaults.permissions, fromOpts.permissions),
|
|
433
464
|
permissionsDeclared: fromOpts.permissions !== undefined || cfg.defaults.permissions !== undefined,
|
|
434
465
|
// Temp agents inherit the fleet-wide monitor defaults via the snapshot.
|
|
@@ -467,10 +498,7 @@ async function spawnTempInner(o, binPath, launch, tx, onStage) {
|
|
|
467
498
|
if (cfg.startStaggerMs > 0)
|
|
468
499
|
writeFileSync(join(dir, START_STAGGER_FILE), String(cfg.startStaggerMs));
|
|
469
500
|
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
|
|
501
|
+
// Run the supervisor independently. On a service-managed host the temp
|
|
474
502
|
// runner gets its own transient unit/job, so stopping the coordinator's unit
|
|
475
503
|
// cannot kill a live worker in the coordinator's cgroup.
|
|
476
504
|
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
|
*/
|
package/dist/supervisor/none.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Tmux, tmuxArgs } from '../tmux.js';
|
|
2
2
|
import { realExec, shq } from '../exec.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
|
*/
|
|
@@ -9,7 +9,7 @@ export function makeNoneBackend(exec = realExec) {
|
|
|
9
9
|
const tmux = new Tmux(exec);
|
|
10
10
|
return {
|
|
11
11
|
id: 'none',
|
|
12
|
-
async init() { return ['no supervisor:
|
|
12
|
+
async init() { return ['no service supervisor: direct runner containment only (no reboot survival)']; },
|
|
13
13
|
async install(name, binPath) {
|
|
14
14
|
const existed = await tmux.kill(name); // true when a session was there
|
|
15
15
|
await tmux.newSession(name, process.cwd(), `${shq(binPath)} _run ${shq(name)}`);
|
package/dist/tmux.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare const tmuxSocket: (session: string) => string;
|
|
|
20
20
|
* the shared default server and re-creates #32.
|
|
21
21
|
*/
|
|
22
22
|
export declare const tmuxArgs: (session: string, args: string[]) => string[];
|
|
23
|
-
/** Thin tmux wrapper
|
|
23
|
+
/** Thin tmux wrapper retained only for supervisor=none process containment. */
|
|
24
24
|
export declare class Tmux {
|
|
25
25
|
private exec;
|
|
26
26
|
constructor(exec?: Exec);
|
|
@@ -32,17 +32,4 @@ export declare class Tmux {
|
|
|
32
32
|
* program inside it exited on its own".
|
|
33
33
|
*/
|
|
34
34
|
kill(name: string): Promise<boolean>;
|
|
35
|
-
capture(name: string, lines?: number): Promise<string>;
|
|
36
|
-
/** Bounded ANSI/history seed for the browser terminal projection. */
|
|
37
|
-
captureHistory(name: string, lines?: number): Promise<string>;
|
|
38
|
-
panePid(name: string): Promise<number | null>;
|
|
39
|
-
/**
|
|
40
|
-
* List the live sessions among `names`, asking each server in turn.
|
|
41
|
-
* There is no fleet-wide `tmux ls` any more: a session per server means the
|
|
42
|
-
* caller says which sessions to ask about. A server that is not running
|
|
43
|
-
* answers non-zero and contributes nothing.
|
|
44
|
-
*/
|
|
45
|
-
list(names: readonly string[]): Promise<string>;
|
|
46
|
-
sendText(name: string, text: string): Promise<void>;
|
|
47
|
-
sendKey(name: string, key: string): Promise<void>;
|
|
48
35
|
}
|
package/dist/tmux.js
CHANGED
|
@@ -20,7 +20,7 @@ export const tmuxSocket = (session) => `${TMUX_SOCKET_PREFIX}${session}`;
|
|
|
20
20
|
* the shared default server and re-creates #32.
|
|
21
21
|
*/
|
|
22
22
|
export const tmuxArgs = (session, args) => ['-L', tmuxSocket(session), ...args];
|
|
23
|
-
/** Thin tmux wrapper
|
|
23
|
+
/** Thin tmux wrapper retained only for supervisor=none process containment. */
|
|
24
24
|
export class Tmux {
|
|
25
25
|
exec;
|
|
26
26
|
constructor(exec = realExec) {
|
|
@@ -42,54 +42,4 @@ export class Tmux {
|
|
|
42
42
|
async kill(name) {
|
|
43
43
|
return (await this.exec('tmux', tmuxArgs(name, ['kill-session', '-t', name]))).code === 0;
|
|
44
44
|
}
|
|
45
|
-
async capture(name, lines = 40) {
|
|
46
|
-
const r = await this.exec('tmux', tmuxArgs(name, ['capture-pane', '-t', name, '-p']));
|
|
47
|
-
if (r.code !== 0)
|
|
48
|
-
throw new Error(`tmux capture-pane '${name}' failed: ${r.stderr.trim()}`);
|
|
49
|
-
const all = r.stdout.replace(/\n+$/, '').split('\n');
|
|
50
|
-
return all.slice(-lines).join('\n');
|
|
51
|
-
}
|
|
52
|
-
/** Bounded ANSI/history seed for the browser terminal projection. */
|
|
53
|
-
async captureHistory(name, lines = 5_000) {
|
|
54
|
-
const bounded = Math.min(Math.max(Math.trunc(lines), 1), 20_000);
|
|
55
|
-
const r = await this.exec('tmux', tmuxArgs(name, ['capture-pane', '-t', name, '-p', '-e', '-J', '-S', `-${bounded}`]));
|
|
56
|
-
if (r.code !== 0)
|
|
57
|
-
throw new Error(`tmux capture-pane '${name}' failed: ${r.stderr.trim()}`);
|
|
58
|
-
return Buffer.from(r.stdout).subarray(0, 4 * 1024 * 1024).toString();
|
|
59
|
-
}
|
|
60
|
-
async panePid(name) {
|
|
61
|
-
const r = await this.exec('tmux', tmuxArgs(name, ['list-panes', '-t', name, '-F', '#{pane_pid}']));
|
|
62
|
-
if (r.code !== 0)
|
|
63
|
-
return null;
|
|
64
|
-
const pid = parseInt(r.stdout.trim().split('\n')[0], 10);
|
|
65
|
-
return Number.isFinite(pid) ? pid : null;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* List the live sessions among `names`, asking each server in turn.
|
|
69
|
-
* There is no fleet-wide `tmux ls` any more: a session per server means the
|
|
70
|
-
* caller says which sessions to ask about. A server that is not running
|
|
71
|
-
* answers non-zero and contributes nothing.
|
|
72
|
-
*/
|
|
73
|
-
async list(names) {
|
|
74
|
-
const lines = [];
|
|
75
|
-
for (const name of names) {
|
|
76
|
-
const r = await this.exec('tmux', tmuxArgs(name, ['ls']));
|
|
77
|
-
if (r.code === 0 && r.stdout.trim())
|
|
78
|
-
lines.push(r.stdout.trimEnd());
|
|
79
|
-
}
|
|
80
|
-
return lines.join('\n');
|
|
81
|
-
}
|
|
82
|
-
async sendText(name, text) {
|
|
83
|
-
let r = await this.exec('tmux', tmuxArgs(name, ['send-keys', '-t', name, '-l', text]));
|
|
84
|
-
if (r.code !== 0)
|
|
85
|
-
throw new Error(`tmux send-keys '${name}' failed: ${r.stderr.trim()}`);
|
|
86
|
-
r = await this.exec('tmux', tmuxArgs(name, ['send-keys', '-t', name, 'Enter']));
|
|
87
|
-
if (r.code !== 0)
|
|
88
|
-
throw new Error(`tmux send-keys Enter '${name}' failed: ${r.stderr.trim()}`);
|
|
89
|
-
}
|
|
90
|
-
async sendKey(name, key) {
|
|
91
|
-
const r = await this.exec('tmux', tmuxArgs(name, ['send-keys', '-t', name, key]));
|
|
92
|
-
if (r.code !== 0)
|
|
93
|
-
throw new Error(`tmux send-keys '${name}' failed: ${r.stderr.trim()}`);
|
|
94
|
-
}
|
|
95
45
|
}
|
package/dist/watchdog/config.js
CHANGED
|
@@ -31,7 +31,7 @@ export function resolveWatchdogs(baseDoc, baseFile, roles, vars, defaults) {
|
|
|
31
31
|
const roleIdentities = new Set(roles.map(r => r.identity));
|
|
32
32
|
// Tracks identity -> owning watchdog name across entries:
|
|
33
33
|
// two watchdogs declaring the same identity would otherwise share a temp
|
|
34
|
-
// dir,
|
|
34
|
+
// dir, agent session, and run lock with no error until they collide at
|
|
35
35
|
// runtime.
|
|
36
36
|
const watchdogIdentities = new Map();
|
|
37
37
|
const out = [];
|
|
@@ -61,9 +61,11 @@ export function resolveWatchdogs(baseDoc, baseFile, roles, vars, defaults) {
|
|
|
61
61
|
throw new ConfigError(`${where}: identity '${identity}' collides with watchdog '${collidingWatchdog}'`);
|
|
62
62
|
watchdogIdentities.set(identity, name);
|
|
63
63
|
const harness = w.harness ?? defaults.harness ?? 'claude-code';
|
|
64
|
-
const sessionRaw = w.session ?? defaults.session ?? '
|
|
65
|
-
if (sessionRaw
|
|
66
|
-
throw new ConfigError(`${where}: session
|
|
64
|
+
const sessionRaw = w.session ?? defaults.session ?? 'acp';
|
|
65
|
+
if (sessionRaw === 'tmux')
|
|
66
|
+
throw new ConfigError(`${where}: session 'tmux' is no longer supported; use session: acp`);
|
|
67
|
+
if (sessionRaw !== 'acp')
|
|
68
|
+
throw new ConfigError(`${where}: session must be 'acp'`);
|
|
67
69
|
if (w.prompt_file !== undefined) {
|
|
68
70
|
if (typeof w.prompt_file !== 'string' || !isAbsolute(w.prompt_file))
|
|
69
71
|
throw new ConfigError(`${where}: prompt_file must be an absolute path`);
|
package/dist/watchdog/run.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { closeSync, existsSync, openSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import { spawn as spawnChild
|
|
2
|
+
import { spawn as spawnChild } from 'node:child_process';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
4
|
import { stringify } from 'yaml';
|
|
6
5
|
import { errorReport, normalizeWatchdogReport, validateWatchdogReport } from './report.js';
|
|
7
6
|
import { acquireRunLock, formatRunId, pruneReports, releaseRunLock, writeReport } from './store.js';
|
|
@@ -15,8 +14,6 @@ import { loadConfig, resolveMonitorConfig, resolveWorklogPolicy, ROLE_NAME_RE, }
|
|
|
15
14
|
import { getAdapter } from '../harness/registry.js';
|
|
16
15
|
import { redactLogLine } from '../application/log-service.js';
|
|
17
16
|
import { controlRequest, controlSocketPath } from '../session/control.js';
|
|
18
|
-
import { Tmux } from '../tmux.js';
|
|
19
|
-
const execFileAsync = promisify(execFile);
|
|
20
17
|
/** How often the deadline loop polls for a completed report or a dead child. */
|
|
21
18
|
const POLL_MS = 1000;
|
|
22
19
|
/** A `report.json` this old, with no further writes, is treated as finished. */
|
|
@@ -56,7 +53,6 @@ async function killIfNeeded(reason, child, roleName, sleep) {
|
|
|
56
53
|
if (reason === 'stable')
|
|
57
54
|
await sleep(HARVEST_GRACE_MS);
|
|
58
55
|
child.kill();
|
|
59
|
-
await killTmuxSession(roleName);
|
|
60
56
|
}
|
|
61
57
|
}
|
|
62
58
|
function defaultLaunchChild(binPath, roleName, runDir) {
|
|
@@ -77,13 +73,6 @@ function defaultLaunchChild(binPath, roleName, runDir) {
|
|
|
77
73
|
}),
|
|
78
74
|
};
|
|
79
75
|
}
|
|
80
|
-
/** Best-effort: the tmux session outlives the supervisor child (`runOnce` created it). */
|
|
81
|
-
async function killTmuxSession(roleName) {
|
|
82
|
-
try {
|
|
83
|
-
await execFileAsync('tmux', ['kill-session', '-t', roleName]);
|
|
84
|
-
}
|
|
85
|
-
catch { /* best effort */ }
|
|
86
|
-
}
|
|
87
76
|
/** Last 4096 chars of run.log, redacted — attached to error reports as diagnostic tail. */
|
|
88
77
|
function readTail(runDir) {
|
|
89
78
|
try {
|
|
@@ -121,19 +110,15 @@ async function discoverLiveTemporaryRoles() {
|
|
|
121
110
|
catch {
|
|
122
111
|
return [];
|
|
123
112
|
}
|
|
124
|
-
const tmux = new Tmux();
|
|
125
113
|
const live = await Promise.all(names.map(async (name) => {
|
|
126
114
|
const dir = agentDir(name, true);
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
tmux.has(name).catch(() => false),
|
|
135
|
-
]);
|
|
136
|
-
return acpAlive || tmuxAlive ? name : undefined;
|
|
115
|
+
const alive = existsSync(controlSocketPath(dir))
|
|
116
|
+
? await controlRequest(dir, { command: 'status' }, 2_000)
|
|
117
|
+
.then(response => response.ok
|
|
118
|
+
&& response.result?.alive === true)
|
|
119
|
+
.catch(() => false)
|
|
120
|
+
: false;
|
|
121
|
+
return alive ? name : undefined;
|
|
137
122
|
}));
|
|
138
123
|
return live.filter((name) => name !== undefined);
|
|
139
124
|
}
|
package/dist/web/auth.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { PrereqReport } from '../harness/types.js';
|
|
2
2
|
export declare const REDACTED_ENV_VALUE = "__OURS_FLEET_SECRET_REDACTED__";
|
|
3
|
-
export
|
|
3
|
+
export interface EditableFleetModel {
|
|
4
|
+
manifest: Record<string, unknown>;
|
|
5
|
+
agents: Record<string, Record<string, unknown>>;
|
|
6
|
+
}
|
|
4
7
|
export interface ConfigReadResult {
|
|
5
8
|
path: string;
|
|
6
|
-
exists:
|
|
7
|
-
firstRun:
|
|
9
|
+
exists: true;
|
|
10
|
+
firstRun: false;
|
|
8
11
|
revision: string;
|
|
9
12
|
model: EditableFleetModel;
|
|
10
13
|
redactions: string[];
|
|
@@ -33,15 +36,15 @@ export interface ConfigWriteResult extends ConfigPreviewResult {
|
|
|
33
36
|
export interface FleetConfigServiceOptions {
|
|
34
37
|
configPath?: string;
|
|
35
38
|
preflight?(configPath: string): Promise<PrereqReport>;
|
|
39
|
+
/** Fault-injection seam; called under lock immediately before each mutation. */
|
|
40
|
+
beforeMutation?(relativePath: string, index: number): void;
|
|
36
41
|
}
|
|
37
42
|
export declare class FleetConfigService {
|
|
38
43
|
readonly path: string;
|
|
39
44
|
private readonly preflight;
|
|
45
|
+
private readonly beforeMutation?;
|
|
40
46
|
constructor(options?: FleetConfigServiceOptions);
|
|
41
47
|
read(): ConfigReadResult;
|
|
42
|
-
preview(baseRevision: string,
|
|
43
|
-
write(baseRevision: string,
|
|
44
|
-
private readSource;
|
|
45
|
-
private assertRevision;
|
|
46
|
-
private validateCandidate;
|
|
48
|
+
preview(baseRevision: string, input: unknown): Promise<ConfigPreviewResult>;
|
|
49
|
+
write(baseRevision: string, input: unknown): Promise<ConfigWriteResult>;
|
|
47
50
|
}
|