@ours.network/fleet 1.0.1 → 1.0.3

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,4 +1,4 @@
1
- import type { RoomOrchestrationRecord, RoomOrchestrationState, SagaPhase, RoomMemberSeat, ProvisioningDetail, MemberRetirementPhase, RoomClosePhase, RoomRoleBriefingDefinition, RoomMemberLaunchState, RoomMemberBriefingState } from './types.js';
1
+ import type { RoomOrchestrationRecord, RoomOrchestrationState, SagaPhase, RoomMemberSeat, ProvisioningDetail, MemberRetirementPhase, RoomClosePhase, RoomRoleBriefingDefinition, RoomMemberLaunchState } from './types.js';
2
2
  export declare const roomsDir: () => string;
3
3
  export declare class RoomStateError extends Error {
4
4
  }
@@ -26,7 +26,6 @@ export declare function updateRoomRoleBriefing(id: string, role: string, definit
26
26
  export declare function updateRoomHistoryCursor(id: string, cursor: number): RoomOrchestrationRecord;
27
27
  export declare function updateMemberStartup(id: string, roleName: string, update: {
28
28
  launch?: RoomMemberLaunchState;
29
- briefing?: RoomMemberBriefingState;
30
29
  }): RoomOrchestrationRecord;
31
30
  export declare function activateRoom(id: string): RoomOrchestrationRecord;
32
31
  export declare function closeRoom(id: string): RoomOrchestrationRecord;
@@ -122,9 +122,6 @@ export function updateRoomHistoryCursor(id, cursor) {
122
122
  const LAUNCH_ORDER = [
123
123
  'pending', 'intent', 'launched', 'stopped', 'failed',
124
124
  ];
125
- const BRIEFING_ORDER = [
126
- 'pending', 'relay_queued', 'relay_failed', 'acknowledged',
127
- ];
128
125
  export function updateMemberStartup(id, roleName, update) {
129
126
  const r = readRoom(id);
130
127
  const seat = r.member_seats.find(candidate => candidate.role_name === roleName);
@@ -136,14 +133,8 @@ export function updateMemberStartup(id, roleName, update) {
136
133
  && !(seat.launch.state === 'failed' && update.launch.state === 'intent')) {
137
134
  throw new RoomStateError(`room ${id} member ${roleName} launch cannot move backward to ${update.launch.state}`);
138
135
  }
139
- if (update.briefing && seat.briefing
140
- && BRIEFING_ORDER.indexOf(update.briefing.state) < BRIEFING_ORDER.indexOf(seat.briefing.state)) {
141
- throw new RoomStateError(`room ${id} member ${roleName} briefing cannot move backward to ${update.briefing.state}`);
142
- }
143
136
  if (update.launch)
144
137
  seat.launch = update.launch;
145
- if (update.briefing)
146
- seat.briefing = update.briefing;
147
138
  writeRoom(r);
148
139
  return r;
149
140
  }
@@ -65,14 +65,14 @@ export interface TaskRecord {
65
65
  terminal_intent?: TaskTerminalIntent;
66
66
  }
67
67
  export type RoomOrchestrationState = 'provisioning' | 'active' | 'closing' | 'closed';
68
- export type SagaPhase = 'persist_intent' | 'create_room' | 'attach_owner' | 'create_members' | 'configure_briefings' | 'join_role_groups' | 'wait_seats' | 'launch_work' | 'wait_briefing_acks' | 'activate' | 'completed' | 'failed';
68
+ export type SagaPhase = 'persist_intent' | 'create_room' | 'attach_owner' | 'create_members' | 'join_role_groups' | 'wait_seats' | 'launch_work' | 'activate' | 'completed' | 'failed';
69
69
  export interface SagaCursor {
70
70
  phase: SagaPhase;
71
71
  step_index: number;
72
72
  error?: string;
73
73
  recovery_hint?: string;
74
74
  }
75
- export type ProvisioningDetail = 'waiting_cowork' | 'waiting_owner_invite' | 'owner_cid_mismatch' | 'member_failed' | 'waiting_seats' | 'waiting_briefing_delivery' | 'waiting_briefing_acks' | 'briefing_delivery_failed' | 'uncertain';
75
+ export type ProvisioningDetail = 'waiting_cowork' | 'waiting_owner_invite' | 'owner_cid_mismatch' | 'member_failed' | 'waiting_seats' | 'uncertain';
76
76
  export interface RoomRoleBriefingDefinition {
77
77
  role: string;
78
78
  text: string;
@@ -87,27 +87,13 @@ export interface RoomMemberLaunchState {
87
87
  state: 'pending' | 'intent' | 'launched' | 'stopped' | 'failed';
88
88
  attempt: number;
89
89
  action_id?: string;
90
+ /** Expected authenticated proxy caller while adopting a post-spawn crash. */
91
+ caller_role?: string;
90
92
  mission_sha256?: string;
91
93
  launch_id?: string;
92
94
  updated_at: string;
93
95
  error?: string;
94
96
  }
95
- export interface RoomMemberBriefingState {
96
- role: string;
97
- state: 'pending' | 'relay_queued' | 'relay_failed' | 'acknowledged';
98
- message_id?: string;
99
- relay_intent_record_id?: string;
100
- relay_result_record_id?: string;
101
- relay_wire_id?: string;
102
- checked_at?: string;
103
- acknowledged_at?: string;
104
- acknowledgement_message_id?: string;
105
- acknowledgement_seq?: number;
106
- rejected_ack_count: number;
107
- last_rejected_ack_reason?: string;
108
- last_rejected_ack_seq?: number;
109
- last_processed_seq?: number;
110
- }
111
97
  export type RoomHistoryEvidence = {
112
98
  kind: 'message';
113
99
  seq: number;
@@ -161,12 +147,12 @@ export interface RoomCloseCursor {
161
147
  }
162
148
  export interface RoomMemberSeat {
163
149
  role_name: string;
164
- identity_cid: string;
150
+ identity_cid?: string;
151
+ invite_id?: string;
165
152
  slot: string;
166
153
  cowork_role: string;
167
154
  seat_state: 'pending' | 'active' | 'removed';
168
155
  launch?: RoomMemberLaunchState;
169
- briefing?: RoomMemberBriefingState;
170
156
  retirement?: MemberRetirement;
171
157
  }
172
158
  export interface RoomOrchestrationRecord {
package/dist/runner.js CHANGED
@@ -61,15 +61,15 @@ const defaultDeps = () => ({
61
61
  },
62
62
  });
63
63
  const MONITOR_OWNER_FILE = '.monitor-owner';
64
- /** Fleet roles consume the operator-owned daemon; a role session never starts it. */
65
- const FLEET_OURS_AUTOSTART = '0';
64
+ const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
66
65
  /** Environment injected only into the managed harness process. */
67
66
  export function managedFleetProxyEnv(role, stateDir) {
67
+ const roleEnv = { ...(role.env ?? {}) };
68
+ // ours-mcp 1.0 treats presence (even "0") as a fatal legacy lifecycle mode.
69
+ // Managed children are daemon clients; the proxy itself never starts one.
70
+ delete roleEnv[OBSOLETE_OURS_AUTOSTART_ENV];
68
71
  return {
69
- ...(role.env ?? {}),
70
- // This must win over both inherited/configured auto-start. ACP agents run
71
- // directly rather than through ours-codex, so the runner owns this fence.
72
- OURS_AUTOSTART: FLEET_OURS_AUTOSTART,
72
+ ...roleEnv,
73
73
  [FLEET_PROXY_STATE_DIR_ENV]: stateDir,
74
74
  [FLEET_PROXY_CALLER_ENV]: role.name,
75
75
  };
@@ -166,17 +166,14 @@ export function recordMonitorOwner(dir, owner) {
166
166
  export function buildPaneCommand(launch, roleEnv, exitStatusPath, paneArgv = launch.argv) {
167
167
  const env = {
168
168
  PATH: process.env.PATH ?? '', COLORTERM: 'truecolor', ...launch.env, ...(roleEnv ?? {}),
169
- // Tmux roles have the same daemon-client boundary as ACP roles. Keep this
170
- // last so neither harness preparation nor a role env block can take over
171
- // the shared daemon lifecycle.
172
- OURS_AUTOSTART: FLEET_OURS_AUTOSTART,
173
169
  };
170
+ delete env[OBSOLETE_OURS_AUTOSTART_ENV];
174
171
  // Interactive panes should advertise colour even when the supervisor itself
175
172
  // was launched with NO_COLOR. A role may still deliberately opt back in to
176
173
  // NO_COLOR (or replace COLORTERM) through its explicit env block.
177
174
  const unsetNoColor = Object.prototype.hasOwnProperty.call(roleEnv ?? {}, 'NO_COLOR')
178
175
  ? '' : '-u NO_COLOR ';
179
- const envPfx = 'env ' + unsetNoColor
176
+ const envPfx = 'env -u OURS_AUTOSTART ' + unsetNoColor
180
177
  + Object.entries(env).map(([k, v]) => `${k}=${shq(v)}`).join(' ');
181
178
  const cmd = paneArgv.map(shq).join(' ');
182
179
  // Write a structured record, not a bare number: the wait status alone cannot
@@ -645,6 +642,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
645
642
  // role-only adapter hook prevents a PATH fallback or resolver skew from
646
643
  // claiming metadata trust for an argv it did not authenticate.
647
644
  permissionMetadataSource: launch.permissionMetadataSource,
645
+ scrubObsoleteOursAutostart: true,
648
646
  log: deps.log,
649
647
  });
650
648
  pid = acpSession.pid;
@@ -21,6 +21,8 @@ export declare const AFTER_TOOL_BOUNDARY_TIMEOUT_MS = 120000;
21
21
  * live role.
22
22
  */
23
23
  export declare const STEERING_OCCUPANCY_IDLE_MS = 150000;
24
+ /** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
25
+ export declare const CODEX_DISABLE_INHERITED_MCP_ENV = "OURS_FLEET_CODEX_DISABLE_INHERITED_MCP";
24
26
  /** Server-generated typed provenance followed by the exact human-authored body. */
25
27
  export declare function promptContentBlocks(text: string, origin?: PromptOrigin): acp.ContentBlock[];
26
28
  export declare function runtimeSelector(options: acp.SessionConfigOption[] | null | undefined, category: string): RuntimeSelectorMetadata | undefined;
@@ -38,10 +40,13 @@ export interface AcpSessionOptions {
38
40
  permissionMode?: NonNullable<SessionSnapshot['permissionMode']>;
39
41
  /** Adapter-authenticated request-metadata vocabulary; never inferred from ACP `_meta`. */
40
42
  permissionMetadataSource?: 'codex-acp';
43
+ /** Fleet-managed ours proxies must never receive the obsolete presence-sensitive flag. */
44
+ scrubObsoleteOursAutostart?: boolean;
41
45
  /**
42
- * MCP servers the ROLE declares, for every session/new, resume and load. Empty
43
- * or omitted sends `[]`, which is what fleet has always sent and leaves the
44
- * agent's own configuration untouched.
46
+ * MCP servers the ROLE declares, for every session/new, resume and load.
47
+ * Omitted preserves inherited configuration (encoded as ACP's required `[]`);
48
+ * an explicit empty array disables every inherited server through the
49
+ * authenticated bundled-adapter compatibility path.
45
50
  */
46
51
  mcpServers?: AcpMcpServer[];
47
52
  /**
@@ -258,14 +263,7 @@ export declare class AcpSession implements SessionHandle {
258
263
  private settlePendingAutomatically;
259
264
  exitResult(): ExitRecord | null;
260
265
  close(): Promise<void>;
261
- /**
262
- * The role's declared MCP servers, or `[]`.
263
- *
264
- * Sent on resume and load as well as on new: the agent builds its server set
265
- * once per session, so a resumed session that omitted them would come back
266
- * without the tools the role's config declares — which is exactly the shape of
267
- * silent drop this plumbing exists to end.
268
- */
266
+ /** ACP requires the field. Bundled agents treat [] as no client-added servers. */
269
267
  private declaredMcpServers;
270
268
  private initialize;
271
269
  private captureRuntimeMetadata;
@@ -32,6 +32,8 @@ export const AFTER_TOOL_BOUNDARY_TIMEOUT_MS = 120_000;
32
32
  */
33
33
  export const STEERING_OCCUPANCY_IDLE_MS = 150_000;
34
34
  const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
35
+ /** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
36
+ export const CODEX_DISABLE_INHERITED_MCP_ENV = 'OURS_FLEET_CODEX_DISABLE_INHERITED_MCP';
35
37
  const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
36
38
  const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
37
39
  const MAX_CANONICAL_SYMLINK_DEPTH = 40;
@@ -304,9 +306,25 @@ export class AcpSession {
304
306
  static async start(options) {
305
307
  if (!options.argv.length)
306
308
  throw new Error('ACP agent command is empty');
309
+ const disableInheritedCodexMcp = options.permissionMetadataSource === 'codex-acp'
310
+ && options.mcpServers !== undefined && options.mcpServers.length === 0;
311
+ // Obsolete ours-mcp lifecycle flags are presence-sensitive. The shared
312
+ // daemon remains operator-owned; managed ACP children are clients only.
313
+ const childEnv = {
314
+ ...process.env,
315
+ ...options.env,
316
+ };
317
+ if (options.scrubObsoleteOursAutostart)
318
+ delete childEnv.OURS_AUTOSTART;
319
+ Object.assign(childEnv,
320
+ // Write both states for the authenticated proxy: a stale ambient `1`
321
+ // must never leak explicit-empty semantics into a later inherited role.
322
+ options.permissionMetadataSource === 'codex-acp'
323
+ ? { [CODEX_DISABLE_INHERITED_MCP_ENV]: disableInheritedCodexMcp ? '1' : '0' }
324
+ : {});
307
325
  const child = spawn(options.argv[0], options.argv.slice(1), {
308
326
  cwd: options.cwd,
309
- env: { ...process.env, ...options.env },
327
+ env: childEnv,
310
328
  stdio: ['pipe', 'pipe', 'pipe'],
311
329
  });
312
330
  await new Promise((resolve, reject) => {
@@ -991,14 +1009,7 @@ export class AcpSession {
991
1009
  });
992
1010
  this.conversation.close();
993
1011
  }
994
- /**
995
- * The role's declared MCP servers, or `[]`.
996
- *
997
- * Sent on resume and load as well as on new: the agent builds its server set
998
- * once per session, so a resumed session that omitted them would come back
999
- * without the tools the role's config declares — which is exactly the shape of
1000
- * silent drop this plumbing exists to end.
1001
- */
1012
+ /** ACP requires the field. Bundled agents treat [] as no client-added servers. */
1002
1013
  declaredMcpServers() {
1003
1014
  return this.options.mcpServers ?? [];
1004
1015
  }
package/dist/spawn.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { IsolationConfig } from './isolation/types.js';
2
- import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type RoleConfig, type MonitorConfig, type SessionBackendId, type UnattendedMode, type RoomStartupGate } from './config.js';
2
+ import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type RoleConfig, type MonitorConfig, type SessionBackendId, type UnattendedMode, type RoomMemberStartup } from './config.js';
3
3
  import { type OpsDeps } from './ops.js';
4
4
  import { type CreationDeps, type CreationProvenance } from './creation.js';
5
5
  import './harness/claude-code.js';
@@ -46,8 +46,8 @@ export interface SpawnOpts {
46
46
  creationActionId?: string;
47
47
  /** Set only by a live role supervisor after a role-scoped proxy request. */
48
48
  callerRole?: string;
49
- /** Trusted Fleet-internal gate for a Cowork room member; not a CLI/config surface. */
50
- roomStartupGate?: RoomStartupGate;
49
+ /** Trusted Fleet-internal first-boot payload for a Cowork room member. */
50
+ roomMemberStartup?: RoomMemberStartup;
51
51
  /** Internal provenance labels for values filled by the caller's supervisor. */
52
52
  inheritedFromCaller?: string[];
53
53
  /**
package/dist/spawn.js CHANGED
@@ -8,7 +8,7 @@ import { loadConfig, resolveAuthProxy, resolveModelChain, resolveMonitorConfig,
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
- import { buildProvenance, daemonIdentityInventoryProvisioner, daemonIdentityProvisioner, ensureIdentity, provenanceOf, withCreationTransaction, writeProvenance, writeRoleFile, } from './creation.js';
11
+ import { buildProvenance, daemonIdentityProvisioner, ensureIdentity, provenanceOf, withCreationTransaction, writeProvenance, writeRoleFile, } from './creation.js';
12
12
  import { VERSION } from './version.js';
13
13
  import './harness/claude-code.js';
14
14
  import './harness/codex.js';
@@ -389,21 +389,13 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
389
389
  return withCreationTransaction({ role: o.name, identity: effectiveIdentity(o) }, async (tx) => {
390
390
  creation.onStage?.('checking_identity');
391
391
  assertNameFree(o);
392
- const guarantee = await ensureIdentity(effectiveIdentity(o), profileValues(o), creation.identityProvisioner ?? daemonIdentityInventoryProvisioner(), creation.log);
393
392
  creation.onStage?.('checking_identity', {
394
- result: guarantee.evidence, guarantee: guarantee.state,
393
+ result: 'unknown', guarantee: 'unverified',
395
394
  });
396
- if (guarantee.state === 'created')
397
- tx.record({
398
- stage: `ours identity ${effectiveIdentity(o)}`,
399
- undo: async () => {
400
- await creation.identityProvisioner?.remove?.(effectiveIdentity(o));
401
- },
402
- });
403
- return spawnTempInner(o, binPath, launch, tx, guarantee, creation.onStage);
395
+ return spawnTempInner(o, binPath, launch, tx, creation.onStage);
404
396
  }, creation);
405
397
  }
406
- async function spawnTempInner(o, binPath, launch, tx, guarantee, onStage) {
398
+ async function spawnTempInner(o, binPath, launch, tx, onStage) {
407
399
  const cfg = loadConfig(o.configPath);
408
400
  const defaultHarness = cfg.defaults.harness;
409
401
  const fromOpts = buildRoleConfig(o, defaultHarness);
@@ -445,13 +437,13 @@ async function spawnTempInner(o, binPath, launch, tx, guarantee, onStage) {
445
437
  worklog: resolveWorklogPolicy(cfg.defaults.worklog, fromOpts.worklog),
446
438
  auth_proxy: tempAuthProxy,
447
439
  sourceFile: '(temp)',
448
- roomStartupGate: o.roomStartupGate,
440
+ roomMemberStartup: o.roomMemberStartup,
449
441
  };
450
442
  role.env = modelEnv.env;
451
443
  if (role.auth_proxy && role.harness !== 'claude-code')
452
444
  throw new Error('auth_proxy is supported only by claude-code');
453
445
  onStage?.('writing_role');
454
- const dir = applyRole(role, { temp: true, identityGuarantee: guarantee.state });
446
+ const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
455
447
  const provenance = buildProvenance({
456
448
  role: o.name, lifetime: 'temporary', fleetVersion: VERSION,
457
449
  settings: provenanceSettings(o, cfg.defaults),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",