@ours.network/fleet 1.0.5 → 1.1.0-nightly.2

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.
Files changed (100) hide show
  1. package/README.md +90 -112
  2. package/dist/application/capabilities.d.ts +0 -1
  3. package/dist/application/capabilities.js +3 -13
  4. package/dist/application/fleet-query-service.d.ts +0 -3
  5. package/dist/application/fleet-query-service.js +1 -24
  6. package/dist/application/role-creation-service.d.ts +5 -5
  7. package/dist/application/role-creation-service.js +14 -9
  8. package/dist/application/role-removal-service.js +6 -3
  9. package/dist/application/role-repository.d.ts +1 -2
  10. package/dist/application/role-repository.js +7 -7
  11. package/dist/application/session-control.d.ts +0 -12
  12. package/dist/application/session-control.js +0 -42
  13. package/dist/application/session-mutations.d.ts +5 -5
  14. package/dist/application/types.d.ts +1 -8
  15. package/dist/briefing.js +3 -6
  16. package/dist/build-info.json +5 -5
  17. package/dist/cli.js +17 -28
  18. package/dist/config.d.ts +29 -6
  19. package/dist/config.js +320 -40
  20. package/dist/creation.d.ts +1 -1
  21. package/dist/creation.js +2 -2
  22. package/dist/docs.d.ts +1 -1
  23. package/dist/docs.js +51 -95
  24. package/dist/doctor.js +1 -8
  25. package/dist/fleet-proxy.d.ts +1 -1
  26. package/dist/generated-agent-source.d.ts +9 -0
  27. package/dist/generated-agent-source.js +53 -0
  28. package/dist/harness/acp-session-transport.d.ts +8 -0
  29. package/dist/harness/acp-session-transport.js +3 -0
  30. package/dist/harness/agent-session.d.ts +46 -0
  31. package/dist/harness/agent-session.js +1 -0
  32. package/dist/harness/claude-code-session.d.ts +22 -0
  33. package/dist/harness/claude-code-session.js +32 -0
  34. package/dist/harness/claude-code.d.ts +2 -1
  35. package/dist/harness/claude-code.js +39 -81
  36. package/dist/harness/codex-session.d.ts +22 -0
  37. package/dist/harness/codex-session.js +32 -0
  38. package/dist/harness/codex.d.ts +2 -1
  39. package/dist/harness/codex.js +49 -45
  40. package/dist/harness/registry.js +4 -0
  41. package/dist/harness/types.d.ts +6 -38
  42. package/dist/index.d.ts +3 -5
  43. package/dist/index.js +1 -3
  44. package/dist/init-guidance.d.ts +1 -0
  45. package/dist/init-guidance.js +5 -0
  46. package/dist/isolation/resources.d.ts +3 -7
  47. package/dist/isolation/resources.js +3 -7
  48. package/dist/model-env.d.ts +2 -4
  49. package/dist/model-env.js +4 -7
  50. package/dist/monitor.d.ts +1 -24
  51. package/dist/monitor.js +5 -140
  52. package/dist/ops.js +4 -2
  53. package/dist/owner-channel/channel.d.ts +2 -2
  54. package/dist/resolved-plan.d.ts +3 -2
  55. package/dist/resolved-plan.js +21 -5
  56. package/dist/runner.d.ts +5 -15
  57. package/dist/runner.js +26 -102
  58. package/dist/sensitive-config.d.ts +2 -0
  59. package/dist/sensitive-config.js +4 -0
  60. package/dist/session/acp.d.ts +2 -2
  61. package/dist/session/acp.js +2 -3
  62. package/dist/session/activity.d.ts +2 -3
  63. package/dist/session/activity.js +2 -3
  64. package/dist/session/arbiter.d.ts +4 -4
  65. package/dist/session/control.d.ts +3 -3
  66. package/dist/session/types.d.ts +11 -5
  67. package/dist/spawn.d.ts +4 -4
  68. package/dist/spawn.js +60 -32
  69. package/dist/supervisor/none.d.ts +1 -1
  70. package/dist/supervisor/none.js +2 -2
  71. package/dist/tmux.d.ts +1 -14
  72. package/dist/tmux.js +1 -51
  73. package/dist/watchdog/config.js +6 -4
  74. package/dist/watchdog/run.js +8 -23
  75. package/dist/web/auth.d.ts +1 -1
  76. package/dist/web/fleet-config-service.d.ts +11 -8
  77. package/dist/web/fleet-config-service.js +383 -183
  78. package/dist/web/runtime.js +4 -15
  79. package/dist/web/server.d.ts +1 -3
  80. package/dist/web/server.js +3 -14
  81. package/dist/web/topology-promote.js +18 -18
  82. package/dist/web/topology.js +2 -2
  83. package/dist/web/yaml-document-edit.js +2 -0
  84. package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
  85. package/dist/web-app/assets/index-BbE9EX6n.js +10 -0
  86. package/dist/web-app/index.html +2 -2
  87. package/examples/fleet/agents/Alice.yaml +11 -0
  88. package/examples/fleet/agents/FleetCoordinator.yaml +7 -0
  89. package/examples/fleet/brains/claude-default.yaml +5 -0
  90. package/examples/fleet/roles/coordinator.yaml +5 -0
  91. package/examples/fleet/roles/developer.yaml +3 -0
  92. package/examples/fleet.yaml +74 -0
  93. package/package.json +4 -8
  94. package/dist/session/tmux.d.ts +0 -28
  95. package/dist/session/tmux.js +0 -80
  96. package/dist/web/terminal/bridge.d.ts +0 -27
  97. package/dist/web/terminal/bridge.js +0 -317
  98. package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
  99. package/dist/web-app/assets/index-BCBK78hw.js +0 -10
  100. package/dist/web-app/assets/index-DuC-xnX4.css +0 -1
