@ours.network/fleet 0.15.3 → 0.15.5

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 (64) hide show
  1. package/README.md +54 -19
  2. package/dist/application/fleet-query-service.js +3 -0
  3. package/dist/application/model-catalog.d.ts +20 -0
  4. package/dist/application/model-catalog.js +57 -0
  5. package/dist/application/role-creation-service.d.ts +7 -0
  6. package/dist/application/role-creation-service.js +21 -4
  7. package/dist/application/role-removal-service.d.ts +32 -0
  8. package/dist/application/role-removal-service.js +87 -0
  9. package/dist/application/role-repository.js +13 -1
  10. package/dist/application/session-control.d.ts +74 -0
  11. package/dist/application/session-control.js +66 -1
  12. package/dist/application/types.d.ts +18 -0
  13. package/dist/briefing.js +10 -1
  14. package/dist/cli.js +1 -1
  15. package/dist/config.d.ts +4 -1
  16. package/dist/config.js +3 -2
  17. package/dist/docs.d.ts +1 -1
  18. package/dist/docs.js +14 -5
  19. package/dist/fleet-proxy.js +3 -1
  20. package/dist/harness/claude-code.js +20 -3
  21. package/dist/harness/codex.js +14 -2
  22. package/dist/harness/types.d.ts +6 -1
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.js +1 -0
  25. package/dist/owner-channel/attachments.d.ts +11 -1
  26. package/dist/owner-channel/attachments.js +24 -3
  27. package/dist/owner-channel/channel.d.ts +2 -0
  28. package/dist/owner-channel/channel.js +134 -12
  29. package/dist/owner-channel/state.d.ts +1 -0
  30. package/dist/owner-channel/state.js +6 -3
  31. package/dist/permissions.d.ts +5 -0
  32. package/dist/permissions.js +7 -0
  33. package/dist/runner.js +2 -0
  34. package/dist/session/acp.d.ts +66 -1
  35. package/dist/session/acp.js +427 -22
  36. package/dist/session/arbiter.d.ts +10 -1
  37. package/dist/session/arbiter.js +24 -0
  38. package/dist/session/control.d.ts +28 -2
  39. package/dist/session/control.js +145 -5
  40. package/dist/session/conversation-normalizer.d.ts +34 -0
  41. package/dist/session/conversation-normalizer.js +356 -0
  42. package/dist/session/conversation-store.d.ts +88 -0
  43. package/dist/session/conversation-store.js +347 -0
  44. package/dist/session/conversation-types.d.ts +274 -0
  45. package/dist/session/conversation-types.js +1 -0
  46. package/dist/session/events.js +6 -1
  47. package/dist/session/types.d.ts +49 -0
  48. package/dist/spawn.d.ts +1 -0
  49. package/dist/spawn.js +9 -4
  50. package/dist/web/auth.d.ts +1 -1
  51. package/dist/web/fleet-config-service.d.ts +47 -0
  52. package/dist/web/fleet-config-service.js +204 -0
  53. package/dist/web/runtime.js +14 -1
  54. package/dist/web/server.d.ts +6 -0
  55. package/dist/web/server.js +181 -9
  56. package/dist/web/topology.d.ts +31 -0
  57. package/dist/web/topology.js +61 -0
  58. package/dist/web-app/assets/{TerminalView-DMoT8udI.js → TerminalView-hZpyUFY_.js} +1 -1
  59. package/dist/web-app/assets/index-COg4Azq1.css +1 -0
  60. package/dist/web-app/assets/index-Cde9auW0.js +10 -0
  61. package/dist/web-app/index.html +2 -2
  62. package/package.json +1 -1
  63. package/dist/web-app/assets/index-B-jtLAkp.css +0 -1
  64. package/dist/web-app/assets/index-B6T8JLSd.js +0 -9
@@ -10,7 +10,7 @@ import { OursMcpClient } from './mcp.js';
10
10
  import { ownerNotices } from './notices.js';
11
11
  import { DuplicateSendError, OwnerAuthorizationState, OwnerChannelState, OwnerConversationState, } from './state.js';
12
12
  import { OwnerTaskState, ownerTaskAuditId, ownerTaskDigest, } from './tasks.js';
