toolroll 0.9.52 → 0.9.53

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 (146) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +55 -80
  3. package/dist/agentconfig.d.ts +11 -36
  4. package/dist/agentconfig.js +3 -62
  5. package/dist/approval-policy.d.ts +1 -1
  6. package/dist/approval-policy.js +1 -1
  7. package/dist/board.d.ts +3 -19
  8. package/dist/board.js +4 -42
  9. package/dist/browser/workspace.js +18 -18
  10. package/dist/browser-workspace.d.ts +2 -1
  11. package/dist/builder.d.ts +9 -40
  12. package/dist/builder.js +50 -257
  13. package/dist/chat-actions.js +3 -14
  14. package/dist/chat-controls.d.ts +0 -4
  15. package/dist/chat-controls.js +0 -1
  16. package/dist/chat-decide.js +1 -3
  17. package/dist/chat-delivery.d.ts +0 -2
  18. package/dist/chat-delivery.js +0 -1
  19. package/dist/chat-quiet.js +2 -2
  20. package/dist/chat-task-actions.d.ts +1 -1
  21. package/dist/chat-task-actions.js +0 -2
  22. package/dist/claim.d.ts +79 -244
  23. package/dist/claim.js +688 -1023
  24. package/dist/cli.d.ts +2 -0
  25. package/dist/cli.js +10 -2
  26. package/dist/contracts/cli.d.ts +0 -13
  27. package/dist/contracts/cli.js +0 -2
  28. package/dist/contracts/console-api.d.ts +5 -10
  29. package/dist/contracts/console-api.js +4 -10
  30. package/dist/contracts/flow.d.ts +50 -0
  31. package/dist/contracts/flow.js +26 -1
  32. package/dist/contracts/lead-tools.d.ts +0 -13
  33. package/dist/contracts/lead-tools.js +3 -3
  34. package/dist/contracts/store-json.d.ts +1 -25
  35. package/dist/contracts/store-json.js +3 -13
  36. package/dist/converse.d.ts +1 -11
  37. package/dist/converse.js +6 -38
  38. package/dist/coordinator.d.ts +0 -1
  39. package/dist/coordinator.js +3 -5
  40. package/dist/demo.js +16 -26
  41. package/dist/desktop-update-gate.js +0 -1
  42. package/dist/desktop-update.js +1 -3
  43. package/dist/dispatch.d.ts +1 -1
  44. package/dist/dispatch.js +1 -1
  45. package/dist/dispose.d.ts +2 -3
  46. package/dist/dispose.js +17 -92
  47. package/dist/envelope.js +0 -3
  48. package/dist/exec.d.ts +0 -64
  49. package/dist/exec.js +3 -356
  50. package/dist/exhaustion.d.ts +22 -76
  51. package/dist/exhaustion.js +14 -140
  52. package/dist/export.js +4 -4
  53. package/dist/flow-engine.js +1 -1
  54. package/dist/flow-schedule.d.ts +154 -0
  55. package/dist/flow-schedule.js +321 -0
  56. package/dist/flow-share.js +1 -1
  57. package/dist/flow-triggers.d.ts +6 -1
  58. package/dist/flow-triggers.js +65 -10
  59. package/dist/flows-cli.js +8 -4
  60. package/dist/flows-ui.js +2 -0
  61. package/dist/flows.js +1 -1
  62. package/dist/guides.js +9 -29
  63. package/dist/invoke.d.ts +1 -24
  64. package/dist/invoke.js +17 -259
  65. package/dist/lead-status.js +2 -4
  66. package/dist/lead-voice.js +1 -1
  67. package/dist/mate-doors.d.ts +1 -2
  68. package/dist/mate-doors.js +12 -25
  69. package/dist/mate-tools.d.ts +2 -2
  70. package/dist/mate-tools.js +10 -22
  71. package/dist/modes.d.ts +6 -7
  72. package/dist/modes.js +7 -13
  73. package/dist/operate-remote-arguments.js +2 -6
  74. package/dist/operate-remote.js +6 -7
  75. package/dist/operate.d.ts +0 -6
  76. package/dist/operate.js +222 -2247
  77. package/dist/phase-routing.d.ts +13 -23
  78. package/dist/phase-routing.js +13 -35
  79. package/dist/plan-auto.js +2 -2
  80. package/dist/planner-source.js +2 -1
  81. package/dist/policy.d.ts +1 -4
  82. package/dist/policy.js +0 -8
  83. package/dist/process-recovery-eligibility.js +2 -3
  84. package/dist/project-delete.js +3 -15
  85. package/dist/proposal.d.ts +4 -24
  86. package/dist/proposal.js +3 -88
  87. package/dist/provider-auth.d.ts +3 -3
  88. package/dist/provider-auth.js +4 -6
  89. package/dist/provider.d.ts +11 -25
  90. package/dist/provider.js +4 -36
  91. package/dist/push.js +1 -1
  92. package/dist/recipe-ui.js +6 -6
  93. package/dist/recipes.d.ts +3 -3
  94. package/dist/recipes.js +27 -23
  95. package/dist/run-closeout.d.ts +1 -1
  96. package/dist/run-closeout.js +1 -4
  97. package/dist/scope.d.ts +11 -65
  98. package/dist/scope.js +17 -82
  99. package/dist/serve.js +15 -24
  100. package/dist/server/chat.js +7 -30
  101. package/dist/server/flows.js +11 -10
  102. package/dist/server/guards.js +0 -1
  103. package/dist/server/pages-assets.js +1 -1
  104. package/dist/server/pages.js +0 -2
  105. package/dist/server/people-tokens.js +0 -4
  106. package/dist/server/route-table.d.ts +1 -1
  107. package/dist/server/route-table.js +2 -17
  108. package/dist/server/settings.js +1 -8
  109. package/dist/server/shared.d.ts +28 -212
  110. package/dist/server/shared.js +71 -780
  111. package/dist/server/task-views.js +9 -76
  112. package/dist/server/tasks.d.ts +1 -50
  113. package/dist/server/tasks.js +36 -946
  114. package/dist/setup-guide.d.ts +2 -2
  115. package/dist/slack-chat.d.ts +0 -2
  116. package/dist/slack-chat.js +0 -1
  117. package/dist/store.d.ts +105 -1340
  118. package/dist/store.js +706 -4010
  119. package/dist/surface.js +10 -24
  120. package/dist/task-composer.d.ts +1 -1
  121. package/dist/task-composer.js +1 -1
  122. package/dist/task-control.d.ts +3 -10
  123. package/dist/task-control.js +2 -7
  124. package/dist/teammate-desk.js +1 -1
  125. package/dist/teammates-ui.js +1 -1
  126. package/dist/telegram-mate.d.ts +0 -3
  127. package/dist/telegram-mate.js +0 -1
  128. package/dist/templates.d.ts +11 -7
  129. package/dist/templates.js +12 -9
  130. package/dist/toolroll-update.d.ts +10 -0
  131. package/dist/toolroll-update.js +56 -4
  132. package/dist/work-index.js +2 -6
  133. package/dist/workspace-ui.js +1 -1
  134. package/package.json +1 -1
  135. package/scripts/recovery-canary.mjs +3 -3
  136. package/dist/contest.d.ts +0 -318
  137. package/dist/contest.js +0 -753
  138. package/dist/held.d.ts +0 -150
  139. package/dist/held.js +0 -799
  140. package/dist/liveness.d.ts +0 -30
  141. package/dist/liveness.js +0 -42
  142. package/dist/pricing.d.ts +0 -45
  143. package/dist/pricing.js +0 -77
  144. package/dist/routine.d.ts +0 -228
  145. package/dist/routine.js +0 -764
  146. package/dist/supervisor.mjs +0 -297
