@ours.network/fleet 1.2.0-nightly.43 → 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 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
@@ -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 available; hidden worker entry points remain internal.');
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:`);
@@ -1,10 +1,11 @@
1
1
  {
2
- "version": "1.2.0-nightly.43",
3
- "buildId": "db00c6f98ef1",
4
- "commit": "8116950e551f2c3e1d9004bf9c138d2659a93266",
2
+ "version": "1.2.0-nightly.45",
3
+ "buildId": "9af6d0fa4b7e",
4
+ "commit": "f59c69b6662ae09b5e72ea2c4c0a958729e1928f",
5
5
  "dirty": true,
6
- "builtAt": "2026-10-04T05:55:03.625Z",
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"
@@ -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
- export declare const CAPABILITIES: readonly ["workspace.enroll.preserve-profile-v1", "cowork.http-management-v1", "monitor.interrupt.after_tool"];
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;
@@ -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');