13
- import { AttachmentRecoveryState, admitAttachments, cleanupAttachmentRoot, parseIncomingAttachments, parseRetrievedAttachments, prepareAttachmentDirectory, recoveredAttachment, removeRequestDirectory, safeField, validateAttachmentSelection, } from './attachments.js';
13
+ import { AttachmentRecoveryState, admitAttachments, cleanupAttachmentRoot, parseIncomingAttachments, parseRetrievedAttachments, prepareAttachmentDirectory, recoveredAttachment, removeRequestDirectory, safeField, validateAttachmentSelection, validateAttachmentRelaySelection, } from './attachments.js';
14
14
  import { acquireOwnerBinderLease, OWNER_BIND_HANDOFF_TIMEOUT_MS, } from './binder.js';
15
15
  const OWNER_UPDATE_MIN_INTERVAL_MS = 5_000;
16
16
  const OWNER_UPDATE_MAX_COUNT = 20;
@@ -19,6 +19,11 @@ const OWNER_UPDATE_MAX_BYTES = 1_024;
19
19
  const PROACTIVE_MESSAGE_MAX_CHARS = 4_000;
20
20
  const PROACTIVE_MESSAGE_MAX_BYTES = 16_384;
21
21
  const RELAY_NACK_MEMORY = 512;
22
+ const COMMENTARY_FLUSH_MS = 750;
23
+ const COMMENTARY_MAX_CHARS = 1_600;
24
+ const COMMENTARY_MAX_BYTES = 6_400;
25
+ const COMMENTARY_MAX_UPDATES = 32;
26
+ const COMMENTARY_DEDUPE_LIMIT = 512;
22
27
  /** A relay attempt that failed only because no owner route exists yet. */
23
28
  class RelayUnroutableError extends Error {
24
29
  }
