@junghanacs/entwurf 0.20.0 → 0.21.0

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 (112) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +6 -5
  3. package/CHANGELOG.md +220 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +202 -60
  6. package/README.md +54 -26
  7. package/VERIFY.md +37 -7
  8. package/docs/acp-backend-rail.md +31 -15
  9. package/docs/external-mcp-host.md +58 -35
  10. package/docs/fresh-cut-policy.md +3 -2
  11. package/docs/setup-clean-host.md +110 -17
  12. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +101 -109
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +6 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +326 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +50 -15
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  29. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  30. package/mcp/entwurf-bridge/src/index.ts +115 -111
  31. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  32. package/package.json +14 -5
  33. package/pi-extensions/entwurf-control.ts +71 -19
  34. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  35. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  36. package/pi-extensions/lib/acp/backend.ts +3 -3
  37. package/pi-extensions/lib/acp/event-mapper.ts +4 -4
  38. package/pi-extensions/lib/acp/tool-surface.ts +6 -0
  39. package/pi-extensions/lib/codex-fresh-preflight.ts +363 -0
  40. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  41. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  42. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  43. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  44. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  45. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  46. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  47. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  48. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  49. package/pi-extensions/lib/mux-fresh-call.ts +64 -19
  50. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  51. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  52. package/pi-extensions/lib/native-push/register.ts +7 -9
  53. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  54. package/run.sh +251 -35
  55. package/scripts/check-acp-usage-accounting.ts +9 -9
  56. package/scripts/check-agy-sender-identity.ts +1 -1
  57. package/scripts/check-codex-birth-hook.ts +264 -0
  58. package/scripts/check-codex-bridge-identity.ts +179 -0
  59. package/scripts/check-codex-native-push.ts +386 -0
  60. package/scripts/check-codex-sender-identity.ts +495 -0
  61. package/scripts/check-compaction-send-guard.ts +130 -0
  62. package/scripts/check-copilot-receive-arm.ts +4 -1
  63. package/scripts/check-entwurf-fact-provider.ts +38 -0
  64. package/scripts/check-entwurf-peers-surface.ts +13 -1
  65. package/scripts/check-entwurf-self-address.ts +15 -16
  66. package/scripts/check-entwurf-v2-contract.ts +4 -3
  67. package/scripts/check-entwurf-v2-decider.ts +7 -5
  68. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  69. package/scripts/check-entwurf-v2-production.ts +203 -11
  70. package/scripts/check-entwurf-v2-runner.ts +1 -1
  71. package/scripts/check-entwurf-v2-surface.ts +1 -1
  72. package/scripts/check-gate-qualification.ts +7 -4
  73. package/scripts/check-harness-admission-parity.ts +0 -1
  74. package/scripts/check-install-surface.ts +23 -7
  75. package/scripts/check-mux-launch-tmux.ts +47 -2
  76. package/scripts/check-native-push-adapter.ts +20 -16
  77. package/scripts/check-native-push-register.ts +5 -1
  78. package/scripts/check-release-gate-outcomes.ts +47 -1
  79. package/scripts/check-setup-qualification.sh +3 -1
  80. package/scripts/codex-birth-doctor.sh +276 -0
  81. package/scripts/codex-birth-install.sh +414 -0
  82. package/scripts/codex-birth-uninstall.sh +170 -0
  83. package/scripts/codex-mcp-config.py +435 -0
  84. package/scripts/codex-statusline-config.py +434 -0
  85. package/scripts/codex_toml_io.py +532 -0
  86. package/scripts/lib/codex-fresh-live-protocol.ts +113 -0
  87. package/scripts/lib/codex-fresh-source-receipts.ts +399 -0
  88. package/scripts/lib/launch-receipt-windows.ts +46 -0
  89. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  90. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  91. package/scripts/mutants/codex-native.json +838 -0
  92. package/scripts/mutants/compaction-send-guard.json +103 -0
  93. package/scripts/mutants/entwurf-peers.json +19 -0
  94. package/scripts/mutants/mux-fresh-call.json +19 -8
  95. package/scripts/mutants/omp-fresh.json +6 -4
  96. package/scripts/mutants/pi-package-ownership.json +26 -0
  97. package/scripts/mutants/release-gate.json +13 -0
  98. package/scripts/mutants/v2-surface.json +53 -1
  99. package/scripts/pi_settings_io.py +3 -1
  100. package/scripts/raw-async-delivery/README.md +2 -1
  101. package/scripts/raw-codex-measure/README.md +114 -46
  102. package/scripts/register-pi-package.py +38 -30
  103. package/scripts/register-pi-provider.py +3 -2
  104. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  105. package/scripts/smoke-agy-native-push-live.ts +3 -1
  106. package/scripts/smoke-codex-birth.sh +347 -0
  107. package/scripts/smoke-codex-config-state.sh +511 -0
  108. package/scripts/smoke-codex-fresh-live.ts +1186 -0
  109. package/scripts/smoke-codex-native-push-live.ts +75 -0
  110. package/scripts/smoke-setup-verdict.sh +123 -10
  111. package/scripts/smoke-user-scope-citizen.sh +199 -71
  112. package/scripts/tsconfig.json +1 -0