package/dist/runner.js CHANGED
@@ -5,16 +5,13 @@ import { parse } from 'yaml';
5
5
  import { agentDir, stateRoot } from './paths.js';
6
6
  import { loadConfig, findRole, isolationContextFor, resolveMonitorConfig, resolvePermissions, } from './config.js';
7
7
  import { getAdapter } from './harness/registry.js';
8
- import { Tmux } from './tmux.js';
9
8
  import { createMonitor, probeIdentityPresence, } from './monitor.js';
10
- import { realExec, shq } from './exec.js';
9
+ import { realExec } from './exec.js';
11
10
  import { resolveIsolation } from './isolation/policy.js';
12
11
  import { selectIsolationBackend } from './isolation/registry.js';
13
12
  import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
14
13
  import { resolveLaunchRuntime } from './isolation/runtime.js';
15
- import { AcpSession } from './session/acp.js';
16
14
  import { controlRequest, RoleControlServer } from './session/control.js';
17
- import { TmuxSession } from './session/tmux.js';
18
15
  import { ACP_CANCEL_DEADLINE_EXCEEDED, classifyShellStatus } from './session/types.js';
19
16
  import { effectiveModelForRole, modelRecoveryHeld, reconcileModelRecovery, recordModelFailure, classifyFailureText, } from './model-recovery.js';
20
17
  import { rotateWorklog } from './worklog.js';
@@ -28,7 +25,6 @@ import { effectivePermissionMode } from './permissions.js';
28
25
  import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
29
26
  import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
