@ours.network/fleet 1.1.0-nightly.9 → 1.1.1
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 +285 -46
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +7 -4
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +4 -4
- package/dist/application/role-creation-service.js +8 -1
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +92 -39
- package/dist/application/task-room-service.js +562 -289
- package/dist/briefing.js +79 -22
- package/dist/build-info.json +5 -5
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +129 -47
- package/dist/config.d.ts +42 -3
- package/dist/config.js +202 -29
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +195 -41
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +22 -1
- package/dist/fleet-command-audit.js +355 -50
- package/dist/fleet-proxy.d.ts +5 -2
- package/dist/harness/claude-code-session.js +5 -1
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +9 -2
- package/dist/harness/codex-session.js +21 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +104 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +1 -1
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +631 -0
- package/dist/lifecycle-summary.d.ts +134 -0
- package/dist/lifecycle-summary.js +179 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +38 -5
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +19 -3
- package/dist/owner-channel/channel.js +301 -120
- package/dist/owner-channel/commands.d.ts +28 -6
- package/dist/owner-channel/commands.js +109 -27
- package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
- package/dist/owner-channel/lifecycle-outbox.js +87 -0
- package/dist/owner-channel/ours-client.d.ts +19 -0
- package/dist/owner-channel/ours-client.js +3 -0
- package/dist/permissions.js +1 -1
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +450 -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.js +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +361 -285
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +23 -7
- package/dist/rooms-tasks/config.js +16 -25
- package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
- package/dist/rooms-tasks/cowork-adapter.js +13 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +8 -0
- package/dist/rooms-tasks/external-worker.js +38 -0
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +181 -0
- package/dist/rooms-tasks/member-overrides.d.ts +30 -0
- package/dist/rooms-tasks/member-overrides.js +204 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +9 -2
- package/dist/rooms-tasks/provision.js +146 -83
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-room-name.d.ts +12 -0
- package/dist/rooms-tasks/task-room-name.js +61 -0
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -1
- package/dist/rooms-tasks/templates.js +34 -3
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +10 -4
- package/dist/rooms-tasks/types.d.ts +92 -7
- package/dist/rooms-tasks/types.js +14 -1
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +97 -47
- package/dist/session/acp.d.ts +27 -1
- package/dist/session/acp.js +307 -17
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +1 -1
- package/dist/session/control.js +17 -1
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/stall-watchdog.d.ts +60 -0
- package/dist/session/stall-watchdog.js +210 -0
- package/dist/session/types.d.ts +29 -8
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +9 -0
- package/dist/spawn.js +77 -9
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +25 -7
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
- package/dist/web-app/index.html +1 -1
- package/package.json +4 -4
- package/presets/brain-catalog.json +17 -0
- package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
- package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
- package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +37 -0
- package/presets/fleet/roles/Critic.yaml +23 -1
- package/presets/fleet/roles/Developer.yaml +27 -5
- package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
- package/presets/fleet/room_templates/pair.yaml +9 -7
- package/presets/fleet/room_templates/single.yaml +5 -4
- package/presets/fleet/room_templates/team.yaml +12 -8
- package/presets/manifest.json +2 -1
- package/presets/fleet/agents/Architect.yaml +0 -3
- package/presets/fleet/agents/Secretary.yaml +0 -3
- package/presets/fleet/agents/Tester.yaml +0 -3
- package/presets/fleet/roles/Agent.yaml +0 -6
- package/presets/fleet/roles/Architect.yaml +0 -6
- package/presets/fleet/roles/Secretary.yaml +0 -6
- package/presets/fleet/roles/Tester.yaml +0 -6
package/dist/spawn.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn as spawnChild } from 'node:child_process';
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { parse, stringify } from 'yaml';
|
|
5
5
|
import { agentDir, defaultConfigPath } from './paths.js';
|
|
@@ -22,9 +22,12 @@ export function agentDefinitionFromSpawn(o) {
|
|
|
22
22
|
if (o.agentDefinition) {
|
|
23
23
|
if (o.brain !== undefined || o.role !== undefined || o.cwd !== undefined
|
|
24
24
|
|| o.coordinator !== undefined || o.approval !== undefined || o.filesystem !== undefined
|
|
25
|
-
|| o.unattended !== undefined || o.isolationFile !== undefined || o.monitorConfig !== undefined
|
|
25
|
+
|| o.unattended !== undefined || o.isolationFile !== undefined || o.monitorConfig !== undefined
|
|
26
|
+
|| o.loopsFile !== undefined)
|
|
26
27
|
throw new Error('canonical agentDefinition conflicts with separate Agent fields');
|
|
27
28
|
const definition = structuredClone(o.agentDefinition);
|
|
29
|
+
if (o.noLoops === true && definition.loops !== undefined)
|
|
30
|
+
throw new Error('canonical agentDefinition loops conflict with explicit no-loops policy');
|
|
28
31
|
if (o.identity)
|
|
29
32
|
definition.identity = o.identity;
|
|
30
33
|
return definition;
|
|
@@ -46,12 +49,21 @@ export function agentDefinitionFromSpawn(o) {
|
|
|
46
49
|
...(permissions ? { permissions } : {}),
|
|
47
50
|
...(o.isolationFile ? { isolation: readIsolationFile(o.isolationFile) } : {}),
|
|
48
51
|
...(o.monitorConfig ? { monitor: structuredClone(o.monitorConfig) } : {}),
|
|
52
|
+
...(o.loopsFile ? { loops: readLoopsFile(o.loopsFile) } : {}),
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
function resolvedSpawn(o) {
|
|
52
56
|
const definition = agentDefinitionFromSpawn(o);
|
|
53
|
-
const cfg = loadConfig(o.configPath, {
|
|
54
|
-
|
|
57
|
+
const cfg = loadConfig(o.configPath, {
|
|
58
|
+
additionalAgent: { id: o.name, definition, temporary: o.temp === true },
|
|
59
|
+
});
|
|
60
|
+
const role = findRole(cfg, o.name);
|
|
61
|
+
if (o.temp)
|
|
62
|
+
role.temporaryLoopSource = o.loopSource
|
|
63
|
+
?? (role.temporaryLoops?.length ? 'agent-template' : 'omitted');
|
|
64
|
+
if (o.noLoops === true)
|
|
65
|
+
role.temporaryLoops = [];
|
|
66
|
+
return { definition, role };
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
69
|
* Read and validate an `--isolation-file`. The file is the existing
|
|
@@ -78,7 +90,37 @@ export function readIsolationFile(path) {
|
|
|
78
90
|
throw new Error(`--isolation-file ${path}: ${problems.join('; ')}`);
|
|
79
91
|
return cfg;
|
|
80
92
|
}
|
|
93
|
+
/** Read a private canonical temporary-loop override before any creation side effect. */
|
|
94
|
+
export function readLoopsFile(path) {
|
|
95
|
+
const stat = lstatSync(path);
|
|
96
|
+
const uid = process.getuid?.();
|
|
97
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_000_000
|
|
98
|
+
|| (uid !== undefined && stat.uid !== uid) || (stat.mode & 0o777) !== 0o600)
|
|
99
|
+
throw new Error(`${path}: loops file must be an owner-only regular file no larger than 1 MB`);
|
|
100
|
+
let raw;
|
|
101
|
+
try {
|
|
102
|
+
raw = parse(readFileSync(path, 'utf8'));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw new Error(`--loops-file ${path}: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)
|
|
108
|
+
|| !Object.hasOwn(raw, 'loops')
|
|
109
|
+
|| Object.keys(raw).some(key => key !== 'loops'))
|
|
110
|
+
throw new Error(`${path}: expected exactly a top-level loops: mapping`);
|
|
111
|
+
const loops = raw.loops;
|
|
112
|
+
if (!loops || typeof loops !== 'object' || Array.isArray(loops)
|
|
113
|
+
|| !Object.keys(loops).length)
|
|
114
|
+
throw new Error(`${path}: loops must be a non-empty mapping; use --no-loops to disable loops`);
|
|
115
|
+
return structuredClone(loops);
|
|
116
|
+
}
|
|
81
117
|
export function validateSpawnOpts(o) {
|
|
118
|
+
if (o.loopsFile && o.noLoops === true)
|
|
119
|
+
throw new Error('--loops-file and --no-loops are mutually exclusive');
|
|
120
|
+
if ((o.loopsFile || o.noLoops === true || o.agentDefinition?.loops !== undefined) && !o.temp)
|
|
121
|
+
throw new Error('temporary Agent loops require --temp');
|
|
122
|
+
if (o.loopsFile)
|
|
123
|
+
readLoopsFile(o.loopsFile);
|
|
82
124
|
if (o.approval && !['ask', 'auto', 'allow', 'deny'].includes(o.approval))
|
|
83
125
|
throw new Error(`invalid --approval '${o.approval}'; allowed: ask, auto, allow (deprecated alias: deny)`);
|
|
84
126
|
if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
|
|
@@ -102,7 +144,15 @@ export function validateSpawnOpts(o) {
|
|
|
102
144
|
* by the reservation rather than by this function.
|
|
103
145
|
*/
|
|
104
146
|
function assertNameFree(o) {
|
|
105
|
-
|
|
147
|
+
let cfg;
|
|
148
|
+
try {
|
|
149
|
+
cfg = loadConfig(o.configPath);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof Error && error.message.includes(`resolve to duplicate identity '${effectiveIdentity(o)}'`))
|
|
153
|
+
throw new Error(`ours identity '${effectiveIdentity(o)}' is already taken or being created right now`);
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
106
156
|
if (cfg.roles.some(r => r.name === o.name))
|
|
107
157
|
throw new Error(`role '${o.name}' already exists (${cfg.roles.find(r => r.name === o.name).sourceFile})`);
|
|
108
158
|
if (existsSync(agentDir(o.name)) || existsSync(agentDir(o.name, true)))
|
|
@@ -141,7 +191,7 @@ export function spawnDryRun(o) {
|
|
|
141
191
|
* `env`, `bio`, `persona` and `harness_options` are deliberately absent: the
|
|
142
192
|
* record exists to be read, and must not become a place credentials collect.
|
|
143
193
|
*/
|
|
144
|
-
function provenanceSettings(o, defaults) {
|
|
194
|
+
function provenanceSettings(o, defaults, resolvedLoops = []) {
|
|
145
195
|
const perms = (defaults.permissions ?? {});
|
|
146
196
|
const callerDefaults = new Set(o.inheritedFromCaller ?? []);
|
|
147
197
|
const tagged = (key, entry) => callerDefaults.has(key) ? { ...entry, source: 'caller-role' } : entry;
|
|
@@ -161,6 +211,16 @@ function provenanceSettings(o, defaults) {
|
|
|
161
211
|
? { value: 'declared via --isolation-file', source: 'cli' }
|
|
162
212
|
: { value: defaults.isolation ? 'from fleet defaults' : undefined, source: defaults.isolation ? 'fleet-default' : 'built-in' },
|
|
163
213
|
monitor: tagged('monitorConfig', provenanceOf(o.monitorConfig, defaults.monitor, { mode: 'fleet' })),
|
|
214
|
+
loops: {
|
|
215
|
+
value: o.noLoops ? { enabled: false, loops: [] }
|
|
216
|
+
: resolvedLoops.length ? resolvedLoops.map(loop => ({
|
|
217
|
+
name: loop.name, enabled: loop.enabled, intervalMs: loop.intervalMs,
|
|
218
|
+
initialDelayMs: loop.initialDelayMs, jitterMs: loop.jitterMs,
|
|
219
|
+
prompt: { bytes: loop.promptBytes, sha256: loop.promptHash },
|
|
220
|
+
})) : 'omitted (legacy no temporary loops)',
|
|
221
|
+
source: o.loopSource === 'agent-template' ? 'agent-template'
|
|
222
|
+
: o.loopsFile || o.noLoops || o.loopSource === 'cli' ? 'cli' : 'built-in',
|
|
223
|
+
},
|
|
164
224
|
};
|
|
165
225
|
}
|
|
166
226
|
/** Permanent spawn: persist one bare Agent document under the selected v2 root. */
|
|
@@ -168,6 +228,9 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
168
228
|
validateSpawnOpts(o);
|
|
169
229
|
if (o.isolationFile)
|
|
170
230
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
231
|
+
// Preserve the detailed owner/source diagnostic for an existing identity.
|
|
232
|
+
// The check is repeated inside the reservation boundary to close races.
|
|
233
|
+
assertNameFree(o);
|
|
171
234
|
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
172
235
|
// Reserve name and identity together before anything is written or started.
|
|
173
236
|
// A loser of the race creates no config, state, or service.
|
|
@@ -221,7 +284,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
221
284
|
// launch still records how it was asked for.
|
|
222
285
|
const provenance = buildProvenance({
|
|
223
286
|
role: o.name, lifetime: 'permanent', fleetVersion: VERSION,
|
|
224
|
-
settings: provenanceSettings(o, cfg.defaults),
|
|
287
|
+
settings: provenanceSettings(o, cfg.defaults, prepared.role.temporaryLoops),
|
|
225
288
|
surface: o.surface, creationActionId: o.creationActionId, callerRole: o.callerRole,
|
|
226
289
|
});
|
|
227
290
|
mkdirSync(agentDir(o.name), { recursive: true });
|
|
@@ -282,14 +345,19 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
282
345
|
}
|
|
283
346
|
async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
284
347
|
const cfg = loadConfig(o.configPath);
|
|
348
|
+
const { temporaryLoops, ...launchRole } = preparedRole;
|
|
285
349
|
const role = {
|
|
286
|
-
...
|
|
350
|
+
...launchRole, sourceFile: '(temp)',
|
|
351
|
+
...(o.noLoops === true ? { loops: [] }
|
|
352
|
+
: temporaryLoops?.length ? { loops: temporaryLoops } : { loops: undefined }),
|
|
353
|
+
temporaryLoopSource: o.loopSource ?? (temporaryLoops?.length ? 'agent-template' : 'omitted'),
|
|
354
|
+
roomMemberStartup: o.roomMemberStartup,
|
|
287
355
|
};
|
|
288
356
|
onStage?.('writing_role');
|
|
289
357
|
const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
|
|
290
358
|
const provenance = buildProvenance({
|
|
291
359
|
role: o.name, lifetime: 'temporary', fleetVersion: VERSION,
|
|
292
|
-
settings: provenanceSettings(o, cfg.defaults),
|
|
360
|
+
settings: provenanceSettings(o, cfg.defaults, preparedRole.temporaryLoops),
|
|
293
361
|
surface: o.surface, creationActionId: o.creationActionId, callerRole: o.callerRole,
|
|
294
362
|
});
|
|
295
363
|
writeProvenance(dir, provenance);
|
|
@@ -3,6 +3,7 @@ export declare const REDACTED_ENV_VALUE = "__OURS_FLEET_SECRET_REDACTED__";
|
|
|
3
3
|
export interface EditableFleetModel {
|
|
4
4
|
manifest: Record<string, unknown>;
|
|
5
5
|
agents: Record<string, Record<string, unknown>>;
|
|
6
|
+
agent_templates: Record<string, Record<string, unknown>>;
|
|
6
7
|
}
|
|
7
8
|
export interface ConfigReadResult {
|
|
8
9
|
path: string;
|
|
@@ -12,7 +12,7 @@ import { isSensitiveConfigKey } from '../sensitive-config.js';
|
|
|
12
12
|
export const REDACTED_ENV_VALUE = '__OURS_FLEET_SECRET_REDACTED__';
|
|
13
13
|
const MAX_CONFIG_BYTES = 256 * 1024;
|
|
14
14
|
const DIFF_CONTEXT_LINES = 3;
|
|
15
|
-
const KINDS = ['agents', 'roles', 'brains', 'room_templates'];
|
|
15
|
+
const KINDS = ['agents', 'agent_templates', 'roles', 'brains', 'room_templates'];
|
|
16
16
|
const emptyReport = () => ({ ok: true, checks: [] });
|
|
17
17
|
export class FleetConfigService {
|
|
18
18
|
path;
|
|
@@ -176,7 +176,7 @@ function readSources(manifest) {
|
|
|
176
176
|
}
|
|
177
177
|
trustedDirectory(dir);
|
|
178
178
|
for (const name of readdirSync(dir).sort()) {
|
|
179
|
-
if ((kind === 'agents' || kind === 'room_templates')
|
|
179
|
+
if ((kind === 'agents' || kind === 'agent_templates' || kind === 'room_templates')
|
|
180
180
|
&& !['.yaml', '.yml'].includes(extname(name).toLowerCase()))
|
|
181
181
|
continue;
|
|
182
182
|
documents.set(join(kind, name), trustedFile(join(dir, name)));
|
|
@@ -188,20 +188,26 @@ function modelFrom(sources) {
|
|
|
188
188
|
const manifestKey = basename(sources.manifest);
|
|
189
189
|
const manifest = parseFleetDocument(sources.manifest, sources.documents.get(manifestKey), 'strict').value;
|
|
190
190
|
const agents = {};
|
|
191
|
+
const agent_templates = {};
|
|
191
192
|
for (const [rel, source] of sources.documents)
|
|
192
193
|
if (rel.startsWith(`agents/`)) {
|
|
193
194
|
const id = basename(rel, extname(rel));
|
|
194
195
|
agents[id] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
+
for (const [rel, source] of sources.documents)
|
|
198
|
+
if (rel.startsWith(`agent_templates/`)) {
|
|
199
|
+
const id = basename(rel, extname(rel));
|
|
200
|
+
agent_templates[id] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
|
|
201
|
+
}
|
|
202
|
+
return { manifest, agents, agent_templates };
|
|
197
203
|
}
|
|
198
204
|
function assertModel(value) {
|
|
199
205
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
200
206
|
throw new FleetError('invalid_request', 'model must be an object');
|
|
201
207
|
const input = value;
|
|
202
|
-
if (Object.keys(input).sort().join(',') !== 'agents,manifest')
|
|
203
|
-
throw new FleetError('invalid_request', 'model allows exactly manifest and
|
|
204
|
-
for (const key of ['manifest', 'agents'])
|
|
208
|
+
if (Object.keys(input).sort().join(',') !== 'agent_templates,agents,manifest')
|
|
209
|
+
throw new FleetError('invalid_request', 'model allows exactly manifest, agents, and agent_templates');
|
|
210
|
+
for (const key of ['manifest', 'agents', 'agent_templates'])
|
|
205
211
|
if (!input[key] || typeof input[key] !== 'object' || Array.isArray(input[key]))
|
|
206
212
|
throw new FleetError('invalid_request', `${key} must be an object`);
|
|
207
213
|
for (const [id, doc] of Object.entries(input.agents)) {
|
|
@@ -210,6 +216,12 @@ function assertModel(value) {
|
|
|
210
216
|
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
211
217
|
throw new FleetError('invalid_request', `Agent '${id}' must be an object`);
|
|
212
218
|
}
|
|
219
|
+
for (const [id, doc] of Object.entries(input.agent_templates)) {
|
|
220
|
+
if (!ROLE_NAME_RE.test(id))
|
|
221
|
+
throw new FleetError('invalid_request', `invalid Agent Template ID '${id}'`);
|
|
222
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
223
|
+
throw new FleetError('invalid_request', `Agent Template '${id}' must be an object`);
|
|
224
|
+
}
|
|
213
225
|
return structuredClone(input);
|
|
214
226
|
}
|
|
215
227
|
function renderProposal(current, model) {
|
|
@@ -222,6 +234,12 @@ function renderProposal(current, model) {
|
|
|
222
234
|
next.set(oldRel ?? join('agents', `${id}.yaml`), oldRel
|
|
223
235
|
? render(current.documents.get(oldRel), doc) : stringify(doc));
|
|
224
236
|
}
|
|
237
|
+
for (const [id, doc] of Object.entries(model.agent_templates).sort(([a], [b]) => a.localeCompare(b))) {
|
|
238
|
+
const oldRel = [...current.documents.keys()].find(rel => rel.startsWith('agent_templates/')
|
|
239
|
+
&& basename(rel, extname(rel)) === id);
|
|
240
|
+
next.set(oldRel ?? join('agent_templates', `${id}.yaml`), oldRel
|
|
241
|
+
? render(current.documents.get(oldRel), doc) : stringify(doc));
|
|
242
|
+
}
|
|
225
243
|
for (const [rel, source] of current.documents)
|
|
226
244
|
if (rel.startsWith('roles/') || rel.startsWith('brains/') || rel.startsWith('room_templates/'))
|
|
227
245
|
next.set(rel, source);
|
|
@@ -425,7 +443,7 @@ function sourceSensitiveValues(sources) {
|
|
|
425
443
|
continue;
|
|
426
444
|
try {
|
|
427
445
|
const parsed = parse(source);
|
|
428
|
-
values.push(...sensitiveValues({ manifest: {}, agents: { source: parsed } }));
|
|
446
|
+
values.push(...sensitiveValues({ manifest: {}, agents: { source: parsed }, agent_templates: {} }));
|
|
429
447
|
}
|
|
430
448
|
catch { /* the authoritative parser reports malformed YAML */ }
|
|
431
449
|
}
|
package/dist/web/runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import { loadConfig } from '../config.js';
|
|
5
5
|
import { RoleRepository } from '../application/role-repository.js';
|
|
6
6
|
import { FleetQueryService } from '../application/fleet-query-service.js';
|
|
7
|
-
import {
|
|
7
|
+
import { RoleSessionControlAdapter } from '../application/session-control.js';
|
|
8
8
|
import { StructuredLogService } from '../application/log-service.js';
|
|
9
9
|
import { RoleCommandService } from '../application/role-command-service.js';
|
|
10
10
|
import { RoleCreationService } from '../application/role-creation-service.js';
|
|
@@ -64,18 +64,22 @@ export async function startWebConsole(options) {
|
|
|
64
64
|
const repository = new RoleRepository({
|
|
65
65
|
configPath: options.configPath,
|
|
66
66
|
probeBackend: async (name) => {
|
|
67
|
-
let
|
|
67
|
+
let backend;
|
|
68
68
|
const permanent = resolve(home(), '.ours-fleet', 'agents', name);
|
|
69
69
|
const temporary = resolve(home(), '.ours-fleet', 'tmp', name);
|
|
70
70
|
for (const dir of [permanent, temporary]) {
|
|
71
71
|
if (!existsSync(controlSocketPath(dir)))
|
|
72
72
|
continue;
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
const response = await controlRequest(dir, { command: 'snapshot' }, 500);
|
|
75
|
+
const snapshot = response.result;
|
|
76
|
+
if (response.ok && (snapshot?.backend === 'acp'
|
|
77
|
+
|| snapshot?.backend === 'codex-app-server'))
|
|
78
|
+
backend = snapshot.backend;
|
|
75
79
|
}
|
|
76
80
|
catch { /* stale socket is evidence, not reachability */ }
|
|
77
81
|
}
|
|
78
|
-
return {
|
|
82
|
+
return { backend };
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
85
|
const events = new FleetEventBus();
|
|
@@ -161,11 +165,12 @@ export async function startWebConsole(options) {
|
|
|
161
165
|
const role = await repository.get(roleId);
|
|
162
166
|
if (!role)
|
|
163
167
|
throw new FleetError('role_not_found', `no such role '${roleId}'`);
|
|
164
|
-
if (role.configuredBackend === 'acp'
|
|
168
|
+
if (role.configuredBackend === 'acp'
|
|
169
|
+
|| role.configuredBackend === 'codex-app-server') {
|
|
165
170
|
const dir = repository.stateDir(role);
|
|
166
171
|
if (!dir)
|
|
167
172
|
throw new FleetError('control_unavailable', 'role state directory is unavailable');
|
|
168
|
-
return new
|
|
173
|
+
return new RoleSessionControlAdapter(dir);
|
|
169
174
|
}
|
|
170
175
|
throw new FleetError('capability_unavailable', 'role session backend is unavailable');
|
|
171
176
|
},
|
package/dist/web/server.js
CHANGED
|
@@ -183,12 +183,39 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
183
183
|
throw new FleetError('invalid_request', 'invalid task state filter');
|
|
184
184
|
if (query.groupByList !== undefined && !['true', 'false'].includes(query.groupByList))
|
|
185
185
|
throw new FleetError('invalid_request', 'groupByList must be true or false');
|
|
186
|
+
if (query.includeDeleting !== undefined && !['true', 'false'].includes(query.includeDeleting))
|
|
187
|
+
throw new FleetError('invalid_request', 'includeDeleting must be true or false');
|
|
186
188
|
const state = query.state && query.state !== 'all' ? query.state : undefined;
|
|
187
|
-
const filter = { ...(state ? { state } : {}), ...(query.list ? { list: query.list } : {})
|
|
189
|
+
const filter = { ...(state ? { state } : {}), ...(query.list ? { list: query.list } : {}),
|
|
190
|
+
...(query.includeDeleting === 'true' ? { includeDeleting: true } : {}) };
|
|
188
191
|
return taskApi(() => query.groupByList === 'true'
|
|
189
192
|
? { groups: requireTaskRooms().groupedTasks(filter) }
|
|
190
193
|
: { tasks: requireTaskRooms().listTasks(filter) });
|
|
191
194
|
});
|
|
195
|
+
app.delete('/api/v1/tasks/:id', async (request, reply) => {
|
|
196
|
+
auth.authenticate(request, true);
|
|
197
|
+
const taskId = request.params.id;
|
|
198
|
+
const confirm = request.query.confirm;
|
|
199
|
+
// Exact-ID-twice confirmation is validated before existence or idempotency.
|
|
200
|
+
if (confirm !== taskId)
|
|
201
|
+
throw new FleetError('invalid_request', 'confirm must exactly repeat the task id');
|
|
202
|
+
const accepted = await taskApi(() => requireTaskRooms().requestTaskDeletion({
|
|
203
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId,
|
|
204
|
+
}));
|
|
205
|
+
if (accepted.status === 'already_absent')
|
|
206
|
+
return { task_id: taskId, deleted: false, already_absent: true };
|
|
207
|
+
// Cleanup runs in an external worker outside the request lifecycle; the
|
|
208
|
+
// request waits boundedly and reports pending — never fabricated success.
|
|
209
|
+
const outcome = await requireTaskRooms().launchTaskDeletionWorker({ taskId, waitMs: 5_000 });
|
|
210
|
+
if (outcome.deleted)
|
|
211
|
+
return { task_id: taskId, deleted: true };
|
|
212
|
+
reply.code(202);
|
|
213
|
+
return {
|
|
214
|
+
task_id: taskId, accepted: true, deletion: 'pending',
|
|
215
|
+
...(outcome.error ? { error: outcome.error } : {}),
|
|
216
|
+
recovery: `Repeat DELETE /api/v1/tasks/${taskId}?confirm=${taskId}.`,
|
|
217
|
+
};
|
|
218
|
+
});
|
|
192
219
|
app.post('/api/v1/tasks', async (request, reply) => {
|
|
193
220
|
auth.authenticate(request, true);
|
|
194
221
|
const body = request.body;
|