@@ -81,6 +81,13 @@ import type {
81
81
  import { getMarkdownTheme, type Theme } from "@earendil-works/pi-coding-agent";
82
82
  import { Box, type Component, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
83
83
  import { ENTWURF_SENT_MESSAGE_TYPE } from "../protocol.js";
84
+ import {
85
+ type CompactionGuard,
86
+ compactionSendReject,
87
+ createCompactionGuard,
88
+ noteCompactionBefore,
89
+ noteCompactionTerminal,
90
+ } from "./lib/compaction-send-guard.js";
84
91
  import { CONTROL_SOCKET_SUFFIX, controlSocketPathIn, defaultControlSocketDir } from "./lib/control-socket-path.js";
85
92
  import {
86
93
  formatSenderInfoBlock,
@@ -126,6 +133,7 @@ interface SocketState {
126
133
  server: net.Server | null;
127
134
  socketPath: string | null;
128
135
  context: ExtensionContext | null;
136
+ compaction: CompactionGuard;
129
137
  }
130
138
 
131
139
  // The resident's GARDEN ADDRESS (#50 C2) — minted by this session's meta-record at
@@ -754,6 +762,15 @@ async function handleCommand(
754
762
  }
755
763
  }
756
764
 
765
+ const compactionReject = compactionSendReject(state.compaction, {
766
+ idle: ctx.isIdle(),
767
+ hasAgentSignal: ctx.signal !== undefined,
768
+ });
769
+ if (compactionReject) {
770
+ respond(false, "send", undefined, compactionReject);
771
+ return;
772
+ }
773
+
757
774
  // wants_reply defaults to false (etiquette marker, not transport contract).
758
775
  // It surfaces a "(wants reply)" badge on the receiver render so the
759
776
  // human/agent at either end sees that the sender wants a conversational
@@ -1036,6 +1053,7 @@ export default function (pi: ExtensionAPI) {
1036
1053
  server: null,
1037
1054
  socketPath: null,
1038
1055
  context: null,
1056
+ compaction: createCompactionGuard(),
1039
1057
  };
1040
1058
 
1041
1059
  pi.registerMessageRenderer(SESSION_MESSAGE_TYPE, renderSessionMessage);
@@ -1152,15 +1170,27 @@ export default function (pi: ExtensionAPI) {
1152
1170
  // were dead. The typecheck-exclude on this file kept that decay invisible.
1153
1171
  // Don't reintroduce them without first confirming the events exist.
1154
1172
  pi.on("session_start", async (_event, ctx) => {
1173
+ noteCompactionTerminal(state.compaction);
1155
1174
  await refreshServer(ctx);
1156
1175
  });
1157
1176
 
1177
+ pi.on("session_before_compact", () => {
1178
+ noteCompactionBefore(state.compaction);
1179
+ });
1180
+ pi.on("session_compact", () => {
1181
+ noteCompactionTerminal(state.compaction);
1182
+ });
1183
+ pi.on("session_compact_failed", () => {
1184
+ noteCompactionTerminal(state.compaction);
1185
+ });
1186
+
1158
1187
  // No session_before_switch / session_before_fork guards: `/new`, `/fork`, `/clone`
1159
1188
  // and RPC session replacement are pi's own again (#50 C2). Each replacement fires
1160
1189
  // session_start, which attaches the new pi session to its own record — the socket
1161
1190
  // simply rebinds to the new address. There is no id to police at the pre-event.
1162
1191
 
1163
1192
  pi.on("session_shutdown", async () => {
1193
+ noteCompactionTerminal(state.compaction);
1164
1194
  updateStatus(state.context, false, null);
1165
1195
  updateSessionEnv(state.context, false, null);
1166
1196
  residentGardenId = null;
@@ -1423,10 +1453,12 @@ interface EntwurfFactProviderModule {
1423
1453
  metaEntries: readonly { filename: string; regularFile: boolean }[];
1424
1454
  readRecord: (filename: string) => string;
1425
1455
  socket: { dir: string };
1456
+ observationLimit: number;
1426
1457
  }): Promise<unknown>;
1427
1458
  }
1428
1459
 
1429
1460
  interface EntwurfPeersRenderModule {
1461
+ ENTWURF_PEERS_RENDER_LIMIT: number;
1430
1462
  renderEntwurfPeers(result: unknown): { text: string; payload: unknown };
1431
1463
  }
1432
1464
 
@@ -1445,14 +1477,17 @@ async function renderEntwurfPeersForSurface(): Promise<{ text: string; payload:
1445
1477
  // surfaced by the #52 duplicate pass, which would let such a symlink quarantine the
1446
1478
  // healthy record it shadowed).
1447
1479
  const provider = (await import(ENTWURF_FACT_PROVIDER_MODULE)) as unknown as EntwurfFactProviderModule;
1480
+ const render = (await import(ENTWURF_PEERS_RENDER_MODULE)) as unknown as EntwurfPeersRenderModule;
1448
1481
  const result = await provider.listEntwurfFacts({
1449
1482
  metaEntries: meta.readActiveStoreEntries(sessionsDir),
1450
1483
  readRecord: meta.makeStoreRecordReader(sessionsDir),
1451
1484
  // Same socket axis as the legacy live-session scan, but merged with the
1452
1485
  // meta-record rail by listEntwurfFacts so meta-mailbox citizens are discoverable too.
1453
1486
  socket: { dir: ENTWURF_DIR },
1487
+ // #112: observation follows the human render budget. The full machine payload
1488
+ // and every authority/diagnostic pass above remain complete.
1489
+ observationLimit: render.ENTWURF_PEERS_RENDER_LIMIT,
1454
1490
  });
1455
- const render = (await import(ENTWURF_PEERS_RENDER_MODULE)) as unknown as EntwurfPeersRenderModule;
1456
1491
  return render.renderEntwurfPeers(result);
1457
1492
  }
1458
1493
 
@@ -1496,11 +1531,16 @@ function registerListSessionsTool(pi: ExtensionAPI): void {
1496
1531
  // ============================================================================
1497
1532
 
1498
1533
  const MUX_FRESH_CALL_MODULE = "./lib/mux-fresh-call.ts";
1534
+ const CODEX_FRESH_PREFLIGHT_MODULE = "./lib/codex-fresh-preflight.ts";
1535
+
1536
+ interface CodexFreshPreflightModule {
1537
+ codexFreshPreflight(env: NodeJS.ProcessEnv): Promise<string | null>;
1538
+ }
1499
1539
 
1500
1540
  interface MuxFreshCallModule {
1501
1541
  freshCall(
1502
1542
  params: {
1503
- backend: "pi" | "claude-code" | "copilot" | "omp";
1543
+ backend: "pi" | "claude-code" | "copilot" | "omp" | "codex";
1504
1544
  model: string;
1505
1545
  task: string;
1506
1546
  cwd?: string;
@@ -1530,35 +1570,34 @@ function registerFreshCallTool(pi: ExtensionAPI): void {
1530
1570
  registerTool({
1531
1571
  name: "entwurf_fresh_call",
1532
1572
  label: "Open Fresh Sibling",
1533
- description: `Open ONE fresh visible sibling in the operator's tmux and hand it a first task. Four fixed
1534
- backends only: pi, claude-code, copilot, omp. The sibling's FIRST action is a callback to you carrying a nonce, and the
1573
+ description: `Open ONE fresh visible sibling in the operator's tmux and hand it a first task. Five fixed
1574
+ backends only: pi, claude-code, copilot, omp, codex. The sibling's FIRST action is a callback to you carrying a nonce, and the
1535
1575
  sender envelope of that callback is its garden id — that is how you learn the address of something that did
1536
1576
  not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing else:
1537
1577
  it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls for the
1538
1578
  callback; if it never arrives the window is visible and can be read directly. For EXISTING citizens use
1539
1579
  entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and passed to the
1540
- chosen runtime CLI (provider/model for pi; model id/alias for Claude Code; a Copilot model name or auto; a
1541
- fuzzy model pattern for omp). A copilot launch goes through entwurf's own managed invocation and is refused
1542
- BEFORE any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units; an omp
1543
- launch is refused the same way if this host lacks the OMP birth, MCP, receiver or visible-status units, or if
1544
- omp's tools.xdev is not false (the vendor default hides MCP tool schemas from the prompt, so the sibling
1545
- could not call you back at all). An optional cwd starts the
1580
+ chosen runtime CLI (provider/model for pi; model id/alias for Claude Code; a Copilot, OMP, or Codex model
1581
+ name). Copilot, omp, and codex are refused BEFORE any window opens when their required birth, MCP,
1582
+ receive/delivery, or visible-identity units are absent. Codex additionally requires the operator-owned
1583
+ default app-server socket; entwurf never starts or supervises it. An optional cwd starts the
1546
1584
  sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume for a dormant
1547
1585
  record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. An optional
1548
- placement.tmuxSession opens it in ONE EXISTING session of this agent's own tmux server; an absent SESSION is
1549
- tmux-session-missing and NOTHING is created. Omit placement for the caller's own session. There are no
1586
+ placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own tmux server.
1587
+ When placement is omitted, Codex targets the exact existing \`codex\` home session; other backends target the
1588
+ caller's session. A missing named/home session is tmux-session-missing and NOTHING is created. There are no
1550
1589
  arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to same-user
1551
1590
  processes on this host.`,
1552
1591
  parameters: Type.Object({
1553
- backend: StringEnum(["pi", "claude-code", "copilot", "omp"], {
1554
- description: "Which fixed runtime to open. Only these four; there is no arbitrary command.",
1592
+ backend: StringEnum(["pi", "claude-code", "copilot", "omp", "codex"], {
1593
+ description: "Which fixed runtime to open. Only these five; there is no arbitrary command.",
1555
1594
  }),
1556
1595
  model: Type.String({
1557
1596
  minLength: 1,
1558
1597
  maxLength: 200,
1559
1598
  pattern: "^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$",
1560
1599
  description:
1561
- "Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot model name (or auto).",
1600
+ "Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot/OMP/Codex model name.",
1562
1601
  }),
1563
1602
  task: Type.String({
1564
1603
  minLength: 1,
@@ -1582,7 +1621,7 @@ processes on this host.`,
1582
1621
  },
1583
1622
  {
1584
1623
  description:
1585
- "Optional project seat: open the sibling in ONE EXISTING tmux session of this agent's own server instead of the caller's session. Nothing is ever created an absent session is a refusal, not a new session. Independent of cwd; neither is inferred from the other. The receipt echoes the REQUESTED name and reports the resolved target session id.",
1624
+ "Optional expert seat override: open the sibling in ONE EXISTING tmux session of this agent's own server. When omitted, Codex selects the exact existing `codex` home session; other backends use the caller's session. Nothing is ever created. Independent of cwd; neither is inferred from the other. The receipt reports the selected name, its source, and resolved target session id.",
1586
1625
  },
1587
1626
  ),
1588
1627
  ),
@@ -1590,7 +1629,7 @@ processes on this host.`,
1590
1629
  async execute(
1591
1630
  _toolCallId: string,
1592
1631
  params: {
1593
- backend: "pi" | "claude-code" | "copilot" | "omp";
1632
+ backend: "pi" | "claude-code" | "copilot" | "omp" | "codex";
1594
1633
  model: string;
1595
1634
  task: string;
1596
1635
  cwd?: string;
@@ -1602,14 +1641,27 @@ processes on this host.`,
1602
1641
  ) {
1603
1642
  try {
1604
1643
  const mux = (await import(MUX_FRESH_CALL_MODULE)) as unknown as MuxFreshCallModule;
1605
- const result = mux.freshCall({
1644
+ // ONE input object for ONE composition call. The codex branch differs only by the
1645
+ // capability preflight that must answer BEFORE any mutation; duplicating the call
1646
+ // would put the caller-identity contract in two places, which is how a mutant that
1647
+ // plants a defect in one of them survives on the other.
1648
+ const call = {
1606
1649
  backend: params.backend,
1607
1650
  model: params.model,
1608
1651
  task: params.task,
1609
1652
  cwd: params.cwd,
1610
1653
  placement: params.placement,
1611
1654
  callerGardenId: residentGardenId,
1612
- });
1655
+ };
1656
+ const result =
1657
+ params.backend === "codex"
1658
+ ? await (async () => {
1659
+ const preflight = (await import(CODEX_FRESH_PREFLIGHT_MODULE)) as unknown as CodexFreshPreflightModule;
1660
+ const missing = await preflight.codexFreshPreflight(process.env);
1661
+ if (missing) return { ok: false as const, reason: missing };
1662
+ return mux.freshCall(call);
1663
+ })()
1664
+ : mux.freshCall(call);
1613
1665
  const rendered = mux.renderFreshCall(result);
1614
1666
  return {
1615
1667
  content: [{ type: "text", text: rendered.text }],
@@ -46,8 +46,8 @@ import type { AcpTextBlock } from "./context.js";
46
46
  * shape) and never onto those four. A field rename upstream fails the typecheck
47
47
  * here rather than silently zeroing the operator's cache-effect badge.
48
48
  */
49
- /** One `_meta.quota.token_count` row (claude-agent-acp 0.75.1
50
- * `dist/acp-agent.js:6493-6502`). `cachedInputTokens` is cache READS — the name
49
+ /** One `_meta.quota.token_count` row (claude-agent-acp 0.76.0
50
+ * `dist/acp-agent.js:6566-6575`). `cachedInputTokens` is cache READS — the name
51
51
  * differs from `usage.cachedReadTokens` because the shape is shared with
52
52
  * codex-acp; `cachedWriteTokens` is Claude's extra sibling. */
53
53
  export type AcpQuotaTokenCount = {
@@ -64,7 +64,7 @@ export type AcpPromptResponse = {
64
64
  /** Vendor-private, version-pinned. `_meta` is a standard ACP extension slot
65
65
  * whose values a client may not assume, and `quota` is NOT in
66
66
  * claude-agent-acp's exported types — it is produced by the private
67
- * `turnQuotaMeta()` (read at 0.75.1 `dist/acp-agent.js:6476-6485`). Read
67
+ * `turnQuotaMeta()` (read at 0.76.0 `dist/acp-agent.js:6549-6558`). Read
68
68
  * defensively, never structurally required, and re-measure on a pin move. */
69
69
  _meta?: {
70
70
  quota?: {
@@ -150,7 +150,7 @@ export interface AcpAdapterSettingsParams {
150
150
  *
151
151
  * During stream handling the vendor keeps a per-message snapshot in
152
152
  * `lastAssistantUsage`, but its `usage_update` publishes only the scalar `used`
153
- * value (read at claude-agent-acp 0.75.1 `dist/acp-agent.js:3853-3878`). Until
153
+ * value (read at claude-agent-acp 0.76.0 `dist/acp-agent.js:3857-3882`). Until
154
154
  * that partition is carried on the wire, NOTHING honest can go in pi's four
155
155
  * fields, so entwurf writes none of them.
156
156
  */
@@ -342,8 +342,8 @@ export const claudeAdapter: AcpBackendAdapter = {
342
342
 
343
343
  // MEASURED (2026-09-02, #93): claude-agent-acp's `usage_update` carries a
344
344
  // session-cumulative ESTIMATED cost (`cost.amount = message.total_cost_usd`)
345
- // and a context-occupancy scalar (`used`; both read at 0.75.1
346
- // `dist/acp-agent.js:3467-3482`). Both are measured, so backend.ts seals them.
345
+ // and a context-occupancy scalar (`used`; both read at 0.76.0
346
+ // `dist/acp-agent.js:3471-3486`). Both are measured, so backend.ts seals them.
347
347
  // The token partition is NOT declared here: ACP's only token carrier is a
348
348
  // per-turn round-trip aggregate, which is not what pi's four fields mean.
349
349
  sealsTurnAccounting: true,
@@ -137,7 +137,7 @@ function finiteOrZero(value: unknown): number {
137
137
  * "the accounting-grade figure per the SDK" — it also counts Task subagents,
138
138
  * sidechains, and INTERNAL CALLS SUCH AS COMPACTION, so its rows "can total more
139
139
  * than `token_count`" and are "the fuller picture, not a decomposition of it"
140
- * (read at claude-agent-acp 0.75.1 `dist/acp-agent.js:6465-6485`). The narrower
140
+ * (read at claude-agent-acp 0.76.0 `dist/acp-agent.js:6538-6558`). The narrower
141
141
  * `PromptResponse.usage` (== `quota.token_count`) is the MAIN AGENT LOOP only.
142
142
  *
143
143
  * The wider one is the right numerator because the denominator already has that
@@ -1237,7 +1237,7 @@ export function streamAcpTurn(
1237
1237
  // Writing zeros is not a placeholder for a better number we could compute:
1238
1238
  // the per-request partition is genuinely absent from the wire. The vendor
1239
1239
  // builds it in `lastAssistantUsage` and sends only its scalar sum (read at
1240
- // claude-agent-acp 0.75.1 `dist/acp-agent.js:3853-3878`) — #96.
1240
+ // claude-agent-acp 0.76.0 `dist/acp-agent.js:3857-3882`) — #96.
1241
1241
  //
1242
1242
  // But silence is NOT the resting state. A cache miss the operator never sees
1243
1243
  // is a false reading, not a modest one: a session can run for hours believing
@@ -1356,7 +1356,7 @@ export function streamAcpTurn(
1356
1356
  // between them is exactly why the notice below names a MECHANISM and
1357
1357
  // never a cause: claude-agent-acp's `conversation_reset` handler only
1358
1358
  // switches the SDK to a fresh conversation and touches no cost at all
1359
- // (read at 0.75.1 `dist/acp-agent.js:4282-4289`), while claude-agent-sdk
1359
+ // (read at 0.76.0 `dist/acp-agent.js:4286-4293`), while claude-agent-sdk
1360
1360
  // separately documents that "a mid-session /clear resets the running
1361
1361
  // total" (read at 0.3.257 `sdk.d.ts:4884`). A reset therefore PLAUSIBLY
1362
1362
  // explains a backwards total, but nothing here has MEASURED that it did,
@@ -60,8 +60,8 @@ export type AcpPiStreamState = {
60
60
  *
61
61
  * Last write wins, never a sum: both are latest session-level observations, and
62
62
  * one turn can legitimately see several (claude emits one per `result` message,
63
- * including a sub-agent's own — read at claude-agent-acp 0.75.1
64
- * `dist/acp-agent.js:3467-3482`), each carrying that result's current values.
63
+ * including a sub-agent's own — read at claude-agent-acp 0.76.0
64
+ * `dist/acp-agent.js:3471-3486`), each carrying that result's current values.
65
65
  */
66
66
  observedSessionCostUsd?: number;
67
67
  observedContextOccupancyTokens?: number;
@@ -346,8 +346,8 @@ export function applyAcpSessionUpdate(
346
346
  case "usage_update": {
347
347
  // `used` is OCCUPANCY-shaped — the backend's post-turn context size, not
348
348
  // the prompt response's turn aggregate. Claude sends `lastAssistantTotalUsage`
349
- // as `used` (read at claude-agent-acp 0.75.1
350
- // `dist/acp-agent.js:3867-3878`), after constructing that scalar from the
349
+ // as `used` (read at claude-agent-acp 0.76.0
350
+ // `dist/acp-agent.js:3871-3882`), after constructing that scalar from the
351
351
  // latest assistant snapshot (`:3853-3866`). pi reads `usage.totalTokens` as
352
352
  // exactly that occupancy (`calculateContextTokens(usage) = usage.totalTokens
353
353
  // || input + output + cacheRead + cacheWrite`, read at pi-coding-agent
@@ -150,6 +150,12 @@ export function buildClaudeSessionMeta(
150
150
  ...(params.modelId ? { model: params.modelId } : {}),
151
151
  tools: [...params.tools],
152
152
  settingSources: [...params.settingSources],
153
+ // `settings` is an OBJECT here, and that is a contract, not a convenience.
154
+ // claude-agent-acp also accepts a STRING PATH, and at 0.76.0 the readFile +
155
+ // JSON.parse for that form moved out of `resolvedProvider` up onto the
156
+ // unconditional `session/new` path — a shape that WOULD reach us, and does not,
157
+ // only because we never hand it a path. Keep it inline: a path would put file
158
+ // IO, and its failure modes, inside every session creation.
153
159
  settings: {
154
160
  permissions: {
155
161
  allow: [...params.permissionAllow],