@ours.network/fleet 1.2.0-nightly.47 → 1.2.0-nightly.49

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.
@@ -1,10 +1,11 @@
1
1
  {
2
- "version": "1.2.0-nightly.47",
3
- "buildId": "bbc0b3f7e8a7",
4
- "commit": "5a8f562be01ca289722c0504f70281f2ce6d64e1",
2
+ "version": "1.2.0-nightly.49",
3
+ "buildId": "992b454dab6f",
4
+ "commit": "007bb41d5f822b22ebc9c708882f6cd7e70ae537",
5
5
  "dirty": true,
6
- "builtAt": "2026-10-05T15:15:27.375Z",
6
+ "builtAt": "2026-10-06T08:03:31.440Z",
7
7
  "capabilities": [
8
+ "managed-cli.codex-reasoning-effort-v1",
8
9
  "managed-cli.setup-v1",
9
10
  "workspace.enroll.preserve-profile-v1",
10
11
  "cowork.http-management-v1",
@@ -18,7 +18,9 @@ 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
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"];
21
+ /** Managed CLI accepts validated Codex Brain reasoning effort without dropping it (#235). */
22
+ export declare const CAP_MANAGED_CLI_CODEX_EFFORT = "managed-cli.codex-reasoning-effort-v1";
23
+ export declare const CAPABILITIES: readonly ["managed-cli.codex-reasoning-effort-v1", "managed-cli.setup-v1", "workspace.enroll.preserve-profile-v1", "cowork.http-management-v1", "monitor.interrupt.after_tool"];
22
24
  export type Capability = (typeof CAPABILITIES)[number];
23
25
  /** Does this build declare `token`? */
24
26
  export declare const hasCapability: (token: string) => boolean;
@@ -18,7 +18,10 @@ export const CAP_HTTP_COWORK = 'cowork.http-management-v1';
18
18
  export 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
20
  export const CAP_MANAGED_CLI_SETUP = 'managed-cli.setup-v1';
21
+ /** Managed CLI accepts validated Codex Brain reasoning effort without dropping it (#235). */
22
+ export const CAP_MANAGED_CLI_CODEX_EFFORT = 'managed-cli.codex-reasoning-effort-v1';
21
23
  export const CAPABILITIES = [
24
+ CAP_MANAGED_CLI_CODEX_EFFORT,
22
25
  CAP_MANAGED_CLI_SETUP,
23
26
  CAP_WORKSPACE_PRESERVE_PROFILE,
24
27
  CAP_HTTP_COWORK,
@@ -0,0 +1,5 @@
1
+ /** Efforts supported by Fleet's Codex Brain contract. Keep native eligibility in sync. */
2
+ export declare const CODEX_REASONING_EFFORTS: readonly ["low", "medium", "high", "xhigh", "max", "ultra"];
3
+ export declare function isCodexReasoningEffort(value: unknown): value is string;
4
+ /** Only inspected model tuning may bypass the managed CLI native-config guard. */
5
+ export declare function isManagedCliCodexConfig(config: unknown): boolean;
@@ -0,0 +1,12 @@
1
+ /** Efforts supported by Fleet's Codex Brain contract. Keep native eligibility in sync. */
2
+ export const CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
3
+ export function isCodexReasoningEffort(value) {
4
+ return typeof value === 'string' && CODEX_REASONING_EFFORTS.includes(value);
5
+ }
6
+ /** Only inspected model tuning may bypass the managed CLI native-config guard. */
7
+ export function isManagedCliCodexConfig(config) {
8
+ if (config == null)
9
+ return true;
10
+ return typeof config === 'object' && !Array.isArray(config)
11
+ && Object.entries(config).every(([key, value]) => key === 'model_reasoning_effort' && isCodexReasoningEffort(value));
12
+ }
@@ -8,6 +8,7 @@ import { harnessRuntimeDir } from '../isolation/policy.js';
8
8
  import { resolveBundledAcpAgent, } from './acp-agent.js';
9
9
  import { VERIFIED_CODEX_ACP_VERSIONS, probeCodexRuntime, codexVersionAtLeast, hostCodex } from './codex-runtime.js';
10
10
  import { CodexAgentSessionAdapter } from './codex-session.js';
11
+ import { CODEX_REASONING_EFFORTS, isCodexReasoningEffort } from './codex-reasoning.js';
11
12
  const OPTION_KEYS = [
12
13
  'launcher', 'sandbox', 'approval', 'permission_mode', 'search', 'profile', 'config', 'add_dirs',
13
14
  'monitor',
@@ -141,6 +142,13 @@ function neutralSandboxMode(role) {
141
142
  function isNativeConfigKeyAllowed(key) {
142
143
  return NATIVE_CONFIG_ALLOWLIST.has(key);
143
144
  }
145
+ /** Brain wins; a validated native effort also needs delivery through ACP's selection API. */
146
+ function reasoningEffort(role) {
147
+ if (typeof role.effort === 'string')
148
+ return role.effort;
149
+ const value = role.harness_options?.config?.model_reasoning_effort;
150
+ return isCodexReasoningEffort(value) ? value : undefined;
151
+ }
144
152
  /** Defense in depth for callers which launch a role after validation was bypassed. */
145
153
  export function nativeCodexConfig(role) {
146
154
  const options = role.harness_options;
@@ -318,10 +326,8 @@ export function makeCodexAdapter(exec = realExec, transport, nativeTransport) {
318
326
  id: 'codex',
319
327
  agentSession: new CodexAgentSessionAdapter({
320
328
  resolveBrain(brain) {
321
- const levels = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
322
- if (brain.effort != null
323
- && (typeof brain.effort !== 'string' || !levels.includes(brain.effort)))
324
- throw new Error(`Codex Brain effort must be one of: ${levels.join(', ')}`);
329
+ if (brain.effort != null && !isCodexReasoningEffort(brain.effort))
330
+ throw new Error(`Codex Brain effort must be one of: ${CODEX_REASONING_EFFORTS.join(', ')}`);
325
331
  const harnessOptions = { ...(brain.harnessOptions ?? {}) };
326
332
  if (brain.effort)
327
333
  harnessOptions.config = {
@@ -343,8 +349,8 @@ export function makeCodexAdapter(exec = realExec, transport, nativeTransport) {
343
349
  sessionConfigSelections: role => [
344
350
  ...(typeof role.model === 'string'
345
351
  ? [{ configId: 'model', value: role.model }] : []),
346
- ...(typeof role.effort === 'string'
347
- ? [{ configId: 'reasoning_effort', value: role.effort }] : []),
352
+ ...(reasoningEffort(role)
353
+ ? [{ configId: 'reasoning_effort', value: reasoningEffort(role) }] : []),
348
354
  ],
349
355
  permissionModeId: role => acpAgentMode(role),
350
356
  mcpServers: () => undefined,
@@ -26,6 +26,7 @@ import { managedPinMarker } from './fleet-command-audit.js';
26
26
  import { MANAGED_CLI_WORKFLOW_IDS, splitRootFor, } from './config.js';
27
27
  import { resolveBundledAcpAgent } from './harness/acp-agent.js';
28
28
  import { executableOnPath, hostCodex } from './harness/codex-runtime.js';
29
+ import { isManagedCliCodexConfig } from './harness/codex-reasoning.js';
29
30
  import { getAdapter } from './harness/registry.js';
30
31
  import { agentDir, defaultConfigPath, home, stateRoot } from './paths.js';
31
32
  /** Capability-gated by the installer; see src/capabilities.ts. */
@@ -333,8 +334,8 @@ export function analyzeManagedCli(role, configPath, options = {}) {
333
334
  // directories the role adds. Native settings Fleet cannot read may add more.
334
335
  const native = (role.harness_options ?? {});
335
336
  const writable = [workspace, ...(Array.isArray(native.add_dirs) ? native.add_dirs.filter((dir) => typeof dir === 'string').map(dir => resolve(workspace, dir)) : [])];
336
- if (role.harness === 'codex' && (native.profile != null || (native.config && typeof native.config === 'object' && Object.keys(native.config).length)))
337
- reasons.push('harness_native.config or harness_native.profile changes Codex\'s native sandbox in ways Fleet does not inspect (for example extra writable roots), so it cannot tell whether the agent could replace the pinned files; drop them or keep this role sandboxed');
337
+ if (role.harness === 'codex' && (native.profile != null || !isManagedCliCodexConfig(native.config)))
338
+ reasons.push('harness_native.config or harness_native.profile contains invalid or uninspected Codex settings (for example extra writable roots), so Fleet does not inspect enough to tell whether the agent could replace the pinned files; only validated model_reasoning_effort is supported; drop the other settings or keep this role sandboxed');
338
339
  if (role.isolation)
339
340
  reasons.push('the role declares isolation:, and Fleet\'s outer sandbox is kept as-is; the workflow is not qualified inside it');
340
341
  if (!plainExecutable(paths.node))
@@ -29,6 +29,7 @@ type EventDraft = Omit<ConversationEventV1, 'schemaVersion' | 'roleId' | 'eventI
29
29
  export declare class ConversationEventStore {
30
30
  private readonly dir;
31
31
  private nextSeq;
32
+ private firstAvailableSeq?;
32
33
  private segments;
33
34
  private tail;
34
35
  private readonly listeners;
@@ -81,7 +82,6 @@ export declare class ConversationEventStore {
81
82
  private trackPromptState;
82
83
  private segmentFd;
83
84
  private writeManifest;
84
- private firstStoredSeq;
85
85
  private eventsAfter;
86
86
  private markDegraded;
87
87
  }
@@ -24,6 +24,7 @@ export class IdempotencyConflictError extends Error {
24
24
  export class ConversationEventStore {
25
25
  dir;
26
26
  nextSeq = 1;
27
+ firstAvailableSeq;
27
28
  segments = [];
28
29
  tail = [];
29
30
  listeners = new Set();
@@ -72,6 +73,7 @@ export class ConversationEventStore {
72
73
  const fd = this.segmentFd(Buffer.byteLength(line));
73
74
  writeSync(fd, line);
74
75
  fsyncSync(fd);
76
+ this.firstAvailableSeq ??= event.seq;
75
77
  this.nextSeq++;
76
78
  this.activeBytes += Buffer.byteLength(line);
77
79
  this.tail.push(event);
@@ -101,7 +103,10 @@ export class ConversationEventStore {
101
103
  const events = this.eventsAfter(after, limit + 1);
102
104
  const hasMore = events.length > limit;
103
105
  const pageEvents = hasMore ? events.slice(0, limit) : events;
104
- const firstStored = this.firstStoredSeq();
106
+ // This single-writer store never removes retained segments. Recovery and
107
+ // append already know the first readable sequence; paging the memory tail
108
+ // must not parse the first segment again, especially while skipping replay.
109
+ const firstStored = this.firstAvailableSeq;
105
110
  return {
106
111
  events: pageEvents,
107
112
  ...(firstStored !== undefined ? { firstAvailableCursor: String(firstStored) } : {}),
@@ -159,6 +164,7 @@ export class ConversationEventStore {
159
164
  let maxSeq = 0;
160
165
  for (const segment of this.segments) {
161
166
  for (const event of this.readSegment(segment)) {
167
+ this.firstAvailableSeq ??= event.seq;
162
168
  maxSeq = Math.max(maxSeq, event.seq);
163
169
  this.tail.push(event);
164
170
  if (this.tail.length > TAIL_EVENTS)
@@ -308,16 +314,6 @@ export class ConversationEventStore {
308
314
  }
309
315
  }
310
316
  // ── reads ──────────────────────────────────────────────────────────────────
311
- firstStoredSeq() {
312
- if (this.tail.length && this.tail[0].seq === 1)
313
- return 1;
314
- for (const segment of this.segments) {
315
- const events = this.readSegment(segment);
316
- if (events.length)
317
- return events[0].seq;
318
- }
319
- return this.tail[0]?.seq;
320
- }
321
317
  eventsAfter(after, limit) {
322
318
  // Serve from the in-memory tail whenever the range allows it.
323
319
  if (this.tail.length && after >= this.tail[0].seq - 1)
@@ -21,12 +21,14 @@ export function startWorkspaceTunnel(origin) {
21
21
  return;
22
22
  child = spawn('cloudflared', ['tunnel', '--no-autoupdate', 'run', '--token-file', tokenFile], { stdio: 'ignore' });
23
23
  let scheduled = false;
24
+ // Keep an error listener for the child's whole lifetime, including shutdown.
24
25
  const retry = () => { if (scheduled || stopped)
25
26
  return; scheduled = true; timer = setTimeout(launch, backoff); backoff = Math.min(30000, backoff * 2); };
26
- child.once('error', retry);
27
+ child.on('error', retry);
27
28
  child.once('exit', retry);
28
29
  };
29
30
  launch();
30
- return async () => { stopped = true; clearTimeout(timer); const current = child; if (!current || current.exitCode !== null)
31
- return; await new Promise(resolve => { const timeout = setTimeout(() => { current.kill('SIGKILL'); resolve(); }, 5000); current.once('exit', () => { clearTimeout(timeout); resolve(); }); current.kill('SIGTERM'); }); };
31
+ // A failed spawn has no pid and reports error/close rather than exit.
32
+ return async () => { stopped = true; clearTimeout(timer); const current = child; if (!current?.pid || current.exitCode !== null || current.signalCode !== null)
33
+ return; await new Promise(resolve => { const timeout = setTimeout(() => { current.kill('SIGKILL'); resolve(); }, 5000); current.once('close', () => { clearTimeout(timeout); resolve(); }); current.kill('SIGTERM'); }); };
32
34
  }
@@ -127,9 +127,14 @@ commands; roles declaring `isolation:`; unspellable paths; a Node path needing
127
127
  quotes; Codex older than 0.160.0 with quoted paths; and any pinned file the
128
128
  agent could replace — inside its workspace or a `harness_options.add_dirs`
129
129
  directory, lexically or through a symlink. Codex roles with
130
- `harness_options.config` or `harness_options.profile` are unsupported too:
131
- those change the native sandbox (for example extra writable roots) in ways Fleet
132
- does not inspect. Native user settings Fleet cannot read are outside this check.
130
+ `harness_options.profile` or uninspected `harness_options.config` remain unsupported:
131
+ those can change the native sandbox (for example extra writable roots) in ways
132
+ Fleet does not inspect. The only accepted nonempty config is
133
+ `model_reasoning_effort` with a supported value (`low`, `medium`, `high`, `xhigh`,
134
+ `max`, `ultra`). This includes the config generated by Codex Brain `effort` (#235).
135
+ The chosen effort remains in native launch config and ACP reasoning selection;
136
+ adding any unknown or security-bearing config key still prevents policy setup.
137
+ Native user settings Fleet cannot read are outside this check.
133
138
 
134
139
  ### Qualification
135
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.2.0-nightly.47",
3
+ "version": "1.2.0-nightly.49",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",