@@ -671,7 +676,8 @@ export class OwnerChannel {
671
676
  const queued = await this.options.session.queuePrompt(this.ownerAttachmentPrompt(sender, originWireId, requestId, outbox, admitted, group.caption), {
672
677
  interrupt: this.options.config.interrupt,
673
678
  ...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
674
- origin: { kind: 'owner', requestId },
679
+ origin: { kind: 'owner', requestId,
680
+ ...(group.caption ? { displayText: String(group.caption.text ?? '') } : {}) },
675
681
  });
676
682
  const accepted = this.options.config.interrupt
677
683
  ? ownerNotices.receivedInterrupting()
@@ -685,6 +691,8 @@ export class OwnerChannel {
685
691
  const active = {
686
692
  contact: sender.id, wireId: originWireId, requestId, outboundTail: receipt,
687
693
  finalizing: false, updateCount: 0, updateDigests: new Set(), handledWireIds,
694
+ commentaryBuffer: '', commentaryCount: 0, commentaryKeys: new Set(),
695
+ commentaryDisabled: false,
688
696
  };
689
697
  this.activeRequests.set(requestId, active);
690
698
  const cleanupDir = requestDir;
@@ -783,9 +791,9 @@ export class OwnerChannel {
783
791
  // never prevent an authenticated owner from using the ordinary channel.
784
792
  this.logError('owner conversation route update failed', error);
785
793
  }
786
- const text = String(message.text ?? '').trim();
787
- if (isOwnerCommandText(text)) {
788
- await this.handleCommand(sender, text, wireId);
794
+ const text = String(message.text ?? '');
795
+ if (isOwnerCommandText(text.trim())) {
796
+ await this.handleCommand(sender, text.trim(), wireId);
789
797
  return true;
790
798
  }
791
799
  const requestId = this.requestId(wireId);
@@ -797,7 +805,7 @@ export class OwnerChannel {
797
805
  queued = await this.options.session.queuePrompt(this.ownerPrompt(sender, text, wireId, outbox), {
798
806
  interrupt: this.options.config.interrupt,
799
807
  ...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
800
- origin: { kind: 'owner', requestId },
808
+ origin: { kind: 'owner', requestId, displayText: text },
801
809
  });
802
810
  }
803
811
  catch (error) {
@@ -822,6 +830,8 @@ export class OwnerChannel {
822
830
  const active = {
823
831
  contact: sender.id, wireId, requestId, outboundTail: receipt, finalizing: false,
824
832
  updateCount: 0, updateDigests: new Set(), handledWireIds: [wireId],
833
+ commentaryBuffer: '', commentaryCount: 0, commentaryKeys: new Set(),
834
+ commentaryDisabled: false,
825
835
  };
826
836
  this.activeRequests.set(requestId, active);
827
837
  const task = this.complete(active, outbox, queued, activityCursor)
@@ -1006,7 +1016,7 @@ export class OwnerChannel {
1006
1016
  const contact = await this.routableContact(route);
1007
1017
  const rejection = !this.attachmentRecovery.integrity()
1008
1018
  ? 'attachment recovery state is unavailable'
1009
- : validateAttachmentSelection(group.files, this.attachmentConfig);
1019
+ : validateAttachmentRelaySelection(group.files, this.attachmentConfig);
1010
1020
  if (rejection)
1011
1021
  throw new Error(rejection);
1012
1022
  const transactionId = this.requestId(`managed-agent-attachment:${handledWireIds.slice().sort().join(':')}`);
@@ -1033,7 +1043,7 @@ export class OwnerChannel {
1033
1043
  }
1034
1044
  const order = new Map(group.files.map((file, index) => [file.wireId, index]));
1035
1045
  retrieved.sort((a, b) => order.get(a.wireId) - order.get(b.wireId));
1036
- const admitted = await admitAttachments(retrieved, requestDir, this.attachmentConfig);
1046
+ const admitted = await admitAttachments(retrieved, requestDir, this.attachmentConfig, { mimePolicy: 'report-only' });
1037
1047
  const digest = createHash('sha256').update(`managed-agent-attachment\0${handledWireIds.slice().sort().join('\0')}`).digest('hex');
1038
1048
  const sending = this.conversations.beginSend(route.contact, digest, Date.now(), 0, 'all');
1039
1049
  try {
@@ -1196,6 +1206,95 @@ export class OwnerChannel {
1196
1206
  let startedAt;
1197
1207
  let phase = 'starting request';
1198
1208
  let timer;
1209
+ const flushCommentary = () => {
1210
+ if (active.commentaryTimer)
1211
+ clearTimeout(active.commentaryTimer);
1212
+ active.commentaryTimer = undefined;
1213
+ const text = active.commentaryBuffer.trim();
1214
+ active.commentaryBuffer = '';
1215
+ if (!text || active.commentaryDisabled || active.finalizing)
1216
+ return;
1217
+ if (active.commentaryCount >= COMMENTARY_MAX_UPDATES) {
1218
+ active.commentaryDisabled = true;
1219
+ return;
1220
+ }
1221
+ let safe;
1222
+ try {
1223
+ safe = this.safeCommentary(text);
1224
+ }
1225
+ catch (error) {
1226
+ active.commentaryDisabled = true;
1227
+ this.logError('ACP commentary forwarding disabled for unsafe content', error);
1228
+ return;
1229
+ }
1230
+ const digest = createHash('sha256')
1231
+ .update(`owner-commentary\0${active.wireId}\0${safe}`).digest('hex');
1232
+ let sending;
1233
+ try {
1234
+ sending = this.conversations.beginSend(active.contact, digest, Date.now(), 0, 'all');
1235
+ }
1236
+ catch (error) {
1237
+ if (error instanceof DuplicateSendError)
1238
+ return;
1239
+ active.commentaryDisabled = true;
1240
+ this.logError('ACP commentary forwarding disabled by dedupe state', error);
1241
+ return;
1242
+ }
1243
+ active.commentaryCount++;
1244
+ active.outboundTail = active.outboundTail
1245
+ .then(async () => {
1246
+ try {
1247
+ if (!this.isEffectiveOwner(active.contact))
1248
+ throw new Error('initiating owner is no longer authorized');
1249
+ await this.send(active.contact, safe, active.wireId);
1250
+ }
1251
+ catch {
1252
+ this.conversations.finishSend(sending.id, 'uncertain');
1253
+ throw new Error('ACP commentary delivery outcome is uncertain');
1254
+ }
1255
+ this.conversations.finishSend(sending.id, 'delivered');
1256
+ })
1257
+ .catch(error => this.logError('ACP commentary delivery failed', error));
1258
+ };
1259
+ const acceptCommentary = (event) => {
1260
+ if (active.commentaryDisabled || active.finalizing || event.turnId !== queued.promptId
1261
+ || event.kind !== 'agent_text' || event.messagePhase !== 'commentary'
1262
+ || event.replayed || event.origin?.kind !== 'owner'
1263
+ || event.origin.requestId !== active.requestId
1264
+ || typeof event.messageId !== 'string' || !event.messageId
1265
+ || typeof event.text !== 'string' || !event.text)
1266
+ return;
1267
+ const key = createHash('sha256')
1268
+ .update(`${event.messageId}\0${event.text}`).digest('hex');
1269
+ if (active.commentaryKeys.has(key))
1270
+ return;
1271
+ active.commentaryKeys.add(key);
1272
+ if (active.commentaryKeys.size > COMMENTARY_DEDUPE_LIMIT)
1273
+ active.commentaryKeys.delete(active.commentaryKeys.values().next().value);
1274
+ let chars = Array.from(active.commentaryBuffer).length;
1275
+ let bytes = Buffer.byteLength(active.commentaryBuffer);
1276
+ for (const point of event.text) {
1277
+ const pointBytes = Buffer.byteLength(point);
1278
+ if (chars >= COMMENTARY_MAX_CHARS || bytes + pointBytes > COMMENTARY_MAX_BYTES) {
1279
+ flushCommentary();
1280
+ if (active.commentaryDisabled)
1281
+ return;
1282
+ chars = 0;
1283
+ bytes = 0;
1284
+ }
1285
+ active.commentaryBuffer += point;
1286
+ chars++;
1287
+ bytes += pointBytes;
1288
+ }
1289
+ if (/\n\s*\n$/u.test(active.commentaryBuffer)) {
1290
+ flushCommentary();
1291
+ return;
1292
+ }
1293
+ if (!active.commentaryTimer) {
1294
+ active.commentaryTimer = setTimeout(flushCommentary, COMMENTARY_FLUSH_MS);
1295
+ active.commentaryTimer.unref?.();
1296
+ }
1297
+ };
1199
1298
  const reportProgress = () => {
1200
1299
  const events = this.options.session.eventsSince(lastSeq);
1201
1300
  lastSeq = Math.max(lastSeq, this.latestEventSeq(events));
@@ -1234,19 +1333,28 @@ export class OwnerChannel {
1234
1333
  timer = setInterval(reportProgress, progressMs);
1235
1334
  timer.unref();
1236
1335
  };
1237
- const unsubscribe = progressMs > 0
1238
- ? this.options.session.subscribe(event => startProgress(event))
1239
- : undefined;
1336
+ const unsubscribe = typeof this.options.session.subscribe === 'function'
1337
+ ? this.options.session.subscribe(event => {
1338
+ startProgress(event);
1339
+ // Automatic commentary is an ACP phase extension. Other backends and
1340
+ // older adapters retain their established final-only behavior.
1341
+ if (this.options.session.backend === 'acp')
1342
+ acceptCommentary(event);
1343
+ })
1344
+ : () => undefined;
1240
1345
  startProgress();
1241
1346
  let result;
1242
1347
  try {
1243
1348
  result = await queued.completion;
1244
1349
  }
1245
1350
  finally {
1246
- unsubscribe?.();
1351
+ unsubscribe();
1247
1352
  if (timer)
1248
1353
  clearInterval(timer);
1354
+ if (active.commentaryTimer)
1355
+ clearTimeout(active.commentaryTimer);
1249
1356
  }
1357
+ flushCommentary();
1250
1358
  active.finalizing = true;
1251
1359
  await active.outboundTail;
1252
1360
  const output = result.output?.trim();
@@ -1267,6 +1375,20 @@ export class OwnerChannel {
1267
1375
  for (const wire of active.handledWireIds)
1268
1376
  this.state.remember(wire);
1269
1377
  }
1378
+ /** Model-authored commentary only; raw protocol/tool data never reaches here. */
1379
+ safeCommentary(value) {
1380
+ const message = value.trim().normalize('NFC');
1381
+ if (!message)
1382
+ throw new Error('commentary is empty');
1383
+ if (Array.from(message).length > COMMENTARY_MAX_CHARS
1384
+ || Buffer.byteLength(message) > COMMENTARY_MAX_BYTES)
1385
+ throw new Error('commentary batch exceeds its bounded size');
1386
+ if (/\u0000|[\u202a-\u202e\u2066-\u2069]/u.test(message))
1387
+ throw new Error('commentary contains unsafe control characters');
1388
+ if (/-----BEGIN [A-Z ]*PRIVATE KEY-----|(?:api[_ -]?key|access[_ -]?token|authorization|password|secret)\s*[:=]|(?:chain of thought|private reasoning|internal reasoning)|^(?:stdout|stderr|tool (?:output|result)|command):/imu.test(message))
1389
+ throw new Error('commentary appears to contain secret, reasoning, or raw tool content');
1390
+ return message;
1391
+ }
1270
1392
  ownerAttachmentPrompt(sender, wireId, requestId, outbox, files, caption) {
1271
1393
  const lines = [
1272
1394
  '[fleet-owner]',
@@ -41,6 +41,7 @@ export declare class OwnerConversationState {
41
41
  route(effective: Set<string>): {
42
42
  contact: string;
43
43
  basis: OwnerConversationRouteBasis;
44
+ replyToWireId?: string;
44
45
  };
45
46
  routeForWire(wireId: string, effective: Set<string>): {
46
47
  contact: string;
@@ -43,7 +43,7 @@ export class OwnerChannelState {
43
43
  }
44
44
  const CONVERSATION_LIMIT = 64;
45
45
  const WIRE_ROUTE_LIMIT = 512;
46
- const PROACTIVE_SEND_LIMIT = 256;
46
+ const PROACTIVE_SEND_LIMIT = 2_048;
47
47
  const PROACTIVE_MIN_INTERVAL_MS = 30_000;
48
48
  const HEX_64_LOWER = /^[a-f0-9]{64}$/;
49
49
  const CID = /^[A-Fa-f0-9]{64}$/;
@@ -158,7 +158,10 @@ export class OwnerConversationState {
158
158
  if (candidates.length) {
159
159
  if (candidates[1]?.lastInboundAt === candidates[0].lastInboundAt)
160
160
  throw new Error('proactive owner route is ambiguous');
161
- return { contact: candidates[0].contact, basis: 'last-inbound' };
161
+ return {
162
+ contact: candidates[0].contact, basis: 'last-inbound',
163
+ replyToWireId: candidates[0].lastInboundWireId,
164
+ };
162
165
  }
163
166
  if (canonical.size === 1)
164
167
  return { contact: [...effective][0], basis: 'sole-owner' };
@@ -180,7 +183,7 @@ export class OwnerConversationState {
180
183
  const recent = this.sends.filter(send => canonicalCid(send.contact) === canonical).slice(-128);
181
184
  // 'all' scope serves wire-keyed idempotency: a crash replay must not
182
185
  // deliver the same wire to a second owner after the route moved.
183
- const scope = dedupe === 'all' ? this.sends.slice(-128) : recent;
186
+ const scope = dedupe === 'all' ? this.sends : recent;
184
187
  if (scope.some(send => send.digest === digest))
185
188
  throw new DuplicateSendError('duplicate proactive owner message refused');
186
189
  const last = recent.at(-1);
@@ -62,6 +62,11 @@ export interface PermissionConflict {
62
62
  /** The role-named line commands print. */
63
63
  warning: string;
64
64
  }
65
+ /** Resolve the effective portable policy after harness-native overrides win. */
66
+ export declare function effectivePermissionMode(role: ResolvedRole): {
67
+ fleetMode: import('./config.js').FleetPermissionMode;
68
+ nativeMode: string;
69
+ };
65
70
  /** Resolve one role's permissions through its adapter. Never throws. */
66
71
  export declare function analyzeRolePermissions(role: ResolvedRole): RolePermissionAnalysis;
67
72
  /** Every line a command should show for a role: translation, conflicts, floor. */
@@ -23,6 +23,13 @@ export function checkUnattendedFloor(granted, required = UNATTENDED_FLOOR) {
23
23
  const missing = required.filter(c => !granted.includes(c));
24
24
  return { meets: missing.length === 0, missing };
25
25
  }
26
+ /** Resolve the effective portable policy after harness-native overrides win. */
27
+ export function effectivePermissionMode(role) {
28
+ const adapter = getAdapter(role.harness);
29
+ if (!adapter.effectivePermissionMode)
30
+ throw new Error(`harness '${role.harness}' cannot report an effective ask|auto|allow permission mode`);
31
+ return adapter.effectivePermissionMode(role);
32
+ }
26
33
  /**
27
34
  * Find native settings that contradict the neutral block. Only fires when the
28
35
  * operator wrote BOTH — a role that states its intent once, neutrally or
package/dist/runner.js CHANGED
@@ -23,6 +23,7 @@ import { acquireOwnerBinderLease, OwnerBinderHandoffTimeoutError, } from './owne
23
23
  import { RoleTurnArbiter } from './session/arbiter.js';
24
24
  import { ScheduledLoopManager, } from './loops/manager.js';
25
25
  import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, inheritCallerSpawnDefaults, } from './fleet-proxy.js';
26
+ import { effectivePermissionMode } from './permissions.js';
26
27
  const defaultDeps = () => ({
27
28
  tmux: new Tmux(),
28
29
  exec: realExec,
@@ -514,6 +515,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
514
515
  mode,
515
516
  permissions: perms,
516
517
  modeId: adapter.acpPermissionModeId?.(role),
518
+ permissionMode: effectivePermissionMode(role),
517
519
  log: deps.log,
518
520
  });
519
521
  pid = acpSession.pid;
@@ -1,5 +1,11 @@
1
+ import * as acp from '@agentclientprotocol/sdk';
1
2
  import type { CommonPermissions } from '../config.js';
2
- import type { ExitRecord, QueuedPrompt, SessionEvent, SessionHandle, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnOutcome, TurnResult } from './types.js';
3
+ import { ConversationEventStore } from './conversation-store.js';
4
+ import type { ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
5
+ import type { ConversationHandlePage, ExitRecord, QueuedPrompt, SessionEvent, RuntimeSelectorMetadata, SessionHandle, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnOutcome, TurnResult } from './types.js';
6
+ /** Server-generated typed provenance followed by the exact human-authored body. */
7
+ export declare function promptContentBlocks(text: string, origin?: PromptOrigin): acp.ContentBlock[];
8
+ export declare function runtimeSelector(options: acp.SessionConfigOption[] | null | undefined, category: string): RuntimeSelectorMetadata | undefined;
3
9
  export interface AcpSessionOptions {
4
10
  name: string;
5
11
  argv: string[];
@@ -10,9 +16,15 @@ export interface AcpSessionOptions {
10
16
  permissions: CommonPermissions;
11
17
  /** Native permission-mode id to request via session/set_mode; undefined keeps the agent default. */
12
18
  modeId?: string;
19
+ /** Adapter-resolved live permission policy; separate from ACP agent-specific session modes. */
20
+ permissionMode?: NonNullable<SessionSnapshot['permissionMode']>;
13
21
  log(line: string): void;
14
22
  /** Test seam for the cancel-escalation grace period; production uses the default. */
15
23
  cancelGraceMs?: number;
24
+ /** How long a pending permission may wait for a human before it expires. */
25
+ permissionTimeoutMs?: number;
26
+ /** Grace after the last controller detaches before the unattended policy applies. */
27
+ controllerGraceMs?: number;
16
28
  }
17
29
  /**
18
30
  * Classify an ACP `stopReason` into a terminal outcome. A refusal and a
@@ -30,6 +42,11 @@ export declare class AcpSession implements SessionHandle {
30
42
  readonly pid: number;
31
43
  private readonly child;
32
44
  private readonly events;
45
+ private readonly conversation;
46
+ /** New on every runner start; permission/turn IDs from prior generations are stale. */
47
+ private readonly sessionGeneration;
48
+ /** True while `session/load` replays history as ordinary updates. */
49
+ private replaying;
33
50
  private readonly sessionFile;
34
51
  private readonly pendingPermissions;
35
52
  private connection;
@@ -41,11 +58,22 @@ export declare class AcpSession implements SessionHandle {
41
58
  private exit;
42
59
  private steeringSupported;
43
60
  private capabilities?;
61
+ private runtimeModel?;
62
+ private reasoningEffort?;
44
63
  private controllerCount;
64
+ /** Armed when the last controller detaches; unattended policy applies on fire. */
65
+ private controllerGrace?;
45
66
  private cancelEscalation?;
46
67
  private activeTurn?;
47
68
  private constructor();
48
69
  static start(options: AcpSessionOptions): Promise<AcpSession>;
70
+ /**
71
+ * Honest restart recovery (spec §5.3): a prompt that was admitted but never
72
+ * started is safe to dispatch again; a turn that had already started may
73
+ * have caused side effects, so it is closed as `unknown_after_restart` —
74
+ * never silently replayed.
75
+ */
76
+ private recoverOpenPrompts;
49
77
  isAlive(): boolean;
50
78
  snapshot(): SessionSnapshot;
51
79
  /**
@@ -54,16 +82,40 @@ export declare class AcpSession implements SessionHandle {
54
82
  * for it is what turned a busy agent into a timeout and then into "dead".
55
83
  */
56
84
  queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
85
+ /**
86
+ * Durably record a prompt admission BEFORE acceptance is returned. Browser
87
+ * admissions are transactional — a prompt the ledger cannot hold is refused,
88
+ * because an acknowledged-then-lost prompt is worse than an error. Every
89
+ * other source degrades to best-effort so the agent keeps working (§5.3).
90
+ */
91
+ private admitToLedger;
92
+ /** Idempotent browser prompt admission (control v3 `submit_prompt_v2`). */
93
+ submitPromptBrowser(command: SubmitPromptCommand): Promise<PromptReceipt>;
57
94
  submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
58
95
  interrupt(source?: TurnCancellationSource): Promise<void>;
59
96
  private cancelActive;
60
97
  respondPermission(permissionId: string, optionId: string): boolean;
98
+ /**
99
+ * A v2 decision binds to the session generation it was shown under. A stale
100
+ * generation, an already-settled request, or an unknown option are all the
101
+ * same answer: someone else's decision (or a restart) got there first.
102
+ */
103
+ respondPermissionV2(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale';
61
104
  eventsSince(seq: number): SessionEvent[];
62
105
  subscribe(listener: (event: SessionEvent) => void): () => void;
63
106
  setControllerAttached(attached: boolean): void;
107
+ private armControllerGrace;
108
+ /**
109
+ * Settle one pending request without a human decision — unattended policy,
110
+ * expiry, or cancellation — and leave the same durable evidence a manual
111
+ * decision would. A denial selects the agent's own one-shot reject option;
112
+ * everything else resolves as cancelled toward the agent.
113
+ */
114
+ private settlePendingAutomatically;
64
115
  exitResult(): ExitRecord | null;
65
116
  close(): Promise<void>;
66
117
  private initialize;
118
+ private captureRuntimeMetadata;
67
119
  private runPrompt;
68
120
  private steerPrompt;
69
121
  private requestPermission;
@@ -75,5 +127,18 @@ export declare class AcpSession implements SessionHandle {
75
127
  private settleAutomatically;
76
128
  private withinAutomaticBoundary;
77
129
  private recordUpdate;
130
+ /**
131
+ * Codex ACP's phase extension is the only currently supported visibility
132
+ * signal. Never infer commentary from text, message order, or unknown meta.
133
+ */
134
+ private codexMessagePhase;
135
+ /** Normalize every ACP update losslessly into the durable ledger. */
136
+ private recordConversationUpdate;
137
+ conversationPage(request?: {
138
+ after?: string;
139
+ limit?: number;
140
+ }): ConversationHandlePage;
141
+ conversationSnapshot(): ConversationSnapshot;
142
+ subscribeConversation(listener: Parameters<ConversationEventStore['subscribe']>[0]): () => void;
78
143
  private fail;
79
144
  }