@ours.network/fleet 1.2.0-nightly.44 → 1.2.0-nightly.45
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 +23 -0
- package/dist/briefing.d.ts +2 -0
- package/dist/briefing.js +8 -1
- package/dist/build-info.json +5 -4
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +3 -0
- package/dist/cli.js +107 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +13 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +82 -0
- package/dist/doctor.js +27 -0
- package/dist/fleet-command-audit.d.ts +6 -0
- package/dist/fleet-command-audit.js +15 -3
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +922 -0
- package/dist/ops.js +3 -1
- package/dist/resolved-plan.js +1 -0
- package/dist/runner.js +9 -1
- package/dist/spawn.js +1 -1
- package/docs/validation/managed-cli-permissions.md +222 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -286,10 +286,33 @@ ours-fleet loops reload <Role>
|
|
|
286
286
|
ours-fleet loops run-now|disable|enable <Role> <Loop>
|
|
287
287
|
ours-fleet rm <Name>
|
|
288
288
|
ours-fleet doctor [--harness H]
|
|
289
|
+
ours-fleet managed-cli setup [--enable <Agent>]... | status [--json] native harness policy for the task workflow
|
|
289
290
|
ours-fleet version [--json] build identity, capabilities, installs on PATH
|
|
290
291
|
ours-fleet init
|
|
291
292
|
```
|
|
292
293
|
|
|
294
|
+
### Fleet task commands from a harness command sandbox
|
|
295
|
+
|
|
296
|
+
A Codex or Claude command sandbox refuses the supervisor socket, so a managed
|
|
297
|
+
`ours-fleet` call fails with `connect EPERM` before it parses anything. Fleet
|
|
298
|
+
prepares the native harness policy for the packaged Coordinator task workflow;
|
|
299
|
+
you do not write Codex rules or Claude sandbox settings by hand:
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
ours-fleet managed-cli setup --enable FleetCoordinator # adds managed_cli: [task-workflow] to that Agent
|
|
303
|
+
ours-fleet managed-cli status # static setup, session policy, observed commands
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The opt-in is an explicit Agent key and changes no permission. Setup starts no
|
|
307
|
+
session or model, is repeated at every agent start, and `ours-install` runs it
|
|
308
|
+
for you. It prepares `task create|start|finish|block|unblock|review|list|show`,
|
|
309
|
+
`room show|members` and template/plan/help inspection for one pinned
|
|
310
|
+
configuration, and nothing else; `task start`/`task finish` are real authority.
|
|
311
|
+
Qualified end to end on Linux with Codex (native and ACP) and Claude ACP; macOS
|
|
312
|
+
is prepared the same way but not yet qualified. Scope, boundaries, unsupported
|
|
313
|
+
combinations and the evidence are in
|
|
314
|
+
[docs/validation/managed-cli-permissions.md](docs/validation/managed-cli-permissions.md).
|
|
315
|
+
|
|
293
316
|
### Which build am I running?
|
|
294
317
|
|
|
295
318
|
`--version` prints a semver, and a semver does not identify an artifact: version
|
package/dist/briefing.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface BriefingOpts {
|
|
|
15
15
|
identityGuarantee?: 'verified' | 'created' | 'unverified';
|
|
16
16
|
/** Temporary spawn whose newly-created identity should share the session lifecycle. */
|
|
17
17
|
temporaryIdentity?: boolean;
|
|
18
|
+
/** Fleet-prepared invocation guidance (src/managed-cli.ts); empty when none applies. */
|
|
19
|
+
managedCli?: string[];
|
|
18
20
|
}
|
|
19
21
|
/** Render a role's briefing.md: narrative (or curated body) + mechanical boot steps. */
|
|
20
22
|
export declare function generateBriefing(role: ResolvedRole, v: BriefingVocab, opts: BriefingOpts): string;
|
package/dist/briefing.js
CHANGED
|
@@ -197,9 +197,16 @@ export function generateBriefing(role, v, opts) {
|
|
|
197
197
|
L.push('For omitted settings, the supervisor inherits your canonical Brain and Role selections,');
|
|
198
198
|
L.push('working directory, neutral permissions, coordinator, and fleet monitor policy. Every');
|
|
199
199
|
L.push('explicit option wins; identity/session-local and secret material never inherit.');
|
|
200
|
-
L.push('All public CLI surfaces are
|
|
200
|
+
L.push('All public CLI surfaces are routed this way; hidden worker entry points remain internal.');
|
|
201
|
+
if (role.permissions && role.permissions.filesystem !== 'unrestricted' && !opts.managedCli?.length) {
|
|
202
|
+
L.push('Routing is not reachability: every call first connects to the supervisor socket, and a');
|
|
203
|
+
L.push('harness command sandbox can refuse that connection (`connect EPERM`), even for `--help`.');
|
|
204
|
+
L.push('No Fleet command is prepared to run outside your sandbox. If one is refused, report it');
|
|
205
|
+
L.push('as a blocker; the owner enables a workflow with `ours-fleet managed-cli setup --enable`.');
|
|
206
|
+
}
|
|
201
207
|
L.push('Lifecycle notice delivery failures stay in service diagnostics and never rerun effects.');
|
|
202
208
|
L.push('This proxy is attribution and convenience, not a security boundary for unisolated roles.');
|
|
209
|
+
L.push(...(opts.managedCli ?? []));
|
|
203
210
|
}
|
|
204
211
|
if (role.coordinator) {
|
|
205
212
|
L.push(`7. ANNOUNCE yourself: call **${v.sendTool}** to contact "${role.coordinator}" with text:`);
|
package/dist/build-info.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.2.0-nightly.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "1.2.0-nightly.45",
|
|
3
|
+
"buildId": "9af6d0fa4b7e",
|
|
4
|
+
"commit": "f59c69b6662ae09b5e72ea2c4c0a958729e1928f",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-04T12:42:35.826Z",
|
|
7
7
|
"capabilities": [
|
|
8
|
+
"managed-cli.setup-v1",
|
|
8
9
|
"workspace.enroll.preserve-profile-v1",
|
|
9
10
|
"cowork.http-management-v1",
|
|
10
11
|
"monitor.interrupt.after_tool"
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
export declare const CAP_MONITOR_INTERRUPT_AFTER_TOOL = "monitor.interrupt.after_tool";
|
|
17
17
|
export declare const CAP_HTTP_COWORK = "cowork.http-management-v1";
|
|
18
18
|
export declare const CAP_WORKSPACE_PRESERVE_PROFILE = "workspace.enroll.preserve-profile-v1";
|
|
19
|
-
|
|
19
|
+
/** `ours-fleet managed-cli setup|status` and the `managed_cli` Agent key (#233). */
|
|
20
|
+
export declare const CAP_MANAGED_CLI_SETUP = "managed-cli.setup-v1";
|
|
21
|
+
export declare const CAPABILITIES: readonly ["managed-cli.setup-v1", "workspace.enroll.preserve-profile-v1", "cowork.http-management-v1", "monitor.interrupt.after_tool"];
|
|
20
22
|
export type Capability = (typeof CAPABILITIES)[number];
|
|
21
23
|
/** Does this build declare `token`? */
|
|
22
24
|
export declare const hasCapability: (token: string) => boolean;
|
package/dist/capabilities.js
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
export const CAP_MONITOR_INTERRUPT_AFTER_TOOL = 'monitor.interrupt.after_tool';
|
|
17
17
|
export const CAP_HTTP_COWORK = 'cowork.http-management-v1';
|
|
18
18
|
export const CAP_WORKSPACE_PRESERVE_PROFILE = 'workspace.enroll.preserve-profile-v1';
|
|
19
|
+
/** `ours-fleet managed-cli setup|status` and the `managed_cli` Agent key (#233). */
|
|
20
|
+
export const CAP_MANAGED_CLI_SETUP = 'managed-cli.setup-v1';
|
|
19
21
|
export const CAPABILITIES = [
|
|
22
|
+
CAP_MANAGED_CLI_SETUP,
|
|
20
23
|
CAP_WORKSPACE_PRESERVE_PROFILE,
|
|
21
24
|
CAP_HTTP_COWORK,
|
|
22
25
|
CAP_MONITOR_INTERRUPT_AFTER_TOOL,
|
package/dist/cli.js
CHANGED
|
@@ -56,6 +56,8 @@ import './harness/claude-code.js'; // registers the claude-code adapter
|
|
|
56
56
|
import './harness/codex.js'; // registers the codex adapter
|
|
57
57
|
import './harness/hermes.js';
|
|
58
58
|
import { registerLayoutCommands } from './rooms-tasks/layout-cli.js';
|
|
59
|
+
import { MANAGED_CONFIGURATION_FLAG, assertPinnedConfiguration, enableManagedCli, managedCliReport, parseManagedEntry, } from './managed-cli.js';
|
|
60
|
+
import { MANAGED_CLI_WORKFLOW_IDS } from './config.js';
|
|
59
61
|
import { registerTemplateCommands, registerTaskCommands, registerRoomCommands } from './rooms-tasks/cli.js';
|
|
60
62
|
import { RoleCreationService } from './application/role-creation-service.js';
|
|
61
63
|
import { RoleRemovalService } from './application/role-removal-service.js';
|
|
@@ -99,6 +101,22 @@ const program = new Command()
|
|
|
99
101
|
.exitOverride()
|
|
100
102
|
.version(VERSION);
|
|
101
103
|
const cOpt = (cmd) => cmd.option('-c, --configuration <file>', 'manifest (default: ~/fleet.yaml; documents under ~/fleet/)');
|
|
104
|
+
/**
|
|
105
|
+
* Set only for the pinned managed entry form (see src/managed-cli.ts). Checked
|
|
106
|
+
* against the PARSED option, so every spelling of a later `-c` is covered.
|
|
107
|
+
*/
|
|
108
|
+
let pinnedConfiguration;
|
|
109
|
+
program.hook('preAction', (_program, action) => {
|
|
110
|
+
if (pinnedConfiguration === undefined)
|
|
111
|
+
return;
|
|
112
|
+
try {
|
|
113
|
+
assertPinnedConfiguration(pinnedConfiguration, action.opts().configuration);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error(`error: ${error.message}`);
|
|
117
|
+
throw new FleetCliExit(1, 'validation', 'not_started');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
102
120
|
const collect = (value, previous) => [...previous, value];
|
|
103
121
|
program.command('docs')
|
|
104
122
|
.alias('man')
|
|
@@ -1299,6 +1317,78 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1299
1317
|
die(e);
|
|
1300
1318
|
}
|
|
1301
1319
|
});
|
|
1320
|
+
function renderManagedCliReport(report) {
|
|
1321
|
+
const lines = [
|
|
1322
|
+
`Managed CLI setup for ${report.configuration}`,
|
|
1323
|
+
` node ${report.node}`, ` cli ${report.cli}`,
|
|
1324
|
+
` platform ${report.platform.os}${report.platform.declared ? '' : ' (not a declared platform)'}`,
|
|
1325
|
+
];
|
|
1326
|
+
for (const role of report.roles) {
|
|
1327
|
+
if (role.setup.state === 'not-declared')
|
|
1328
|
+
continue;
|
|
1329
|
+
lines.push('', `${role.role} (${role.harness}/${role.session}) — ${role.workflows.join(', ')}`);
|
|
1330
|
+
lines.push(` setup (static): ${role.setup.state}${role.setup.action ? ` [${role.setup.action}]` : ''} — ${role.setup.detail}`);
|
|
1331
|
+
if (role.setup.artifact)
|
|
1332
|
+
lines.push(` ${role.setup.artifact}`);
|
|
1333
|
+
lines.push(` running session: ${role.session_policy}`);
|
|
1334
|
+
lines.push(` observed (audit ledger): ${role.observed.summary}`);
|
|
1335
|
+
if (role.commandPrefix)
|
|
1336
|
+
lines.push(` invoke as: ${role.commandPrefix} <command>`);
|
|
1337
|
+
for (const line of role.scope)
|
|
1338
|
+
lines.push(` scope: ${line}`);
|
|
1339
|
+
for (const line of role.warnings)
|
|
1340
|
+
lines.push(` note: ${line}`);
|
|
1341
|
+
}
|
|
1342
|
+
if (!report.roles.some(role => role.setup.state !== 'not-declared'))
|
|
1343
|
+
lines.push('', 'No agent declares managed_cli. Nothing was generated and no permission changed.', 'Enable it for one agent with: ours-fleet managed-cli setup --enable <Agent>');
|
|
1344
|
+
for (const item of report.removed)
|
|
1345
|
+
lines.push('', `${item.action}: ${item.path} — ${item.detail ?? ''}`);
|
|
1346
|
+
lines.push('', ...report.disclosure.map(line => `* ${line}`));
|
|
1347
|
+
return lines.join('\n');
|
|
1348
|
+
}
|
|
1349
|
+
const managedCliCommand = program.command('managed-cli')
|
|
1350
|
+
.description('prepare and inspect native harness policy for Fleet commands run from a command sandbox');
|
|
1351
|
+
for (const [name, write, description] of [
|
|
1352
|
+
['setup', true, 'generate the policy for agents declaring managed_cli; starts no session and no model'],
|
|
1353
|
+
['status', false, 'compare generated policy and observed supervisor access without changing anything'],
|
|
1354
|
+
]) {
|
|
1355
|
+
const command = cOpt(managedCliCommand.command(name).description(description)).option('--json', 'JSON output');
|
|
1356
|
+
if (write)
|
|
1357
|
+
command.option('--enable <agent>', 'first add managed_cli: [task-workflow] to this Agent file (repeatable); its permissions are not changed', (value, all) => [...all, value], [])
|
|
1358
|
+
.option('--workflow <id>', `workflow --enable adds (${MANAGED_CLI_WORKFLOW_IDS.join(', ')})`, 'task-workflow');
|
|
1359
|
+
command.action((opts) => {
|
|
1360
|
+
try {
|
|
1361
|
+
const enabled = [];
|
|
1362
|
+
if (opts.enable?.length) {
|
|
1363
|
+
if (!MANAGED_CLI_WORKFLOW_IDS.includes(opts.workflow ?? ''))
|
|
1364
|
+
throw new Error(`unknown workflow '${opts.workflow}'; supported: ${MANAGED_CLI_WORKFLOW_IDS.join(', ')}`);
|
|
1365
|
+
// Validate every name before the first file is touched.
|
|
1366
|
+
const cfg = loadConfig(opts.configuration);
|
|
1367
|
+
for (const agent of opts.enable)
|
|
1368
|
+
findRole(cfg, agent);
|
|
1369
|
+
for (const agent of opts.enable)
|
|
1370
|
+
enabled.push({ agent, ...enableManagedCli(opts.configuration, agent, opts.workflow) });
|
|
1371
|
+
}
|
|
1372
|
+
const report = managedCliReport(loadConfig(opts.configuration), opts.configuration, { write });
|
|
1373
|
+
if (opts.json) {
|
|
1374
|
+
console.log(JSON.stringify({ ...report, enabled }, null, 2));
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
for (const item of enabled)
|
|
1378
|
+
console.log(`${item.changed ? 'Enabled' : 'Already enabled'} ${opts.workflow} for ${item.agent} in ${item.file}`);
|
|
1379
|
+
console.log(renderManagedCliReport(report));
|
|
1380
|
+
}
|
|
1381
|
+
const failing = report.roles.filter(role => ['unsupported', 'conflict', 'stale', 'missing'].includes(role.setup.state));
|
|
1382
|
+
if (failing.length)
|
|
1383
|
+
throw new FleetCliExit(1);
|
|
1384
|
+
}
|
|
1385
|
+
catch (error) {
|
|
1386
|
+
if (error instanceof FleetCliExit)
|
|
1387
|
+
throw error;
|
|
1388
|
+
die(error);
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1302
1392
|
cOpt(program.command('doctor').description('prerequisite report'))
|
|
1303
1393
|
.option('--harness <id>', 'check one harness explicitly')
|
|
1304
1394
|
.option('--yaml-mode <mode>', 'non-plain YAML policy: compat|strict', 'compat')
|
|
@@ -1797,7 +1887,15 @@ async function parseFleetCli() {
|
|
|
1797
1887
|
async function runFleetCli() {
|
|
1798
1888
|
const stateDir = process.env[FLEET_PROXY_STATE_DIR_ENV];
|
|
1799
1889
|
const caller = process.env[FLEET_PROXY_CALLER_ENV];
|
|
1890
|
+
const entry = parseManagedEntry(process.argv.slice(2));
|
|
1800
1891
|
if (!stateDir || !caller) {
|
|
1892
|
+
// The pinned form exists for managed sessions; outside one there is no
|
|
1893
|
+
// supervisor to audit the call, so it is refused rather than run unaudited.
|
|
1894
|
+
if (entry.kind !== 'none') {
|
|
1895
|
+
console.error(`error: ${entry.kind === 'error' ? entry.message
|
|
1896
|
+
: `${MANAGED_CONFIGURATION_FLAG} is only valid inside a Fleet-managed agent session, where the supervisor audits the call`}`);
|
|
1897
|
+
throw new FleetCliExit(1, 'validation', 'not_started');
|
|
1898
|
+
}
|
|
1801
1899
|
await parseFleetCli();
|
|
1802
1900
|
return;
|
|
1803
1901
|
}
|
|
@@ -1832,6 +1930,15 @@ async function runFleetCli() {
|
|
|
1832
1930
|
const originalExit = process.exit;
|
|
1833
1931
|
process.exit = ((code) => { throw new FleetCliExit(code ?? 0); });
|
|
1834
1932
|
try {
|
|
1933
|
+
if (entry.kind === 'error') {
|
|
1934
|
+
console.error(`error: ${entry.message}`);
|
|
1935
|
+
throw new FleetCliExit(1, 'validation', 'not_started');
|
|
1936
|
+
}
|
|
1937
|
+
if (entry.kind === 'pinned') {
|
|
1938
|
+
// The supervisor audited the argv as typed; Commander parses the ordinary form.
|
|
1939
|
+
pinnedConfiguration = entry.configuration;
|
|
1940
|
+
process.argv = [process.argv[0], process.argv[1], ...entry.argv];
|
|
1941
|
+
}
|
|
1835
1942
|
await parseFleetCli();
|
|
1836
1943
|
}
|
|
1837
1944
|
catch (error) {
|
package/dist/config.d.ts
CHANGED
|
@@ -36,6 +36,12 @@ export type FilesystemMode = 'read-only' | 'workspace' | 'unrestricted';
|
|
|
36
36
|
export type UnattendedMode = 'deny' | 'wait';
|
|
37
37
|
/** Monitor wake policy: preserve legacy booleans and add one explicit safe boundary. */
|
|
38
38
|
export type MonitorInterrupt = boolean | 'after_tool';
|
|
39
|
+
/**
|
|
40
|
+
* Workflows Fleet prepares native harness execution policy for. Declared per
|
|
41
|
+
* Agent; never inferred from a role's name or persona, and never inherited.
|
|
42
|
+
*/
|
|
43
|
+
export declare const MANAGED_CLI_WORKFLOW_IDS: readonly ["task-workflow"];
|
|
44
|
+
export type ManagedCliWorkflowId = (typeof MANAGED_CLI_WORKFLOW_IDS)[number];
|
|
39
45
|
export interface CommonPermissions {
|
|
40
46
|
approval: ApprovalMode;
|
|
41
47
|
filesystem: FilesystemMode;
|
|
@@ -146,6 +152,8 @@ export interface RoleConfig {
|
|
|
146
152
|
owner_channel?: OwnerChannelConfigInput;
|
|
147
153
|
worklog?: WorklogPolicyInput;
|
|
148
154
|
auth_proxy?: Partial<AuthProxyConfig>;
|
|
155
|
+
/** Fleet-prepared CLI workflows this role may run outside its command sandbox. */
|
|
156
|
+
managed_cli?: ManagedCliWorkflowId[];
|
|
149
157
|
}
|
|
150
158
|
export type AgentSelection<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
151
159
|
ref: string;
|
|
@@ -167,6 +175,8 @@ export interface AgentDefinition {
|
|
|
167
175
|
owner_channel?: OwnerChannelConfigInput;
|
|
168
176
|
worklog?: WorklogPolicyInput;
|
|
169
177
|
auth_proxy?: Partial<AuthProxyConfig>;
|
|
178
|
+
/** Fleet-prepared CLI workflows this Agent may run outside its command sandbox. */
|
|
179
|
+
managed_cli?: ManagedCliWorkflowId[];
|
|
170
180
|
/** Scheduled turns scoped only to temporary launches; persistent Agents reject this field. */
|
|
171
181
|
loops?: AgentLoopsConfig;
|
|
172
182
|
}
|
package/dist/config.js
CHANGED
|
@@ -24,6 +24,11 @@ export const NOTIFY_EVENT_TYPES = [
|
|
|
24
24
|
'message_received', 'file_received', 'sibling_contact_added', 'local_contact_request',
|
|
25
25
|
'pending_message', 'contact_restored', 'inbound_error', 'state_import_failed',
|
|
26
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* Workflows Fleet prepares native harness execution policy for. Declared per
|
|
29
|
+
* Agent; never inferred from a role's name or persona, and never inherited.
|
|
30
|
+
*/
|
|
31
|
+
export const MANAGED_CLI_WORKFLOW_IDS = ['task-workflow'];
|
|
27
32
|
/** Default wake sources when a role does not list its own. */
|
|
28
33
|
export const DEFAULT_WAKE_SOURCES = ['message_received', 'file_received', 'local_contact_request', 'pending_message'];
|
|
29
34
|
const MONITOR_KEYS = [
|
|
@@ -139,7 +144,7 @@ export const ROLE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
|
139
144
|
const ROLE_KEYS = [
|
|
140
145
|
'harness', 'session', 'session_options', 'permissions', 'identity', 'cwd', 'coordinator', 'mission', 'persona', 'bio',
|
|
141
146
|
'briefing_file', 'model', 'effort', 'model_chain', 'max_tokens', 'autocompact_pct', 'env', 'oversee', 'harness_options',
|
|
142
|
-
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy',
|
|
147
|
+
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'managed_cli',
|
|
143
148
|
];
|
|
144
149
|
export const ROLE_PRESET_KEYS = ['mission', 'persona', 'bio', 'briefing_file'];
|
|
145
150
|
export const BRAIN_PRESET_KEYS = [
|
|
@@ -148,7 +153,7 @@ export const BRAIN_PRESET_KEYS = [
|
|
|
148
153
|
];
|
|
149
154
|
export const AGENT_KEYS = [
|
|
150
155
|
'role', 'brain', 'permissions', 'identity', 'cwd', 'coordinator', 'env', 'oversee',
|
|
151
|
-
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'loops',
|
|
156
|
+
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'loops', 'managed_cli',
|
|
152
157
|
];
|
|
153
158
|
const AGENT_INSTANCE_KEYS = ['template', 'overrides'];
|
|
154
159
|
const TEMPLATE_FORBIDDEN_KEYS = ['identity'];
|
|
@@ -236,6 +241,12 @@ function validateAgentScalars(value, file, id) {
|
|
|
236
241
|
if (current !== undefined && (typeof current !== 'string' || !current.trim()))
|
|
237
242
|
schemaError(file, `/agents/${id}/${key}`, 'must be a non-blank string');
|
|
238
243
|
}
|
|
244
|
+
if (value.managed_cli !== undefined) {
|
|
245
|
+
const ids = value.managed_cli;
|
|
246
|
+
if (!Array.isArray(ids) || ids.some(entry => !MANAGED_CLI_WORKFLOW_IDS.includes(entry))
|
|
247
|
+
|| new Set(ids).size !== ids.length)
|
|
248
|
+
schemaError(file, `/agents/${id}/managed_cli`, `must be a list of distinct workflow IDs; supported: ${MANAGED_CLI_WORKFLOW_IDS.join(', ')}`);
|
|
249
|
+
}
|
|
239
250
|
if (value.coordinator !== undefined
|
|
240
251
|
&& (typeof value.coordinator !== 'string' || !ROLE_NAME_RE.test(value.coordinator)))
|
|
241
252
|
schemaError(file, `/agents/${id}/coordinator`, 'must be a valid Agent ID');
|