package/dist/exec.js CHANGED
@@ -9,8 +9,7 @@
9
9
  import { linuxFenceAvailable, linuxFenced, macosFenceAvailable, macosFenced } from "./agent-fence.js";
10
10
  import { execFile, spawn } from "node:child_process";
11
11
  import { realpathSync } from "node:fs";
12
- import { fileURLToPath } from "node:url";
13
- import { observeProcessTree, stopProcessTree, readProcessObservationFailure } from "./process-tree.js";
12
+ import { observeProcessTree, stopProcessTree } from "./process-tree.js";
14
13
  import { jsonlDiscriminants } from "./jsonl-discriminants.js";
15
14
  import { createContainer, currentContainment } from "./containment.js";
16
15
  import { claudeLimitsOf, noteLimits } from "./provider-limits.js";
@@ -63,8 +62,8 @@ function rejectedChild(error, child) {
63
62
  return error instanceof SpawnCustodyFailure ? error.child : child;
64
63
  }
65
64
  /**
66
- * The ONE spawn road for containable children (buffered, streaming and
67
- * held transports): consult the pinned policy, make this spawn's OS
65
+ * The ONE spawn road for containable children (buffered and streaming
66
+ * transports): consult the pinned policy, make this spawn's OS
68
67
  * object, spawn the target INSIDE it, and register the object beside the
69
68
  * handle. Under observed containment this is exactly the old spawn. A
70
69
  * required policy that cannot be met throws ContainmentRefusal here —
@@ -274,15 +273,6 @@ export function terminateOwnedProcesses(owner) {
274
273
  }
275
274
  return terminated;
276
275
  }
277
- /**
278
- * Live held-session SUPERVISORS (v6 W7). Not in liveProviders: SIGKILLing a
279
- * supervisor's group would orphan the agent living in its own fresh group.
280
- * The sweep's contract here is SIGTERM first — the supervisor's handler
281
- * fences its child (EOF → grace → group SIGKILL) — and SIGKILL only on a
282
- * repeat sweep, which is the one road that can orphan an agent and is why
283
- * it is the escalation, never the opener.
284
- */
285
- const heldSupervisors = new Map();
286
276
  /** SIGKILL the child's whole process group; fall back to the child alone. */
