@mnemom/mnemom 0.17.33 → 0.17.34-next.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 (236) hide show
  1. package/dist/base-url-proxy.mjs +18 -4
  2. package/dist/commands/agent-config.js +5 -1
  3. package/dist/commands/agent-doctor.d.ts +9 -0
  4. package/dist/commands/agent-doctor.js +4 -0
  5. package/dist/commands/agent-session.d.ts +1 -2
  6. package/dist/commands/agent-session.js +1 -2
  7. package/dist/commands/agent-setup.d.ts +6 -0
  8. package/dist/commands/agent-setup.js +12 -1
  9. package/dist/commands/agent-share-render.d.ts +1 -1
  10. package/dist/commands/agent-share-render.js +1 -1
  11. package/dist/commands/agent.d.ts +26 -0
  12. package/dist/commands/agent.js +228 -53
  13. package/dist/commands/durable-doctor.d.ts +50 -0
  14. package/dist/commands/durable-doctor.js +186 -0
  15. package/dist/commands/durable-lock.d.ts +254 -0
  16. package/dist/commands/durable-lock.js +595 -0
  17. package/dist/commands/durable-sessions.d.ts +87 -0
  18. package/dist/commands/durable-sessions.js +303 -0
  19. package/dist/commands/durable-verbs.d.ts +116 -0
  20. package/dist/commands/durable-verbs.js +393 -0
  21. package/dist/commands/protection.js +1 -1
  22. package/dist/commands/scan.d.ts +97 -0
  23. package/dist/commands/scan.js +238 -0
  24. package/dist/durable/flock.d.ts +26 -0
  25. package/dist/durable/flock.js +80 -0
  26. package/dist/durable/launch-spec.d.ts +7 -1
  27. package/dist/durable/launch-spec.js +10 -3
  28. package/dist/durable/seat-proxy-entry.d.ts +11 -0
  29. package/dist/durable/seat-proxy-entry.js +25 -0
  30. package/dist/durable/seat-proxy.d.ts +107 -0
  31. package/dist/durable/seat-proxy.js +599 -0
  32. package/dist/durable/seat-socket.d.ts +46 -0
  33. package/dist/durable/seat-socket.js +179 -0
  34. package/dist/durable/seat-spec-validator.d.ts +28 -0
  35. package/dist/durable/seat-spec-validator.js +59 -0
  36. package/dist/durable/test-mode.mjs +81 -0
  37. package/dist/durable/turn-chain.d.ts +55 -0
  38. package/dist/durable/turn-chain.js +178 -0
  39. package/dist/host/auto-continue.d.ts +67 -0
  40. package/dist/host/auto-continue.js +118 -0
  41. package/dist/host/bootid.d.ts +13 -0
  42. package/dist/host/bootid.js +41 -0
  43. package/dist/host/client.d.ts +123 -0
  44. package/dist/host/client.js +171 -0
  45. package/dist/host/commands.d.ts +2 -0
  46. package/dist/host/commands.js +122 -0
  47. package/dist/host/control.d.ts +33 -0
  48. package/dist/host/control.js +144 -0
  49. package/dist/host/daemon.d.ts +217 -0
  50. package/dist/host/daemon.js +606 -0
  51. package/dist/host/gate.d.ts +46 -0
  52. package/dist/host/gate.js +82 -0
  53. package/dist/host/grant.d.ts +24 -0
  54. package/dist/host/grant.js +24 -0
  55. package/dist/host/instance.d.ts +35 -0
  56. package/dist/host/instance.js +62 -0
  57. package/dist/host/integrity.d.ts +35 -0
  58. package/dist/host/integrity.js +115 -0
  59. package/dist/host/launchd.d.ts +16 -0
  60. package/dist/host/launchd.js +45 -0
  61. package/dist/host/lock.d.ts +20 -0
  62. package/dist/host/lock.js +63 -0
  63. package/dist/host/owner.d.ts +55 -0
  64. package/dist/host/owner.js +121 -0
  65. package/dist/host/plist.d.ts +16 -0
  66. package/dist/host/plist.js +85 -0
  67. package/dist/host/procs.d.ts +27 -0
  68. package/dist/host/procs.js +105 -0
  69. package/dist/host/run.d.ts +66 -0
  70. package/dist/host/run.js +314 -0
  71. package/dist/host/runtime.d.ts +8 -0
  72. package/dist/host/runtime.js +22 -0
  73. package/dist/host/s1-names.d.ts +22 -0
  74. package/dist/host/s1-names.js +21 -0
  75. package/dist/host/seat-credential.d.ts +27 -0
  76. package/dist/host/seat-credential.js +41 -0
  77. package/dist/host/seat-launch-main.d.ts +1 -0
  78. package/dist/host/seat-launch-main.js +59 -0
  79. package/dist/host/seat-launch.d.ts +116 -0
  80. package/dist/host/seat-launch.js +302 -0
  81. package/dist/host/seat.d.ts +48 -0
  82. package/dist/host/seat.js +149 -0
  83. package/dist/host/state.d.ts +33 -0
  84. package/dist/host/state.js +117 -0
  85. package/dist/host/status.d.ts +101 -0
  86. package/dist/host/status.js +307 -0
  87. package/dist/host/tmux.d.ts +56 -0
  88. package/dist/host/tmux.js +182 -0
  89. package/dist/host/transcript.d.ts +30 -0
  90. package/dist/host/transcript.js +130 -0
  91. package/dist/host/verbs.d.ts +50 -0
  92. package/dist/host/verbs.js +271 -0
  93. package/dist/index.d.ts +8 -6
  94. package/dist/index.js +232 -35
  95. package/dist/lib/agent.d.ts +8 -26
  96. package/dist/lib/agent.js +11 -26
  97. package/dist/lib/api.d.ts +13 -0
  98. package/dist/lib/api.js +8 -0
  99. package/dist/lib/claude-plugin.d.ts +11 -2
  100. package/dist/lib/claude-plugin.js +10 -1
  101. package/dist/lib/durable/api.d.ts +136 -0
  102. package/dist/lib/durable/api.js +220 -0
  103. package/dist/lib/durable/codes.d.ts +8 -0
  104. package/dist/lib/durable/codes.js +2 -0
  105. package/dist/lib/durable/first-turn.d.ts +50 -0
  106. package/dist/lib/durable/first-turn.js +102 -0
  107. package/dist/lib/durable/gate.d.ts +48 -0
  108. package/dist/lib/durable/gate.js +122 -0
  109. package/dist/lib/durable/handover.d.ts +101 -0
  110. package/dist/lib/durable/handover.js +175 -0
  111. package/dist/lib/durable/launch.d.ts +182 -0
  112. package/dist/lib/durable/launch.js +443 -0
  113. package/dist/lib/durable/launching.d.ts +15 -0
  114. package/dist/lib/durable/launching.js +70 -0
  115. package/dist/lib/durable/live.d.ts +194 -0
  116. package/dist/lib/durable/live.js +635 -0
  117. package/dist/lib/durable/refusal.d.ts +57 -0
  118. package/dist/lib/durable/refusal.js +78 -0
  119. package/dist/lib/durable/view.d.ts +83 -0
  120. package/dist/lib/durable/view.js +244 -0
  121. package/dist/lib/experimental.d.ts +0 -9
  122. package/dist/lib/experimental.js +18 -17
  123. package/dist/lib/launcher-config.d.ts +7 -0
  124. package/dist/lib/launcher-config.js +21 -0
  125. package/dist/lib/scan/caps.d.ts +35 -0
  126. package/dist/lib/scan/caps.js +67 -0
  127. package/dist/lib/scan/credential.d.ts +37 -0
  128. package/dist/lib/scan/credential.js +104 -0
  129. package/dist/lib/scan/failure.d.ts +10 -0
  130. package/dist/lib/scan/failure.js +43 -0
  131. package/dist/lib/scan/pr-source.d.ts +57 -0
  132. package/dist/lib/scan/pr-source.js +130 -0
  133. package/dist/lib/scan/record.d.ts +49 -0
  134. package/dist/lib/scan/record.js +85 -0
  135. package/dist/lib/scan/report-email.d.ts +92 -0
  136. package/dist/lib/scan/report-email.js +144 -0
  137. package/dist/lib/scan/report.d.ts +13 -0
  138. package/dist/lib/scan/report.js +30 -0
  139. package/dist/lib/scan/scan.d.ts +64 -0
  140. package/dist/lib/scan/scan.js +182 -0
  141. package/dist/lib/scan/share.d.ts +38 -0
  142. package/dist/lib/scan/share.js +47 -0
  143. package/dist/lib/scan/spend/ai-tool-detect.d.ts +6 -0
  144. package/dist/lib/scan/spend/ai-tool-detect.js +18 -0
  145. package/dist/lib/scan/spend/anthropic-usage.d.ts +57 -0
  146. package/dist/lib/scan/spend/anthropic-usage.js +244 -0
  147. package/dist/lib/scan/spend/cost-corpus.v1.d.ts +56 -0
  148. package/dist/lib/scan/spend/cost-corpus.v1.js +64 -0
  149. package/dist/lib/scan/spend/prices.d.ts +33 -0
  150. package/dist/lib/scan/spend/prices.js +57 -0
  151. package/dist/lib/scan/spend/spend-estimate.d.ts +30 -0
  152. package/dist/lib/scan/spend/spend-estimate.js +70 -0
  153. package/dist/lib/scan/spend/spend-tiers.d.ts +25 -0
  154. package/dist/lib/scan/spend/spend-tiers.js +42 -0
  155. package/dist/lib/scan/spend/token-rate.d.ts +30 -0
  156. package/dist/lib/scan/spend/token-rate.js +54 -0
  157. package/dist/lib/session-key.d.ts +41 -0
  158. package/dist/lib/session-key.js +124 -0
  159. package/dist/lib/subscription-login.d.ts +15 -0
  160. package/dist/lib/subscription-login.js +28 -1
  161. package/dist/rc-key-401.mjs +75 -0
  162. package/dist/rc-proxy.mjs +221 -25
  163. package/dist/vendor/durable-contract/index.js +240 -18
  164. package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
  165. package/dist/vendor/shared/durable-contract/admission.js +533 -0
  166. package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
  167. package/dist/vendor/shared/durable-contract/arm.js +133 -0
  168. package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
  169. package/dist/vendor/shared/durable-contract/canonical.js +98 -0
  170. package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
  171. package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
  172. package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
  173. package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
  174. package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
  175. package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
  176. package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
  177. package/dist/vendor/shared/durable-contract/codes.js +475 -0
  178. package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
  179. package/dist/vendor/shared/durable-contract/constants.js +179 -0
  180. package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
  181. package/dist/vendor/shared/durable-contract/doors.js +249 -0
  182. package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
  183. package/dist/vendor/shared/durable-contract/envelope.js +317 -0
  184. package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
  185. package/dist/vendor/shared/durable-contract/events.js +630 -0
  186. package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
  187. package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
  188. package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
  189. package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
  190. package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
  191. package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
  192. package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
  193. package/dist/vendor/shared/durable-contract/index.js +37 -0
  194. package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
  195. package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
  196. package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
  197. package/dist/vendor/shared/durable-contract/keys.js +66 -0
  198. package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
  199. package/dist/vendor/shared/durable-contract/lock.js +151 -0
  200. package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
  201. package/dist/vendor/shared/durable-contract/names.js +78 -0
  202. package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
  203. package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
  204. package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
  205. package/dist/vendor/shared/durable-contract/paths.js +35 -0
  206. package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
  207. package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
  208. package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
  209. package/dist/vendor/shared/durable-contract/routes.js +315 -0
  210. package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
  211. package/dist/vendor/shared/durable-contract/schema.js +441 -0
  212. package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
  213. package/dist/vendor/shared/durable-contract/seal.js +99 -0
  214. package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
  215. package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
  216. package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
  217. package/dist/vendor/shared/durable-contract/session-id.js +58 -0
  218. package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
  219. package/dist/vendor/shared/durable-contract/spec.js +286 -0
  220. package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
  221. package/dist/vendor/shared/durable-contract/states.js +218 -0
  222. package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
  223. package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
  224. package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
  225. package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
  226. package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
  227. package/dist/vendor/shared/durable-contract/turn.js +81 -0
  228. package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
  229. package/dist/vendor/shared/durable-contract/vectors.js +208 -0
  230. package/package.json +4 -3
  231. package/plugins/integrity-manifest.json +35 -0
  232. package/plugins/mnemom/hooks/hooks.json +15 -1
  233. package/plugins/mnemom/hooks/register.tsx +60 -2
  234. package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
  235. package/dist/commands/agent-implicit-notice.d.ts +0 -31
  236. package/dist/commands/agent-implicit-notice.js +0 -73
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Durable refusals on the CLI (wave-0 fix ruling 19; S1 cli-output.ts). One shape for every refused
3
+ * durable verb, with or without --json:
4
+ *
5
+ * stderr <refusal line>
6
+ * fix: <S1 catalogue line, rendered under the fix-line rule> (when there is one)
7
+ * code: <machine code>
8
+ * stdout with --json only: exactly one JSON document, the ruling-14 error envelope. An API or
9
+ * gateway refusal body is passed through unchanged; a refusal the CLI decides itself is
10
+ * built with S1 `cliRefusalJson` (details.fix carries the rendered line).
11
+ * exit CLI_REFUSAL_EXIT (1).
12
+ *
13
+ * The refusal line never contains "code:"/"error:" text, so the first machine-readable stderr token
14
+ * is always the code line.
15
+ */
16
+ import { CLI_REFUSAL_EXIT, cliRefusalJson, } from "../../vendor/shared/durable-contract/cli-output.js";
17
+ import { isDurableCode } from "../../vendor/shared/durable-contract/codes.js";
18
+ import { renderFixLine, } from "../../vendor/shared/durable-contract/fixlines.js";
19
+ /** A refused durable verb. Commands throw it; `printDurableRefusal` renders it. */
20
+ export class DurableRefusal extends Error {
21
+ code;
22
+ fix;
23
+ details;
24
+ apiBody;
25
+ exit;
26
+ fixParams;
27
+ constructor(init) {
28
+ super(init.message);
29
+ this.name = "DurableRefusal";
30
+ this.code = init.code;
31
+ this.fix = init.fix ?? null;
32
+ this.details = init.details ?? {};
33
+ this.apiBody = init.apiBody;
34
+ this.exit = init.exit ?? CLI_REFUSAL_EXIT;
35
+ this.fixParams = init.fixParams ?? {};
36
+ }
37
+ }
38
+ /** Render one catalogue line (thin wrapper so callers never restate a fix text). */
39
+ export function fixLine(key, launcherKey, nameHeld, params) {
40
+ const ctx = { launcherKey, nameHeld, params };
41
+ return renderFixLine(key, ctx);
42
+ }
43
+ /** The --json stdout document for a refusal. */
44
+ export function refusalJson(r) {
45
+ if (r.apiBody !== undefined && r.apiBody !== null)
46
+ return r.apiBody;
47
+ const details = { ...r.details };
48
+ if (r.fix)
49
+ details.fix = r.fix;
50
+ if (isDurableCode(r.code) && r.fix) {
51
+ return cliRefusalJson(r.code, r.message, details);
52
+ }
53
+ // A non-durable code (agent_not_found, nested_launch_refused, …) or no fix: the same envelope.
54
+ return {
55
+ error: {
56
+ code: r.code,
57
+ message: r.message,
58
+ ...(Object.keys(details).length ? { details } : {}),
59
+ },
60
+ };
61
+ }
62
+ /** Print a refusal (stderr always; stdout JSON only with --json) and return its exit status. */
63
+ export function printDurableRefusal(r, json, sink) {
64
+ sink.err(r.message);
65
+ if (r.fix)
66
+ sink.err(`fix: ${r.fix}`);
67
+ sink.err(`code: ${r.code}`);
68
+ if (json)
69
+ sink.out(JSON.stringify(refusalJson(r)));
70
+ return r.exit;
71
+ }
72
+ /**
73
+ * The agent action's catch (index.ts): a DurableRefusal is printed (refusal line, fix, code) and its
74
+ * exit status returned; anything else is null and goes to the generic CLI error report.
75
+ */
76
+ export function durableRefusalExit(error, sink) {
77
+ return error instanceof DurableRefusal ? printDurableRefusal(error, false, sink) : null;
78
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Pure presentation and decision helpers for durable sessions on the CLI (C01-R12, R16, R24, R25,
3
+ * R28, R30; C11-R27). No I/O: every input is passed in, so each branch is unit-tested.
4
+ */
5
+ import { type FixKey, type LauncherKey } from "../../vendor/shared/durable-contract/fixlines.js";
6
+ import { type EffectiveState, type StateReason, type UserState } from "../../vendor/shared/durable-contract/states.js";
7
+ import type { RegistryRow } from "./api.js";
8
+ /** The effective state the CLI branches on (GET returns both; I23). */
9
+ export declare function effectiveOf(row: RegistryRow): EffectiveState;
10
+ export declare function isLocked(row: RegistryRow): boolean;
11
+ export declare function isEnded(row: RegistryRow): boolean;
12
+ export declare function reasonOf(row: RegistryRow): StateReason | null;
13
+ /** The user-facing state word (running | waiting | stopped | ended). */
14
+ export declare function userState(row: RegistryRow): UserState;
15
+ /** "ended: agent killed" style label: user state, then the reason in words when there is one. */
16
+ export declare function stateLabel(row: RegistryRow): string;
17
+ /** Spend column (C01-R25): guard_state.spend_usd for locked rows, NA_TEXT for tracked ones. */
18
+ export declare function spendText(row: RegistryRow): string;
19
+ /** --json spend: the cents string for locked rows, null for tracked ones. */
20
+ export declare function spendJson(row: RegistryRow): string | null;
21
+ export declare function committedBudget(row: RegistryRow): number;
22
+ /** The home machine's name, or null when the row carries none. */
23
+ export declare function machineName(row: RegistryRow): string | null;
24
+ /** Where the session lives, for the machine column. */
25
+ export declare function machineText(row: RegistryRow): string;
26
+ export declare function lastActivity(row: RegistryRow): string | null;
27
+ export interface HomeFacts {
28
+ /** This machine's executor id from its state file, or null (no enrollment here). */
29
+ localExecutorId: string | null;
30
+ /** This machine's label (machine_label), for the unreachable-home rule. */
31
+ localMachineLabel: string | null;
32
+ nowMs: number;
33
+ }
34
+ export type HomeKind = "here" | "unreachable" | "elsewhere_live" | "elsewhere_stale";
35
+ export declare function homeKind(row: RegistryRow, f: HomeFacts): HomeKind;
36
+ export interface FixContext {
37
+ launcherKey: LauncherKey;
38
+ /** The name is held by a non-terminal row (it is when this row is non-terminal). */
39
+ nameHeld: boolean;
40
+ }
41
+ /** The catalogue key for a row's stopped or ended state, or null for running/waiting. */
42
+ export declare function stateFixKey(row: RegistryRow, extra?: {
43
+ agentKilled?: boolean;
44
+ cwdCause?: "missing" | "not_toplevel" | null;
45
+ }): FixKey | null;
46
+ export declare function renderKey(key: FixKey, row: RegistryRow, ctx: FixContext, params?: Record<string, string>): string;
47
+ /** The unreachable-home line (C01-R28): done-then-new, same text in continue, sessions and show. */
48
+ export declare function unreachableHomeFix(row: RegistryRow, ctx: FixContext): string;
49
+ /** The grant warning (BACKBONE C1): days left when inside GRANT_WARNING_S, else null. */
50
+ export declare function grantWarning(row: RegistryRow, nowMs: number): string | null;
51
+ /** The single fix line a row shows in `sessions` / `show` (null when it needs none). */
52
+ export declare function rowFixLine(row: RegistryRow, ctx: FixContext, home: HomeFacts | null): string | null;
53
+ export type ContinueDecision = {
54
+ kind: "fresh";
55
+ } | {
56
+ kind: "tracked_continue";
57
+ row: RegistryRow;
58
+ } | {
59
+ kind: "attach";
60
+ row: RegistryRow;
61
+ wait: boolean;
62
+ } | {
63
+ kind: "refuse";
64
+ code: string;
65
+ message: string;
66
+ fix: string | null;
67
+ details?: Record<string, unknown>;
68
+ };
69
+ export interface ContinueInput {
70
+ /** Every row the caller owns under this name in the active org (any state). */
71
+ rows: RegistryRow[];
72
+ launcherKey: LauncherKey;
73
+ home: HomeFacts;
74
+ /** This machine's host daemon job is loaded (C01-R16 (c)). */
75
+ daemonLoaded: boolean;
76
+ /** The agent the name's latest session ran on is killed (C01-R16 (j), R27). */
77
+ agentKilled: boolean;
78
+ /** An explicit --agent that differs from the row's agent (C01-R17). */
79
+ explicitAgentId: string | null;
80
+ }
81
+ /** The non-terminal row holding the name, if any (names are unique among non-terminal rows). */
82
+ export declare function liveRow(rows: RegistryRow[]): RegistryRow | null;
83
+ export declare function continueDecision(i: ContinueInput): ContinueDecision;
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Pure presentation and decision helpers for durable sessions on the CLI (C01-R12, R16, R24, R25,
3
+ * R28, R30; C11-R27). No I/O: every input is passed in, so each branch is unit-tested.
4
+ */
5
+ import { EXECUTOR_LIVE_S, GRANT_WARNING_S, PER_SESSION_BUDGET_LIMIT_USD, } from "../../vendor/shared/durable-contract/constants.js";
6
+ import { formatSpendCents, grantWarningDays, NA_TEXT, renderFixLine, stoppedFixKey, } from "../../vendor/shared/durable-contract/fixlines.js";
7
+ import { endedBody, isEffectiveState, isSessionState, isTerminalState, userStateOf, } from "../../vendor/shared/durable-contract/states.js";
8
+ import { CODE } from "./codes.js";
9
+ /** The effective state the CLI branches on (GET returns both; I23). */
10
+ export function effectiveOf(row) {
11
+ const e = row.effective_state;
12
+ if (isEffectiveState(e))
13
+ return e;
14
+ if (isSessionState(row.state))
15
+ return row.state;
16
+ return "running";
17
+ }
18
+ export function isLocked(row) {
19
+ return row.durable_mode === "locked";
20
+ }
21
+ export function isEnded(row) {
22
+ const e = effectiveOf(row);
23
+ return e === "expired" || (isSessionState(e) && isTerminalState(e));
24
+ }
25
+ export function reasonOf(row) {
26
+ return (row.state_reason ?? null);
27
+ }
28
+ /** The user-facing state word (running | waiting | stopped | ended). */
29
+ export function userState(row) {
30
+ return userStateOf(effectiveOf(row));
31
+ }
32
+ /** "ended: agent killed" style label: user state, then the reason in words when there is one. */
33
+ export function stateLabel(row) {
34
+ const u = userState(row);
35
+ const e = effectiveOf(row);
36
+ if (e === "suspended_grant_expired")
37
+ return `${u}: grant expired`;
38
+ const reason = reasonOf(row);
39
+ if (u === "ended" && e === "expired")
40
+ return "ended: expired";
41
+ if (reason)
42
+ return `${u}: ${reason.replace(/_/g, " ")}`;
43
+ return u;
44
+ }
45
+ /** Spend column (C01-R25): guard_state.spend_usd for locked rows, NA_TEXT for tracked ones. */
46
+ export function spendText(row) {
47
+ if (!isLocked(row))
48
+ return NA_TEXT;
49
+ const s = row.guard_state?.spend_usd ?? row.spend_usd ?? null;
50
+ return typeof s === "number" && Number.isFinite(s) && s >= 0 ? formatSpendCents(s) : NA_TEXT;
51
+ }
52
+ /** --json spend: the cents string for locked rows, null for tracked ones. */
53
+ export function spendJson(row) {
54
+ const t = spendText(row);
55
+ return t === NA_TEXT ? null : t;
56
+ }
57
+ export function committedBudget(row) {
58
+ const b = row.guard_state?.budget_usd ?? row.budget_usd ?? 0;
59
+ return typeof b === "number" && Number.isFinite(b) ? b : 0;
60
+ }
61
+ /** The home machine's name, or null when the row carries none. */
62
+ export function machineName(row) {
63
+ const h = row.home_executor;
64
+ return h?.machine_label ?? h?.hostname ?? row.machine ?? null;
65
+ }
66
+ /** Where the session lives, for the machine column. */
67
+ export function machineText(row) {
68
+ return machineName(row) ?? NA_TEXT;
69
+ }
70
+ export function lastActivity(row) {
71
+ return row.last_activity_at ?? row.updated_at ?? row.created_at ?? null;
72
+ }
73
+ export function homeKind(row, f) {
74
+ const h = row.home_executor ?? null;
75
+ const homeId = row.home_executor_id ?? h?.id ?? null;
76
+ if (h?.revoked_at)
77
+ return "unreachable";
78
+ if (homeId !== null && f.localExecutorId !== null && homeId === f.localExecutorId)
79
+ return "here";
80
+ const seen = h?.last_seen_at ? Date.parse(h.last_seen_at) : NaN;
81
+ const stale = !Number.isFinite(seen) || f.nowMs - seen > EXECUTOR_LIVE_S * 1000;
82
+ if (stale && f.localMachineLabel !== null && h?.machine_label === f.localMachineLabel)
83
+ return "unreachable";
84
+ return stale ? "elsewhere_stale" : "elsewhere_live";
85
+ }
86
+ /** The catalogue key for a row's stopped or ended state, or null for running/waiting. */
87
+ export function stateFixKey(row, extra = {}) {
88
+ const e = effectiveOf(row);
89
+ const reason = reasonOf(row);
90
+ const ended = endedBody(e, reason);
91
+ if (ended !== null)
92
+ return ended.state_reason === "agent_killed" || extra.agentKilled ? "agent_killed" : null;
93
+ if (!isLocked(row))
94
+ return null;
95
+ if (e === "paused" || e === "suspended" || e === "suspended_grant_expired") {
96
+ try {
97
+ return stoppedFixKey(e, reason, {
98
+ committedBudgetUsd: committedBudget(row),
99
+ cwdCause: extra.cwdCause ?? null,
100
+ });
101
+ }
102
+ catch {
103
+ return "stopped_no_m1_remedy";
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ export function renderKey(key, row, ctx, params = {}) {
109
+ const name = row.name ?? row.conversation_id;
110
+ return renderFixLine(key, {
111
+ launcherKey: ctx.launcherKey,
112
+ nameHeld: ctx.nameHeld,
113
+ params: {
114
+ name,
115
+ agent: row.agent_name ?? row.agent_id,
116
+ limit: String(PER_SESSION_BUDGET_LIMIT_USD),
117
+ n: String(PER_SESSION_BUDGET_LIMIT_USD),
118
+ machine: machineText(row),
119
+ days: "",
120
+ cwd: row.cwd ?? "",
121
+ toplevel: row.cwd ?? "",
122
+ checkout: row.cwd ?? "",
123
+ realpath: row.cwd ?? "",
124
+ ...params,
125
+ },
126
+ });
127
+ }
128
+ /** The unreachable-home line (C01-R28): done-then-new, same text in continue, sessions and show. */
129
+ export function unreachableHomeFix(row, ctx) {
130
+ return renderKey("unreachable_home", row, { ...ctx, nameHeld: true });
131
+ }
132
+ /** The grant warning (BACKBONE C1): days left when inside GRANT_WARNING_S, else null. */
133
+ export function grantWarning(row, nowMs) {
134
+ const g = row.home_executor?.grant_expires_at;
135
+ if (!g)
136
+ return null;
137
+ const left = (Date.parse(g) - nowMs) / 1000;
138
+ const days = grantWarningDays(left, GRANT_WARNING_S);
139
+ if (days === null)
140
+ return null;
141
+ return renderFixLine("grant_warning", {
142
+ launcherKey: "always",
143
+ nameHeld: false,
144
+ params: { days: String(days) },
145
+ });
146
+ }
147
+ /** The single fix line a row shows in `sessions` / `show` (null when it needs none). */
148
+ export function rowFixLine(row, ctx, home) {
149
+ if (isLocked(row) && !isEnded(row) && home) {
150
+ const k = homeKind(row, home);
151
+ if (k === "unreachable")
152
+ return unreachableHomeFix(row, ctx);
153
+ }
154
+ const key = stateFixKey(row);
155
+ if (key === null)
156
+ return null;
157
+ return renderKey(key, row, { ...ctx, nameHeld: ctx.nameHeld && !isEnded(row) });
158
+ }
159
+ /** The non-terminal row holding the name, if any (names are unique among non-terminal rows). */
160
+ export function liveRow(rows) {
161
+ return rows.find((r) => !isEnded(r)) ?? null;
162
+ }
163
+ export function continueDecision(i) {
164
+ const row = liveRow(i.rows);
165
+ const ctx = { launcherKey: i.launcherKey, nameHeld: row !== null };
166
+ if (row === null) {
167
+ // (j) every session under the name ended: fresh, unless the agent was killed.
168
+ const last = i.rows[0];
169
+ if (last && i.agentKilled && i.explicitAgentId === null) {
170
+ return {
171
+ kind: "refuse",
172
+ code: CODE.durable_session_ended,
173
+ message: `${last.name ?? "this session"}: the agent was killed`,
174
+ fix: renderKey("agent_killed", last, { ...ctx, nameHeld: false }),
175
+ details: { state: "killed", state_reason: "agent_killed" },
176
+ };
177
+ }
178
+ return { kind: "fresh" };
179
+ }
180
+ if (i.explicitAgentId !== null && i.explicitAgentId !== row.agent_id) {
181
+ return {
182
+ kind: "refuse",
183
+ code: "session_agent_mismatch",
184
+ message: `${row.name}: this session runs on agent ${row.agent_name ?? row.agent_id}; a session never changes agents`,
185
+ fix: null,
186
+ details: { agent_id: row.agent_id },
187
+ };
188
+ }
189
+ if (!isLocked(row))
190
+ return { kind: "tracked_continue", row };
191
+ const e = effectiveOf(row);
192
+ // stopped (d)-(i): their own line, wherever the home is.
193
+ const stoppedKey = stateFixKey(row);
194
+ if (e === "paused" || e === "suspended" || e === "suspended_grant_expired") {
195
+ return {
196
+ kind: "refuse",
197
+ code: CODE.durable_session_stopped,
198
+ message: `${row.name} is ${stateLabel(row)}`,
199
+ fix: stoppedKey ? renderKey(stoppedKey, row, ctx) : null,
200
+ details: { state: row.state, state_reason: row.state_reason ?? null },
201
+ };
202
+ }
203
+ const home = homeKind(row, i.home);
204
+ if (home === "unreachable") {
205
+ // C01-R28: only a home elsewhere is `session_homed_elsewhere`. S1 names no code for an
206
+ // unreachable home, so it takes a CLI-local one (like cwd_untrusted), never a durable_* name.
207
+ return {
208
+ kind: "refuse",
209
+ code: "home_unreachable",
210
+ message: `${row.name}: its home machine can no longer run it`,
211
+ fix: unreachableHomeFix(row, ctx),
212
+ };
213
+ }
214
+ if (home === "elsewhere_live" || home === "elsewhere_stale") {
215
+ // Both fix lines name <machine>: with no machine name the line is omitted, never printed with a
216
+ // placeholder (C11-R27).
217
+ const machine = machineName(row);
218
+ if (machine === null) {
219
+ return {
220
+ kind: "refuse",
221
+ code: "session_homed_elsewhere",
222
+ message: `${row.name} runs on another machine`,
223
+ fix: null,
224
+ };
225
+ }
226
+ return {
227
+ kind: "refuse",
228
+ code: "session_homed_elsewhere",
229
+ message: `${row.name} runs on ${machine}`,
230
+ fix: renderKey(home === "elsewhere_live" ? "session_homed_elsewhere" : "session_homed_elsewhere_stale", row, ctx),
231
+ details: { machine },
232
+ };
233
+ }
234
+ // home here: running/parked/detached/resuming
235
+ if (!i.daemonLoaded) {
236
+ return {
237
+ kind: "refuse",
238
+ code: "session_locked",
239
+ message: `${row.name} is locked and the host daemon is not running here`,
240
+ fix: renderKey("daemon_not_running", row, ctx),
241
+ };
242
+ }
243
+ return { kind: "attach", row, wait: e !== "running" };
244
+ }
@@ -63,15 +63,6 @@ export declare function resolveExperimental(opts: {
63
63
  stored: ExperimentalPrefs | undefined;
64
64
  env: Record<string, string | undefined>;
65
65
  }): ExperimentalState;
66
- /**
67
- * Features a prerelease (`-next.N`) build makes available without `mnemom experimental
68
- * enable`: door one's T16 turns implicit goals on by default on `next` (decisions 12 and
69
- * 22), so its flag, its launcher.toml value and `mnemom agent goal` are not refused there.
70
- * Stable is unchanged.
71
- */
72
- export declare const PRERELEASE_AVAILABLE: readonly ExperimentalId[];
73
- /** The features whose flags, keys and commands are usable: the enabled ones, plus PRERELEASE_AVAILABLE on a prerelease. */
74
- export declare function availableFeatures(enabled: readonly ExperimentalId[], prerelease: boolean): ExperimentalId[];
75
66
  /** The one-line stderr notice for a launch with experimental features on; null when none are. */
76
67
  export declare function experimentalNotice(enabled: readonly ExperimentalId[]): string | null;
77
68
  /** The error for using an experimental flag or command while its feature is off. */
@@ -108,15 +108,23 @@ export const EXPERIMENTAL_FEATURES = [
108
108
  },
109
109
  },
110
110
  {
111
- // Durable governed sessions (M1). Off: every durable verb word exits 2 with the gate pointer
112
- // and launches are unchanged; the host daemon reads config.json only, never MNEMOM_EXPERIMENTAL.
111
+ // Durable M1 (BACKBONE Flags/CLI): every durable verb word, `mnemom agent host …` and
112
+ // `mnemom agent containment …` exit 2 with the pointer while off; the host daemon and
113
+ // `host install` read the gate from config.json only (never MNEMOM_EXPERIMENTAL).
113
114
  id: "durable",
114
- summary: "Durable sessions: lock a session so this machine keeps it running, and adopt running ones",
115
+ summary: "Durable sessions: lock a session so the host daemon (`mnemom agent host`) keeps it running and brings it back after a crash or reboot, and adopt running ones",
115
116
  since: "0.17.27",
116
117
  gates: {
117
- flags: [],
118
- configKeys: ["durable"],
119
- commands: ["mnemom agent sessions adopt"],
118
+ flags: ["--new"],
119
+ configKeys: ["durable = ask|always"],
120
+ commands: [
121
+ "mnemom agent lock|pause|resume|done|kill|amend",
122
+ "mnemom agent containment",
123
+ "mnemom agent containment pause|resume",
124
+ "mnemom agent host",
125
+ "mnemom agent sessions unlock",
126
+ "mnemom agent sessions adopt",
127
+ ],
120
128
  env: [],
121
129
  },
122
130
  },
@@ -175,17 +183,6 @@ export function resolveExperimental(opts) {
175
183
  unknown: stored.filter((t) => !isExperimentalId(t)),
176
184
  };
177
185
  }
178
- /**
179
- * Features a prerelease (`-next.N`) build makes available without `mnemom experimental
180
- * enable`: door one's T16 turns implicit goals on by default on `next` (decisions 12 and
181
- * 22), so its flag, its launcher.toml value and `mnemom agent goal` are not refused there.
182
- * Stable is unchanged.
183
- */
184
- export const PRERELEASE_AVAILABLE = ["implicit"];
185
- /** The features whose flags, keys and commands are usable: the enabled ones, plus PRERELEASE_AVAILABLE on a prerelease. */
186
- export function availableFeatures(enabled, prerelease) {
187
- return normalize(prerelease ? [...enabled, ...PRERELEASE_AVAILABLE] : enabled);
188
- }
189
186
  /** The one-line stderr notice for a launch with experimental features on; null when none are. */
190
187
  export function experimentalNotice(enabled) {
191
188
  if (enabled.length === 0)
@@ -229,6 +226,8 @@ export function experimentalIdForConfigKey(key, value) {
229
226
  return value === "true" ? "context-handoff" : undefined;
230
227
  case "goal_mode":
231
228
  return value === "implicit" ? "implicit" : undefined;
229
+ case "durable":
230
+ return value === "off" ? undefined : "durable";
232
231
  default:
233
232
  return undefined;
234
233
  }
@@ -307,6 +306,8 @@ export function configKeyFeature(key) {
307
306
  return "context-handoff";
308
307
  if (key === "goal_mode")
309
308
  return "implicit";
309
+ if (key === "durable")
310
+ return "durable";
310
311
  return undefined;
311
312
  }
312
313
  /**
@@ -16,6 +16,7 @@
16
16
  * the caller folds the plain-value settings under the CLI flags.
17
17
  */
18
18
  import { type SavedAgentAuth } from "./subscription-login.js";
19
+ import { type LauncherKey } from "../vendor/shared/durable-contract/fixlines.js";
19
20
  import { type ExperimentalId } from "./experimental.js";
20
21
  import { type ContextProfile, type GoalModeDefault, type LaunchShape } from "./agent.js";
21
22
  /** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
@@ -85,6 +86,12 @@ export interface LauncherConfig {
85
86
  * "off", "350000"), parsed with `parseCompactAt` at the point of use.
86
87
  */
87
88
  compactAt?: string;
89
+ /**
90
+ * Durable launcher key (EXPERIMENTAL `durable`; S1 LAUNCHER_KEYS): `off` (default) launches
91
+ * tracked, `ask` offers to lock on a TTY, `always` locks every launch. Ignored with the
92
+ * pointer while `durable` is off (`durable = off` is harmless either way).
93
+ */
94
+ durable?: LauncherKey;
88
95
  guardrails?: {
89
96
  maxTurns?: number;
90
97
  budgetUsd?: number;
@@ -20,6 +20,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
20
20
  import { dirname, join } from "node:path";
21
21
  import { parse as parseToml, stringify as stringifyToml, TomlError } from "smol-toml";
22
22
  import { MNEMOM_DIR } from "./config.js";
23
+ import { LAUNCHER_KEYS } from "../vendor/shared/durable-contract/fixlines.js";
23
24
  import { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseCompactAt, parseAgentRef, parseContextProfile, } from "./agent.js";
24
25
  /** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
25
26
  export const LAUNCHER_CONFIG_PATH = join(MNEMOM_DIR, "launcher.toml");
@@ -27,6 +28,12 @@ const LAUNCH_SHAPES = ["terminal", "remote-control", "server"];
27
28
  const EFFORTS = ["low", "medium", "high", "xhigh", "max"];
28
29
  const KEY_SOURCES = ["env", "store", "prompt"];
29
30
  const GOAL_MODES = ["implicit", "off"];
31
+ function coerceDurable(raw) {
32
+ const v = raw.trim().toLowerCase();
33
+ if (LAUNCHER_KEYS.includes(v))
34
+ return v;
35
+ throw new Error(`durable must be one of ${LAUNCHER_KEYS.join(", ")} (got '${raw}').`);
36
+ }
30
37
  function coerceLaunch(raw) {
31
38
  const v = raw.trim();
32
39
  if (LAUNCH_SHAPES.includes(v))
@@ -180,6 +187,14 @@ export const CONFIG_KEYS = [
180
187
  c.goalMode = coerceGoalMode(v);
181
188
  },
182
189
  },
190
+ {
191
+ key: "durable",
192
+ describe: `Durable sessions (${LAUNCHER_KEYS.join("|")}): lock launches so the host daemon runs them (experimental)`,
193
+ get: (c) => c.durable,
194
+ set: (c, v) => {
195
+ c.durable = coerceDurable(v);
196
+ },
197
+ },
183
198
  {
184
199
  key: "context",
185
200
  describe: "Context folding (on by default; false disables it for every launch)",
@@ -330,6 +345,8 @@ export function parseLauncherConfig(text) {
330
345
  cfg.keySource = coerceKeySource(str(raw.key_source) ?? "");
331
346
  if (raw.goal_mode !== undefined)
332
347
  cfg.goalMode = coerceGoalMode(str(raw.goal_mode) ?? "");
348
+ if (raw.durable !== undefined)
349
+ cfg.durable = coerceDurable(str(raw.durable) ?? "");
333
350
  if (raw.context !== undefined)
334
351
  cfg.context = coerceBool("context", str(raw.context) ?? "");
335
352
  if (raw.slack_listen !== undefined)
@@ -414,6 +431,8 @@ export function serializeLauncherConfig(cfg) {
414
431
  doc.key_source = cfg.keySource;
415
432
  if (cfg.goalMode !== undefined)
416
433
  doc.goal_mode = cfg.goalMode;
434
+ if (cfg.durable !== undefined)
435
+ doc.durable = cfg.durable;
417
436
  if (cfg.context !== undefined)
418
437
  doc.context = cfg.context;
419
438
  if (cfg.slackChannel !== undefined)
@@ -527,5 +546,7 @@ export function withoutDisabledExperimental(cfg, enabled) {
527
546
  delete out.contextHandoff;
528
547
  if (!enabled.includes("implicit") && out.goalMode === "implicit")
529
548
  delete out.goalMode;
549
+ if (!enabled.includes("durable"))
550
+ delete out.durable;
530
551
  return out;
531
552
  }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Coverage caps and sampling. Port of mnemom-api src/org-scan/scan/capped-scan.ts (PR #4208).
3
+ *
4
+ * Coverage decision (Shraddha, 2026-10-28): each scan covers the last 3 months OR the last 2,000 merged
5
+ * PRs, whichever is MORE; above 2,000 in the window, an even deterministic sample of 2,000.
6
+ */
7
+ export declare const COVERAGE_MONTHS = 3;
8
+ export interface ScanCaps {
9
+ maxPrs: number;
10
+ /** A PR with more additions+deletions than this is skipped, never read in full. */
11
+ maxPrSizeLines: number;
12
+ /** Upstream read calls. */
13
+ maxReads: number;
14
+ /** Wall-clock budget. */
15
+ maxMillis: number;
16
+ }
17
+ export declare const DEFAULT_SCAN_CAPS: ScanCaps;
18
+ export type StopReason = "pr-cap" | "read-cap" | "time-cap";
19
+ /** The day `months` calendar months before `end` (YYYY-MM-DD, UTC), clamped to the month's last day. */
20
+ export declare function monthsBefore(end: string, months: number): string;
21
+ /** `k` items spread evenly across `items` (order kept): index floor(i*n/k). Deterministic; n <= k returns all. */
22
+ export declare function evenSample<T>(items: readonly T[], k: number): T[];
23
+ export declare function sanitiseCaps(c: Partial<ScanCaps>): ScanCaps;
24
+ /** Running read budget. Every call is told how many reads it may spend (`room`). */
25
+ export declare class Budget {
26
+ private caps;
27
+ private now;
28
+ private startedAt;
29
+ reads: number;
30
+ constructor(caps: ScanCaps, now: () => number, startedAt: number);
31
+ block(n?: number): StopReason | null;
32
+ room(): number;
33
+ overrun(): StopReason | null;
34
+ charge(n: number): void;
35
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Coverage caps and sampling. Port of mnemom-api src/org-scan/scan/capped-scan.ts (PR #4208).
3
+ *
4
+ * Coverage decision (Shraddha, 2026-10-28): each scan covers the last 3 months OR the last 2,000 merged
5
+ * PRs, whichever is MORE; above 2,000 in the window, an even deterministic sample of 2,000.
6
+ */
7
+ export const COVERAGE_MONTHS = 3;
8
+ export const DEFAULT_SCAN_CAPS = {
9
+ maxPrs: 2_000,
10
+ maxPrSizeLines: 20_000,
11
+ maxReads: 4_500,
12
+ maxMillis: 15 * 60_000,
13
+ };
14
+ /** The day `months` calendar months before `end` (YYYY-MM-DD, UTC), clamped to the month's last day. */
15
+ export function monthsBefore(end, months) {
16
+ const [y, m, d] = end.split("-").map(Number);
17
+ const target = new Date(Date.UTC(y, m - 1 - months, 1));
18
+ const last = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
19
+ target.setUTCDate(Math.min(d, last));
20
+ return target.toISOString().slice(0, 10);
21
+ }
22
+ /** `k` items spread evenly across `items` (order kept): index floor(i*n/k). Deterministic; n <= k returns all. */
23
+ export function evenSample(items, k) {
24
+ const n = items.length;
25
+ if (n <= k)
26
+ return [...items];
27
+ return Array.from({ length: k }, (_, i) => items[Math.floor((i * n) / k)]);
28
+ }
29
+ export function sanitiseCaps(c) {
30
+ const pos = (n, d) => typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : d;
31
+ return {
32
+ maxPrs: pos(c.maxPrs, DEFAULT_SCAN_CAPS.maxPrs),
33
+ maxPrSizeLines: pos(c.maxPrSizeLines, DEFAULT_SCAN_CAPS.maxPrSizeLines),
34
+ maxReads: pos(c.maxReads, DEFAULT_SCAN_CAPS.maxReads),
35
+ maxMillis: pos(c.maxMillis, DEFAULT_SCAN_CAPS.maxMillis),
36
+ };
37
+ }
38
+ /** Running read budget. Every call is told how many reads it may spend (`room`). */
39
+ export class Budget {
40
+ caps;
41
+ now;
42
+ startedAt;
43
+ reads = 0;
44
+ constructor(caps, now, startedAt) {
45
+ this.caps = caps;
46
+ this.now = now;
47
+ this.startedAt = startedAt;
48
+ }
49
+ block(n = 1) {
50
+ if (this.now() - this.startedAt >= this.caps.maxMillis)
51
+ return "time-cap";
52
+ if (this.reads + n > this.caps.maxReads)
53
+ return "read-cap";
54
+ return null;
55
+ }
56
+ room() {
57
+ if (this.now() - this.startedAt >= this.caps.maxMillis)
58
+ return 0;
59
+ return Math.max(0, this.caps.maxReads - this.reads);
60
+ }
61
+ overrun() {
62
+ return this.reads > this.caps.maxReads ? "read-cap" : null;
63
+ }
64
+ charge(n) {
65
+ this.reads += n;
66
+ }
67
+ }