30
27
  const defaultDeps = () => ({
31
- tmux: new Tmux(),
32
28
  exec: realExec,
33
29
  cpuDelegated: () => cpuControllerDelegated(),
34
30
  isAlive: pid => { try {
@@ -44,7 +40,7 @@ const defaultDeps = () => ({
44
40
  fetch: (url, init) => globalThis.fetch(url, init),
45
41
  createMonitor: opts => createMonitor(opts),
46
42
  createOwnerChannel: opts => new OwnerChannel(opts),
47
- startAcpSession: opts => AcpSession.start(opts),
43
+ startAgentSession: (adapter, options) => adapter.start(options),
48
44
  createControlServer: (stateDir, session, log) => new RoleControlServer(stateDir, session, log),
49
45
  acquireOwnerBinder: (stateDir, role, identity) => acquireOwnerBinderLease(stateDir, role, identity),
50
46
  reportOwnerStartupFailure: async (stateDir) => {
@@ -133,38 +129,10 @@ export function recordMonitorOwner(dir, owner) {
133
129
  catch { /* ownership diagnostics must never take the role down */ }
134
130
  return owner === 'fleet' && previous === 'native';
135
131
  }
136
- /**
137
- * Compose the tmux pane shell command: env prefix + argv + exit-status capture.
138
- * `paneArgv` defaults to `launch.argv`; when isolation is active the caller passes
139
- * the sandbox-wrapped argv (e.g. `bwrap … -- claude …`). The `env` prefix and the
140
- * `echo $? > exitfile` capture stay host-side, outside the sandbox, so the runner
141
- * still sees the real exit code.
142
- */
143
- export function buildPaneCommand(launch, roleEnv, exitStatusPath, paneArgv = launch.argv) {
144
- const env = {
145
- PATH: process.env.PATH ?? '', COLORTERM: 'truecolor', ...launch.env, ...(roleEnv ?? {}),
146
- };
147
- delete env[OBSOLETE_OURS_AUTOSTART_ENV];
148
- // Interactive panes should advertise colour even when the supervisor itself
149
- // was launched with NO_COLOR. A role may still deliberately opt back in to
150
- // NO_COLOR (or replace COLORTERM) through its explicit env block.
151
- const unsetNoColor = Object.prototype.hasOwnProperty.call(roleEnv ?? {}, 'NO_COLOR')
152
- ? '' : '-u NO_COLOR ';
153
- const envPfx = 'env -u OURS_AUTOSTART ' + unsetNoColor
154
- + Object.entries(env).map(([k, v]) => `${k}=${shq(v)}`).join(' ');
155
- const cmd = paneArgv.map(shq).join(' ');
156
- // Write a structured record, not a bare number: the wait status alone cannot
157
- // say whether the file is missing because the program never exited or because
158
- // nothing ever wrote it. `printf` is POSIX; no shell branching is needed
159
- // because classification happens in one place, in TypeScript.
160
- const record = `'{"version":1,"backend":"tmux","status":'"$__ofs"'}'`;
161
- return `${envPfx} ${cmd}; __ofs=$?; printf %s ${record} > ${shq(exitStatusPath)}`;
162
- }
163
132
  /** Adapt runner deps and the role's daemon-profile overrides for the monitor. */
164
133
  function monitorDeps(deps, roleEnv) {
165
134
  return {
166
135
  fetch: deps.fetch,
167
- tmux: deps.tmux,
168
136
  isAlive: deps.isAlive,
169
137
  sleep: deps.sleep,
170
138
  now: deps.now,
@@ -484,16 +452,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
484
452
  writeFileSync(bootedFile, `${new Date(deps.now()).toISOString()} ${mode}\n`);
485
453
  const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
486
454
  const prep = await adapter.prepareSession(role, { stateDir: dir, runCwd });
487
- const sessionBackend = role.session ?? 'tmux';
488
- let launch = sessionBackend === 'acp'
489
- ? (() => {
490
- if (!adapter.buildAcpLaunch)
491
- throw new Error(`harness '${role.harness}' does not support the ACP session backend`);
492
- return adapter.buildAcpLaunch(role, prep);
493
- })()
494
- : adapter.buildLaunch(role, mode, { sessionId }, prep);
495
- // Isolation is additive: only roles that declare `isolation:` are wrapped. The
496
- // env prefix + exit capture in buildPaneCommand stay host-side.
455
+ const sessionBackend = role.session ?? 'acp';
456
+ let launch = adapter.agentSession.prepareLaunch(role, prep);
457
+ // Isolation is additive: only roles that declare `isolation:` are wrapped.
497
458
  let wrappedArgv = launch.argv;
498
459
  if (role.isolation) {
499
460
  // Start with the same durable context that config validation and doctor judged,
@@ -530,7 +491,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
530
491
  // user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
531
492
  // hit the harness/API rate limit at once. Time-based via a shared launch gate, so
532
493
  // a lone start or a solo crash-restart waits zero. Applied right before the harness
533
- // launch (tmux.newSession); the cheap monitor prime still runs immediately after.
494
+ // agent-session start; the cheap monitor prime still runs immediately after.
534
495
  if (staggerMs > 0) {
535
496
  const slot = await reserveLaunchSlot(stateRoot(), staggerMs, deps);
536
497
  const wait = slot - deps.now();
@@ -575,7 +536,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
575
536
  await monitor.prime({ resetCursor: resetMonitorCursor });
576
537
  rmSync(exitFile, { force: true });
577
538
  let pid;
578
- let acpSession;
539
+ let agentSession;
579
540
  let control;
580
541
  let unsubscribeRecovery;
581
542
  let monitorLoop;
@@ -590,7 +551,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
590
551
  let loopGeneration = JSON.stringify((role.loops ?? []).map(loop => [
591
552
  loop.name, loop.definitionHash, loop.promptHash,
592
553
  ]));
593
- if (sessionBackend === 'acp') {
554
+ {
594
555
  const perms = role.permissions ?? resolvePermissions(undefined, undefined);
595
556
  // Say once, at startup, that this role will decide permission requests by
596
557
  // itself. Without it the only trace of an auto-denied tool call is a turn
@@ -598,34 +559,16 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
598
559
  if (perms.unattended === 'deny')
599
560
  deps.log(`[${name}] permission policy: unattended=deny — with no console attached, ` +
600
561
  `permission requests are automatically denied once each (reject_once) and the turn continues`);
601
- acpSession = await deps.startAcpSession({
602
- name,
603
- argv: wrappedArgv,
604
- cwd: runCwd,
605
- env: harnessChildEnv(role, launch.env, dir),
606
- stateDir: dir,
607
- mode,
608
- permissions: perms,
609
- modeId: adapter.acpPermissionModeId?.(role),
610
- // The role's declared MCP servers, and the bundled agent's `_meta`
611
- // vocabulary for the options it takes no flag for. Both come from the
612
- // ADAPTER and from `prep`: the ACP launch cannot carry `prep.argv`, so this
613
- // is the route by which harness_options that used to be silently dropped
614
- // for an ACP role actually reach the session.
615
- mcpServers: adapter.acpMcpServers?.(role),
616
- sessionMeta: adapter.acpSessionMeta?.(role, prep),
617
- permissionMode: effectivePermissionMode(role),
618
- // Provenance travels with the exact ACP launch. Keeping it out of a
619
- // role-only adapter hook prevents a PATH fallback or resolver skew from
620
- // claiming metadata trust for an argv it did not authenticate.
621
- permissionMetadataSource: launch.permissionMetadataSource,
622
- scrubObsoleteOursAutostart: true,
623
- log: deps.log,
562
+ agentSession = await deps.startAgentSession(adapter.agentSession, {
563
+ role, prep,
564
+ launch: { ...launch, argv: wrappedArgv, env: harnessChildEnv(role, launch.env, dir) },
565
+ cwd: runCwd, stateDir: dir, mode, permissions: perms,
566
+ permissionMode: effectivePermissionMode(role), log: deps.log,
624
567
  });
625
- pid = acpSession.pid;
626
- arbiter = new RoleTurnArbiter(acpSession);
568
+ pid = agentSession.pid;
569
+ arbiter = new RoleTurnArbiter(agentSession);
627
570
  sessionHandle = arbiter;
628
- unsubscribeRecovery = acpSession.subscribe(event => {
571
+ unsubscribeRecovery = agentSession.subscribe(event => {
629
572
  if (event.kind !== 'error' || !event.text)
630
573
  return;
631
574
  const evidence = classifyFailureText(event.text, 'acp', new Date(deps.now()).toISOString());
@@ -647,7 +590,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
647
590
  + `${notifyError?.message ?? String(notifyError)}`);
648
591
  }
649
592
  }
650
- await acpSession.close();
593
+ await agentSession.close();
651
594
  unsubscribeRecovery?.();
652
595
  throw new Error(`[${name}] owner channel failed to start: `
653
596
  + `${error?.message ?? String(error)}`);
@@ -659,7 +602,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
659
602
  }
660
603
  catch (error) {
661
604
  ownerBinder?.release();
662
- await acpSession.close();
605
+ await agentSession.close();
663
606
  unsubscribeRecovery?.();
664
607
  throw error;
665
608
  }
@@ -740,7 +683,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
740
683
  const started = await starting;
741
684
  // A temporary role's first turn can be the active turn when an ours wake
742
685
  // needs immediate attention. A typed console/monitor cancellation ends
743
- // only that turn: the already-live ACP session and any queued wake remain
686
+ // only that turn: the already-live agent session and any queued wake remain
744
687
  // valid. Keep every unproven cancellation, refusal, shutdown, and genuine
745
688
  // failure terminal so a role that never accepted its briefing is not
746
689
  // silently reported as healthy.
@@ -749,7 +692,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
749
692
  monitor?.stop();
750
693
  await control.close();
751
694
  ownerBinder?.release();
752
- await acpSession.close();
695
+ await agentSession.close();
753
696
  unsubscribeRecovery?.();
754
697
  if (modelRecovery) {
755
698
  if (monitorLoop)
@@ -795,7 +738,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
795
738
  await ownerChannel.close().catch(() => undefined);
796
739
  await control.close();
797
740
  ownerBinder?.release();
798
- await acpSession.close();
741
+ await agentSession.close();
799
742
  unsubscribeRecovery?.();
800
743
  throw new Error(`[${name}] owner channel failed to start: `
801
744
  + `${error?.message ?? String(error)}`);
@@ -854,20 +797,6 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
854
797
  }
855
798
  }
856
799
  }
857
- else {
858
- await deps.tmux.kill(name);
859
- await deps.tmux.newSession(name, runCwd, buildPaneCommand(launch, role.env, exitFile, wrappedArgv));
860
- let panePid = null;
861
- for (let i = 0; i < 40 && panePid === null; i++) {
862
- panePid = await deps.tmux.panePid(name);
863
- if (panePid === null)
864
- await deps.sleep(250);
865
- }
866
- if (panePid === null)
867
- throw new Error(`[${name}] could not resolve tmux pane pid`);
868
- pid = panePid;
869
- sessionHandle = new TmuxSession(name, pid, deps.tmux, deps.isAlive);
870
- }
871
800
  deps.log(`[${name}] up; pid=${pid} cwd=${runCwd} harness=${role.harness} session=${sessionBackend} mode=${mode}`);
872
801
  // The monitor loop lives exactly as long as the session: it starts once the
873
802
  // pane pid is known and is stopped when that pid dies (task dies with runner).
@@ -899,7 +828,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
899
828
  else if (presence.state === 'absent' && identityObserved) {
900
829
  identityAbsentSince ??= now;
901
830
  // Require a continuous, time-bounded run of authoritative absence.
902
- // The first positive observation is the readiness gate: cold tmux
831
+ // The first positive observation is the readiness gate: cold harness
903
832
  // starts may spend minutes loading the harness and briefing before the
904
833
  // agent creates/binds its identity, and absence before then is not a
905
834
  // close event. After readiness, debounce a real disappearance.
@@ -948,19 +877,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
948
877
  await monitorLoop;
949
878
  }
950
879
  unsubscribeRecovery?.();
951
- if (acpSession && !sessionClosed)
952
- await acpSession.close();
880
+ if (agentSession && !sessionClosed)
881
+ await agentSession.close();
953
882
  const elapsed = (deps.now() - start) / 1000;
954
883
  // Establish what actually happened before deciding anything. Absence of a
955
884
  // record is `unknown` — except when the console itself is gone, which is a
956
885
  // different event with a different consequence.
957
- const exitRecord = acpSession
958
- ? acpSession.exitResult()
959
- ?? { version: 1, class: 'unknown', detail: 'the ACP agent stopped without reporting an exit' }
960
- : readExitRecord(exitFile)
961
- ?? (await deps.tmux.has(name)
962
- ? { version: 1, class: 'unknown', detail: 'the pane process ended without writing an exit record' }
963
- : { version: 1, class: 'session-destroyed', detail: `the tmux session '${name}' no longer exists` });
886
+ const exitRecord = agentSession.exitResult()
887
+ ?? { version: 1, class: 'unknown', detail: 'the agent session stopped without reporting an exit' };
964
888
  writeFileSync(exitFile, JSON.stringify({
965
889
  ...exitRecord, at: new Date(deps.now()).toISOString(), elapsedSecs: Number(elapsed.toFixed(1)),
966
890
  }) + '\n');
@@ -0,0 +1,2 @@
1
+ /** Shared recursive configuration redaction boundary for operator-facing models. */
2
+ export declare function isSensitiveConfigKey(key: string): boolean;
@@ -0,0 +1,4 @@
1
+ /** Shared recursive configuration redaction boundary for operator-facing models. */
2
+ export function isSensitiveConfigKey(key) {
3
+ return /(?:secret|token|password|credential|private[_-]?key|auth|invite|api[_-]?key)/i.test(key);
4
+ }
@@ -3,7 +3,7 @@ import type { CommonPermissions } from '../config.js';
3
3
  import type { AcpMcpServer } from '../harness/types.js';
4
4
  import { ConversationEventStore } from './conversation-store.js';
5
5
  import type { ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
6
- import type { ConversationHandlePage, ExitRecord, InterruptOutcome, QueuedPrompt, SessionEvent, RuntimeSelectorMetadata, SessionHandle, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnOutcome, TurnResult } from './types.js';
6
+ import type { ConversationHandlePage, ExitRecord, InterruptOutcome, QueuedPrompt, SessionEvent, AgentSession, RuntimeSelectorMetadata, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnOutcome, TurnResult } from './types.js';
7
7
  /** Bound safe-boundary waiting without turning a hung tool into cancellation. */
8
8
  export declare const AFTER_TOOL_BOUNDARY_TIMEOUT_MS = 120000;
9
9
  /**
@@ -81,7 +81,7 @@ export declare function classifyStopReason(stopReason: string | undefined): Turn
81
81
  * Persistent ACP v1 client. It is the sole owner of the agent's stdio; all
82
82
  * human/automation attachment happens through the fleet role-control protocol.
83
83
  */
84
- export declare class AcpSession implements SessionHandle {
84
+ export declare class AcpSession implements AgentSession {
85
85
  private readonly options;
86
86
  readonly backend: "acp";
87
87
  readonly pid: number;
@@ -281,8 +281,7 @@ export class AcpSession {
281
281
  clearTimeout(this.cancelForceKill);
282
282
  this.cancelForceKill = undefined;
283
283
  this.releaseSteeringOccupancy('adapter exited');
284
- // Record the child's real exit code/signal. The tmux path can only see a
285
- // shell's `$?`; here the truth is available, so keep it.
284
+ // Record the child's real exit code/signal while the truth is available.
286
285
  const classified = classifyChildExit(code, signal);
287
286
  this.exit = this.cancelRecoveryReason
288
287
  ? { ...classified, detail: `${this.cancelRecoveryReason}; ${classified.detail}` }
@@ -1476,7 +1475,7 @@ export class AcpSession {
1476
1475
  ...(normalized.adapterMeta ? { adapterMeta: normalized.adapterMeta } : {}),
1477
1476
  });
1478
1477
  }
1479
- // ── conversation ledger access (SessionHandle) ─────────────────────────────
1478
+ // ── conversation ledger access (AgentSession) ─────────────────────────────
1480
1479
  conversationPage(request = {}) {
1481
1480
  const floor = Number(this.conversationStartCursor ?? 0);
1482
1481
  const requested = Number(request.after ?? 0);
@@ -18,9 +18,8 @@ export interface ObservedActivity {
18
18
  lastUpdateAt?: string;
19
19
  }
20
20
  /**
21
- * Classify agent-side activity. `unobservable` is NOT `quiet`: a backend that
22
- * cannot see the agent (tmux) has no evidence, and no evidence must never be
23
- * reported as "doing nothing".
21
+ * Classify agent-side activity. `unobservable` is NOT `quiet`: absent evidence
22
+ * must never be reported as "doing nothing".
24
23
  */
25
24
  export declare function classifyActivity(activity: SessionActivity | undefined, now?: number): ObservedActivity;
26
25
  /**
@@ -11,9 +11,8 @@
11
11
  */
12
12
  export const ACTIVITY_WINDOW_MS = 60_000;
13
13
  /**
14
- * Classify agent-side activity. `unobservable` is NOT `quiet`: a backend that
15
- * cannot see the agent (tmux) has no evidence, and no evidence must never be
16
- * reported as "doing nothing".
14
+ * Classify agent-side activity. `unobservable` is NOT `quiet`: absent evidence
15
+ * must never be reported as "doing nothing".
17
16
  */
18
17
  export function classifyActivity(activity, now = Date.now()) {
19
18
  if (!activity)
@@ -1,4 +1,4 @@
1
- import type { ConversationHandlePage, ExitRecord, InterruptOutcome, PromptOrigin, QueuedPrompt, SessionEvent, SessionHandle, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnResult } from './types.js';
1
+ import type { ConversationHandlePage, ExitRecord, InterruptOutcome, PromptOrigin, QueuedPrompt, SessionEvent, AgentSession, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnResult } from './types.js';
2
2
  import type { ConversationEventV1, ConversationSnapshot, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
3
3
  export type ScheduledAttempt = {
4
4
  state: 'started';
@@ -15,9 +15,9 @@ export type ScheduledAttempt = {
15
15
  * producers retain ACP queue semantics while making their unsettled claim
16
16
  * visible before another producer can inspect idle state.
17
17
  */
18
- export declare class RoleTurnArbiter implements SessionHandle {
18
+ export declare class RoleTurnArbiter implements AgentSession {
19
19
  private readonly session;
20
- readonly backend: import("../config.js").SessionBackendId;
20
+ readonly backend: "acp";
21
21
  readonly pid: number;
22
22
  private tail;
23
23
  private unsettled;
@@ -26,7 +26,7 @@ export declare class RoleTurnArbiter implements SessionHandle {
26
26
  private generation;
27
27
  /** Resolves the instant the CURRENT generation is retired. */
28
28
  private retirement;
29
- constructor(session: SessionHandle);
29
+ constructor(session: AgentSession);
30
30
  /**
31
31
  * Waiters race the tail against their own generation's retirement, so a
32
32
  * single operation that never settles cannot own the boundary forever. A
@@ -1,5 +1,5 @@
1
1
  import { type Socket } from 'node:net';
2
- import type { ControlFailureKind, SessionEvent, SessionHandle, SessionSnapshot } from './types.js';
2
+ import type { AgentSession, ControlFailureKind, SessionEvent, SessionSnapshot } from './types.js';
3
3
  import type { OwnerChannelHandle, OwnerChannelManagementRequest } from '../owner-channel/channel.js';
4
4
  import type { ScheduledLoopManagerHandle } from '../loops/manager.js';
5
5
  import type { SpawnOpts } from '../spawn.js';
@@ -48,7 +48,7 @@ export interface RetainedEventPage {
48
48
  truncated: boolean;
49
49
  }
50
50
  /** The one retained-range projection shared by polling and live-follow admission. */
51
- export declare function retainedEventPage(session: SessionHandle, since: number): RetainedEventPage;
51
+ export declare function retainedEventPage(session: AgentSession, since: number): RetainedEventPage;
52
52
  /**
53
53
  * One line saying what a control failure does — and does not — prove about the
54
54
  * agent. Only `offline` is evidence that it is gone; every other kind used to
@@ -105,7 +105,7 @@ export declare class RoleControlServer {
105
105
  private loopManager?;
106
106
  private reloadConfig?;
107
107
  private fleetSpawner?;
108
- constructor(stateDir: string, session: SessionHandle, log: (line: string) => void);
108
+ constructor(stateDir: string, session: AgentSession, log: (line: string) => void);
109
109
  start(): Promise<void>;
110
110
  close(): Promise<void>;
111
111
  /** Attach only the already-started supervisor-owned channel client. */
@@ -96,7 +96,7 @@ export interface InterruptOutcome {
96
96
  reasonCode?: string;
97
97
  }
98
98
  /**
99
- * What a `SessionHandle.interrupt` implementation may resolve. Before 0.17.1 the
99
+ * What an `AgentSession.interrupt` implementation may resolve. Before 0.17.1 the
100
100
  * contract was `Promise<void>`, and resolving at all meant the cancellation had
101
101
  * taken effect cooperatively — so an implementation written against that
102
102
  * contract stays valid and keeps its exact meaning. Only in-tree consumers read
@@ -146,7 +146,7 @@ export interface ExitRecord {
146
146
  code?: number;
147
147
  /** Signal that killed it, when one did. */
148
148
  signal?: string;
149
- /** Raw wait status as the pane shell saw it (tmux only). */
149
+ /** Raw wait status retained when migrating a legacy shell-exit record. */
150
150
  status?: number;
151
151
  at?: string;
152
152
  /** One line an operator can read. */
@@ -194,8 +194,7 @@ export interface SessionSnapshot {
194
194
  };
195
195
  /**
196
196
  * Observed agent activity, independent of turn occupancy: the evidence a
197
- * human-facing surface needs before calling a role idle. Absent on backends
198
- * that cannot observe the agent at all (tmux), which is itself honest — no
197
+ * human-facing surface needs before calling a role idle. When absent, no
199
198
  * evidence is not evidence of inactivity.
200
199
  */
201
200
  activity?: SessionActivity;
@@ -259,7 +258,12 @@ export interface ConversationHandlePage {
259
258
  hasMore: boolean;
260
259
  snapshot: ConversationSnapshot;
261
260
  }
262
- export interface SessionHandle {
261
+ /**
262
+ * The live, harness-neutral contract between Fleet and one agent session.
263
+ * Harness adapters construct this handle; orchestration code must not depend
264
+ * on the ACP transport which currently implements it.
265
+ */
266
+ export interface AgentSession {
263
267
  readonly backend: SessionBackendId;
264
268
  readonly pid: number;
265
269
  isAlive(): boolean;
@@ -301,3 +305,5 @@ export interface SessionHandle {
301
305
  exitResult(): ExitRecord | null;
302
306
  close(): Promise<void>;
303
307
  }
308
+ /** @deprecated Public compatibility name; use AgentSession. */
309
+ export type SessionHandle = AgentSession;
package/dist/spawn.d.ts CHANGED
@@ -85,17 +85,17 @@ export declare const effectiveIdentity: (o: SpawnOpts) => string;
85
85
  export interface SpawnDryRun {
86
86
  schemaVersion: 1;
87
87
  warning: string;
88
- roleDocument: {
89
- roles: Record<string, RoleConfig>;
90
- };
88
+ roleDocument: Record<string, unknown>;
91
89
  resolvedRole: ResolvedRole;
92
90
  }
91
+ /** Bare Agent document written by v2 spawn: inline Role × inline Brain + operations. */
92
+ export declare function buildAgentDocument(raw: RoleConfig): Record<string, unknown>;
93
93
  /**
94
94
  * Validate and resolve a spawn without reserving names, contacting the daemon,
95
95
  * or writing state. Collision checks are necessarily a point-in-time snapshot.
96
96
  */
97
97
  export declare function spawnDryRun(o: SpawnOpts): SpawnDryRun;
98
- /** Permanent spawn: persist to ~/fleet.d/<Name>.yaml, then bring it up. */
98
+ /** Permanent spawn: persist one bare Agent document under the selected v2 root. */
99
99
  export declare function spawnPermanent(o: SpawnOpts, deps: OpsDeps, creation?: CreationDeps): Promise<string>;
100
100
  export type { SupervisorLauncher } from './temp-lifecycle.js';
101
101
  /** Temp spawn: live state under ~/.ours-fleet/tmp, independent transient supervision. */