287
277
  function killGroup(child) {
288
278
  const container = containers.get(child);
@@ -346,28 +336,6 @@ export function terminateLiveProviders() {
346
336
  killGroup(child);
347
337
  terminated += 1;
348
338
  }
349
- // Held supervisors take the graceful road first (v6 W7): SIGTERM makes
350
- // the supervisor fence its own agent race-free through the handle only
351
- // it holds. A second sweep escalates to SIGKILL — the documented
352
- // residual hole, reachable only by double-escalation, with the startup
353
- // orphan fence paging for anything that lingers.
354
- for (const [supervisor, stage] of heldSupervisors) {
355
- try {
356
- if (stage === "fresh") {
357
- supervisor.kill("SIGTERM");
358
- heldSupervisors.set(supervisor, "termed");
359
- }
360
- else {
361
- // With a native OS object the escalation reaches the agent too:
362
- // the object's kill is the one road that cannot orphan it.
363
- killGroup(supervisor);
364
- }
365
- }
366
- catch {
367
- // Already gone.
368
- }
369
- terminated += 1;
370
- }
371
339
  return terminated;
372
340
  }
373
341
  /** The one place a child's environment is decided (audit IV-5). */
@@ -1555,324 +1523,3 @@ export function runClaudeStreamJsonl(file, args, options = {}) {
1555
1523
  child.on("close", code => finish(code));
1556
1524
  });
1557
1525
  }
1558
- // ---- the held session transport (Parity II Phase 2, spec v2 S0a + v6 W9) ----
1559
- /**
1560
- * Where the supervisor script lives: a plain-.mjs sibling of this module in
1561
- * BOTH layouts (src/ under tsx, dist/ after build — postbuild copies it).
1562
- */
1563
- export function supervisorPath() {
1564
- return fileURLToPath(new URL("./supervisor.mjs", import.meta.url));
1565
- }
1566
- /**
1567
- * Darwin's sun_path is 104 bytes including the terminator; Linux allows a
1568
- * hair more. Refusing at spawn beats failing inside bind() with a raw
1569
- * ENAMETOOLONG after custody was already recorded.
1570
- */
1571
- export const HELD_SOCKET_PATH_LIMIT = 103;
1572
- export function heldSocketPathProblem(path) {
1573
- const bytes = Buffer.byteLength(path, "utf8");
1574
- if (bytes > HELD_SOCKET_PATH_LIMIT) {
1575
- return `the control socket path is ${bytes} bytes — the platform limit is ${HELD_SOCKET_PATH_LIMIT}: ${path}`;
1576
- }
1577
- return null;
1578
- }
1579
- /**
1580
- * Hold one claude process open across stream-json turns, under a
1581
- * supervisor whose parenthood makes the eventual kill provable. Returns
1582
- * once the supervisor's control frame settles the spawn either way.
1583
- *
1584
- * No timers live here: the per-turn wall deadline, liveness lapse, and
1585
- * absolute expiry are the coordinator's exact timers (v2 S1d) — this
1586
- * transport only moves bytes and counts the protocol's own marks.
1587
- */
1588
- export function startClaudeHeldSession(file, args, options) {
1589
- const pathProblem = heldSocketPathProblem(options.socketPath);
1590
- if (pathProblem !== null) {
1591
- return Promise.resolve({ ok: false, reason: "socket-path", message: pathProblem });
1592
- }
1593
- const childEnv = resolveChildEnv(options) ?? { ...process.env };
1594
- const events = options.events ?? {};
1595
- return new Promise(resolveStart => {
1596
- let supervisor;
1597
- let container = null;
1598
- let attachFailure = null;
1599
- try {
1600
- // The supervisor spawns INSIDE the OS object, so the agent it holds
1601
- // and every tool the agent launches are members too: one kill reaches
1602
- // the whole hold, and the object's empty state proves the hold over.
1603
- const spawned = spawnContained(process.execPath, [supervisorPath(), file, ...args], {
1604
- cwd: options.cwd,
1605
- env: {
1606
- ...childEnv,
1607
- SO_HELD_SOCKET: options.socketPath,
1608
- SO_HELD_COOKIE: options.cookie,
1609
- SO_HELD_GRACE_MS: String(options.graceMs ?? 10_000),
1610
- SO_HELD_DIAGNOSTIC_FD: currentContainment().mode === "observed" ? "3" : "",
1611
- },
1612
- stdio: currentContainment().mode === "observed" ? ["pipe", "pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],
1613
- detached: false,
1614
- }, options.owner ?? "held", {
1615
- beforeSpawn: options.beforeSpawn,
1616
- onSpawn: options.onSpawn,
1617
- onSpawnFailed: options.onSpawnFailed,
1618
- onContainer: options.onContainer,
1619
- onContainerEmpty: options.onContainerEmpty,
1620
- onUnknown: options.onUnknown,
1621
- fence: options.fence,
1622
- });
1623
- supervisor = spawned.child;
1624
- container = spawned.container;
1625
- void spawned.attached.then(outcome => { if (!outcome.ok)
1626
- attachFailure = outcome.detail; });
1627
- if (container === null)
1628
- observeProcessTree(supervisor, options);
1629
- }
1630
- catch (error) {
1631
- const left = rejectedChild(error, supervisor);
1632
- const refused = () => resolveStart({ ok: false, reason: "spawn-failed", message: String(error instanceof SpawnCustodyFailure ? error.reason : error) });
1633
- if (left === undefined)
1634
- refused();
1635
- else {
1636
- left.once("close", refused);
1637
- left.on("error", () => { });
1638
- killGroup(left);
1639
- }
1640
- return;
1641
- }
1642
- // This pipe belongs to the supervisor, not the agent's relayed stream.
1643
- // Native containment uses its existing attachment descriptors instead.
1644
- if (container === null) {
1645
- const diagnostics = supervisor.stdio[3];
1646
- let buffer = "", reportedFault = false;
1647
- const diagnosticFault = () => {
1648
- if (reportedFault)
1649
- return;
1650
- reportedFault = true;
1651
- // This channel carries diagnostics only. Its failure cannot invent
1652
- // lost child identity; genuine supervisor uncertainty still exits126.
1653
- try {
1654
- options.onObservationFailure?.({ phase: "supervisor", operation: "diagnostic-read", code: "SUPERVISOR_DIAGNOSTIC", rootPid: supervisor.pid ?? null, at: new Date().toISOString(), identityUnknown: false });
1655
- }
1656
- catch { }
1657
- };
1658
- if (diagnostics !== null && diagnostics !== undefined && "on" in diagnostics) {
1659
- diagnostics.on("data", (chunk) => {
1660
- buffer += chunk.toString("utf8");
1661
- if (buffer.length > 16_384) {
1662
- buffer = "";
1663
- diagnosticFault();
1664
- return;
1665
- }
1666
- let cut;
1667
- while ((cut = buffer.indexOf("\n")) !== -1) {
1668
- const line = buffer.slice(0, cut);
1669
- buffer = buffer.slice(cut + 1);
1670
- let failure = null;
1671
- try {
1672
- failure = readProcessObservationFailure(JSON.parse(line));
1673
- }
1674
- catch { }
1675
- if (failure === null) {
1676
- diagnosticFault();
1677
- continue;
1678
- }
1679
- try {
1680
- options.onObservationFailure?.(failure);
1681
- }
1682
- catch { /* No diagnostic can certify exit or clear an existing witness. */ }
1683
- }
1684
- });
1685
- diagnostics.on("error", diagnosticFault);
1686
- diagnostics.on("end", () => { if (buffer !== "")
1687
- diagnosticFault(); });
1688
- }
1689
- }
1690
- heldSupervisors.set(supervisor, "fresh");
1691
- let settledStart = false;
1692
- let agentPgid = null;
1693
- let initSeq = 0;
1694
- let resultSeq = 0;
1695
- let sessionSeen = false;
1696
- let partial = "";
1697
- let exitResolve = () => { };
1698
- const exited = new Promise(pass => {
1699
- exitResolve = pass;
1700
- });
1701
- const readyTimer = setTimeout(() => {
1702
- if (settledStart)
1703
- return;
1704
- settledStart = true;
1705
- try {
1706
- killGroup(supervisor);
1707
- }
1708
- catch {
1709
- // Already gone.
1710
- }
1711
- resolveStart({ ok: false, reason: "ready-timeout", message: "the supervisor never sent its control frame" });
1712
- }, options.readyTimeoutMs ?? 10_000);
1713
- const handle = {
1714
- supervisorPid: null,
1715
- agentPgid: null,
1716
- writeTurn(json) {
1717
- const stdin = supervisor.stdin;
1718
- if (stdin === null || stdin.destroyed || !stdin.writable)
1719
- return false;
1720
- try {
1721
- stdin.write(json.endsWith("\n") ? json : `${json}\n`);
1722
- return true;
1723
- }
1724
- catch {
1725
- return false;
1726
- }
1727
- },
1728
- endInput() {
1729
- try {
1730
- supervisor.stdin?.end();
1731
- }
1732
- catch {
1733
- // Already closed.
1734
- }
1735
- },
1736
- terminate() {
1737
- try {
1738
- supervisor.kill("SIGTERM");
1739
- heldSupervisors.set(supervisor, "termed");
1740
- }
1741
- catch {
1742
- // Already gone.
1743
- }
1744
- },
1745
- killHard() {
1746
- try {
1747
- killGroup(supervisor);
1748
- }
1749
- catch {
1750
- // Already gone.
1751
- }
1752
- },
1753
- exited,
1754
- };
1755
- const keep = (line) => {
1756
- if (line.length > JSONL_LINE_CAP)
1757
- return;
1758
- let event;
1759
- try {
1760
- event = JSON.parse(line);
1761
- }
1762
- catch {
1763
- return;
1764
- }
1765
- if (event === null || typeof event !== "object")
1766
- return;
1767
- // The control frame settles the start, exactly once, before any
1768
- // agent byte — the two-hop handshake (v6 W9).
1769
- if (!settledStart && typeof event["so_supervisor"] === "string") {
1770
- settledStart = true;
1771
- clearTimeout(readyTimer);
1772
- if (event["so_supervisor"] === "ready") {
1773
- agentPgid = typeof event["agentPgid"] === "number" ? event["agentPgid"] : null;
1774
- handle.agentPgid = agentPgid;
1775
- handle.supervisorPid =
1776
- typeof event["supervisorPid"] === "number" ? event["supervisorPid"] : (supervisor.pid ?? null);
1777
- resolveStart({ ok: true, handle });
1778
- }
1779
- else {
1780
- const message = typeof event["message"] === "string" ? event["message"] : "spawn failed";
1781
- resolveStart({ ok: false, reason: "spawn-failed", message });
1782
- }
1783
- return;
1784
- }
1785
- if (typeof event["so_supervisor"] === "string")
1786
- return; // never two frames; drop strays
1787
- const type = String(event["type"] ?? "");
1788
- if (type === "system" && String(event["subtype"] ?? "") === "init") {
1789
- initSeq += 1;
1790
- if (!sessionSeen && events.onSessionId !== undefined) {
1791
- const id = transportSessionId(event["session_id"]);
1792
- if (id !== null) {
1793
- sessionSeen = true;
1794
- try {
1795
- events.onSessionId(id);
1796
- }
1797
- catch {
1798
- // A registry that cannot be written must not kill the turn.
1799
- }
1800
- }
1801
- }
1802
- try {
1803
- events.onTurnInit?.(initSeq);
1804
- }
1805
- catch {
1806
- // Observational.
1807
- }
1808
- }
1809
- else if (type === "rate_limit_event") {
1810
- noteLimits(claudeLimitsOf(event));
1811
- }
1812
- else if (type === "result" && primaryResultOrigin(event)) {
1813
- resultSeq += 1;
1814
- try {
1815
- events.onTurnResult?.(resultSeq, event);
1816
- }
1817
- catch {
1818
- // Observational.
1819
- }
1820
- }
1821
- try {
1822
- events.onStreamEvent?.(event);
1823
- }
1824
- catch {
1825
- // Observational only, arc-1 rule unchanged.
1826
- }
1827
- };
1828
- supervisor.stdout?.setEncoding("utf8");
1829
- supervisor.stdout?.on("data", (chunk) => {
1830
- partial += chunk;
1831
- let cut = partial.indexOf("\n");
1832
- while (cut !== -1) {
1833
- keep(partial.slice(0, cut));
1834
- partial = partial.slice(cut + 1);
1835
- cut = partial.indexOf("\n");
1836
- }
1837
- if (partial.length > JSONL_LINE_CAP * 2)
1838
- partial = partial.slice(-JSONL_LINE_CAP);
1839
- });
1840
- supervisor.on("error", error => {
1841
- heldSupervisors.delete(supervisor);
1842
- if (!settledStart) {
1843
- settledStart = true;
1844
- clearTimeout(readyTimer);
1845
- resolveStart({ ok: false, reason: "spawn-failed", message: String(error) });
1846
- }
1847
- exitResolve({ code: null });
1848
- });
1849
- supervisor.on("close", code => {
1850
- heldSupervisors.delete(supervisor);
1851
- if (partial.trim() !== "")
1852
- keep(partial);
1853
- // The POSIX supervisor could not prove its observed tree drained.
1854
- // Persist uncertainty before any onExit handler can release custody.
1855
- if (container === null && code === CONTAINMENT_REFUSED_CODE) {
1856
- try {
1857
- options.onUnknown?.();
1858
- }
1859
- catch { /* the observer must not prevent settlement */ }
1860
- }
1861
- const settlement = container === null ? Promise.resolve(true) : settleContainer(container, options);
1862
- void settlement.then(empty => {
1863
- if (!settledStart) {
1864
- settledStart = true;
1865
- clearTimeout(readyTimer);
1866
- resolveStart({ ok: false, reason: "spawn-failed", message: attachFailure ?? "the supervisor exited before its control frame" });
1867
- }
1868
- try {
1869
- events.onExit?.({ code: empty ? code : CONTAINMENT_REFUSED_CODE });
1870
- }
1871
- catch {
1872
- // Observational.
1873
- }
1874
- exitResolve({ code: empty ? code : CONTAINMENT_REFUSED_CODE });
1875
- });
1876
- });
1877
- });
1878
- }
@@ -1,93 +1,39 @@
1
1
  /**
2
- * The terminal exhaustion taxonomy (fallback chains, R1/C5/C8 of the
3
- * approved design). A finished attempt is classified into ONE of these,
4
- * where the evidence still exists — the provider adapter's structural
5
- * record plus the gateway's authoritative CLI version and auth mode —
6
- * NEVER in disposal, by which time the structural signal is gone.
2
+ * How a finished attempt ended, classified where the evidence still exists
3
+ * (the provider adapter's structural terminal record), never in disposal.
4
+ * The one class that changes what happens next is `auth-expired`: a sign-in
5
+ * or API key that no longer works pauses dispatch for that provider until
6
+ * someone signs in, instead of burning attempts. Everything else is the
7
+ * ordinary failure road.
7
8
  *
8
- * The whole feature is FAIL CLOSED by construction: a recognizer's
9
- * eligible-signal set is EMPTY until a versioned fixture, captured from a
10
- * GENUINELY EXHAUSTED subscription, proves the exact bytes. A build with
11
- * no fixture for a (provider, version) can therefore NEVER return an
12
- * eligible class — so it can never authorize a paid fallback. A
13
- * successful run cannot establish the exhausted shape; only real
14
- * exhaustion can, and the plane cannot manufacture it.
15
- *
16
- * Precedence (C5), applied by the classifier:
17
- * - A present STRUCTURAL FAILURE terminal blocks success ingestion even
18
- * on exit 0 (that is the parser's job; this module reads the record).
19
- * - Only an ALLOWLISTED, version+auth-specific pattern yields an ELIGIBLE
20
- * class. Exit code, bare rate-limit prose, timeout, notFound, stderr,
21
- * and zero tokens NEVER independently establish exhaustion.
22
- * - Everything unmatched is `unknown` — the ordinary failure road.
23
- * - A sign-in that no longer works (`auth-expired`) is recognized FIRST,
24
- * for every provider and version: an expired or revoked login is not a
25
- * property of a CLI version, and retrying it only burns attempts. It is
26
- * never fallback-eligible — a lapsed login must not reach a paid key.
9
+ * `usage-exhausted`, `credits-depleted` and `transient-throttle` are only
10
+ * ever read from older runs' records (they were stamped by a removed
11
+ * feature); this build never produces them.
27
12
  */
28
- import type { ProviderId } from "./provider.js";
29
- export type TerminalClass =
30
- /** The subscription/plan usage cap is spent — FALLBACK-ELIGIBLE. */
31
- "usage-exhausted"
32
- /** An API-key account's paid credits/quota are gone — FALLBACK-ELIGIBLE. */
33
- | "credits-depleted"
34
- /** Temporary throttling NOT tied to the usage cap — backoff, NEVER paid fallback. */
35
- | "transient-throttle"
36
- /** The provider's sign-in or API key no longer works — NO retry, NO
37
- * fallback: dispatch for that provider pauses until someone signs in. */
13
+ export type TerminalClass = "usage-exhausted" | "credits-depleted" | "transient-throttle"
14
+ /** The provider's sign-in or API key no longer works — no retry:
15
+ * dispatch for that provider pauses until someone signs in. */
38
16
  | "auth-expired"
39
- /** A definite non-exhaustion terminal (ordinary failure). */
17
+ /** A definite structural failure terminal (ordinary failure). */
40
18
  | "not-exhausted"
41
- /** No structural signal matched — the ordinary failure/strike road. */
19
+ /** No structural signal — the ordinary failure/strike road. */
42
20
  | "unknown";
43
- /** The two classes that may authorize a fallback advance. */
44
- export declare const FALLBACK_ELIGIBLE: ReadonlySet<TerminalClass>;
45
- export declare function isFallbackEligible(cls: TerminalClass): boolean;
46
21
  /**
47
- * The structural terminal a provider's parser retained — the honest input
48
- * to classification. `failed` marks a structural FAILURE terminal (codex
49
- * turn.failed, a gemini error terminal): its presence blocks success
50
- * ingestion regardless of exit code. `text` is the bounded terminal
51
- * message; `code` its machine code if the harness typed one.
22
+ * The structural terminal a provider's parser retained. `failed` marks a
23
+ * structural FAILURE terminal (codex turn.failed, a gemini error terminal):
24
+ * its presence blocks success ingestion regardless of exit code. `text` is
25
+ * the bounded terminal message; `code` its machine code if the harness
26
+ * typed one.
52
27
  */
53
28
  export type StructuralTerminal = {
54
29
  failed: boolean;
55
30
  text: string | null;
56
31
  code: string | null;
57
32
  };
58
- /** Whether THIS build carries a fixture-backed recognizer for a
59
- * (provider, version). The C8 gate consults this at every authority
60
- * point — not only at classification — so a downgrade to an unfixtured
61
- * build severs any in-flight fallback authority. */
62
- export declare function hasRecognizer(provider: ProviderId, version: string | null): boolean;
63
- /**
64
- * The EXACT C8 capability check (Codex E2/E3 review, finding 8): whether
65
- * this build can recognize the specific eligible class an AUTH MODE would
66
- * produce — a subscription's `usage-exhausted` needs a nonempty
67
- * eligibleUsage set; an api-key account's `credits-depleted` needs a
68
- * nonempty eligibleCredits set. `hasRecognizer` (any-eligible) is too coarse
69
- * at the authority point: a version fixtured for usage only must NOT
70
- * authorize a credits-depleted advance.
71
- */
72
- export declare function recognizesEligible(provider: ProviderId, version: string | null, authMode: "subscription" | "api-key"): boolean;
73
- /** The eligible class an auth mode can legitimately carry — the C8
74
- * consistency invariant: `usage-exhausted` iff subscription,
75
- * `credits-depleted` iff api-key. A stamp that violates this pairing is a
76
- * bad/overwritten record and must never advance. */
77
- export declare function classMatchesAuthMode(cls: TerminalClass, authMode: "subscription" | "api-key"): boolean;
78
- /**
79
- * Classify a finished attempt. Fail closed: no recognizer for this exact
80
- * (provider, version) => never an eligible class. `authMode` decides which
81
- * eligible class a match yields — a subscription that hits its cap is
82
- * `usage-exhausted`; an api-key account out of credits is
83
- * `credits-depleted`. A structural failure with no eligible/transient
84
- * match is `not-exhausted`; no structural failure and no match is
85
- * `unknown`.
86
- */
33
+ /** Classify a finished attempt: a sign-in that no longer works first, for
34
+ * any provider or version; then a structural failure terminal is
35
+ * `not-exhausted`, and anything else `unknown`. */
87
36
  export declare function classifyTerminal(args: {
88
- provider: ProviderId;
89
- version: string | null;
90
- authMode: "subscription" | "api-key";
91
37
  terminal: StructuralTerminal | null;
92
38
  /** What a run that failed within seconds, having produced nothing, said
93
39
  * on its way out (stderr and unstructured stdout) — read ONLY for the