@tokenfactory/acc-runner 0.40.11 → 0.40.13

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 (48) hide show
  1. package/dist/capacity/account-window.d.ts +64 -0
  2. package/dist/capacity/account-window.d.ts.map +1 -0
  3. package/dist/capacity/account-window.js +66 -0
  4. package/dist/capacity/account-window.js.map +1 -0
  5. package/dist/capacity/fleet-pause-verbs.d.ts +38 -0
  6. package/dist/capacity/fleet-pause-verbs.d.ts.map +1 -0
  7. package/dist/capacity/fleet-pause-verbs.js +26 -0
  8. package/dist/capacity/fleet-pause-verbs.js.map +1 -0
  9. package/dist/capacity/index.d.ts +15 -0
  10. package/dist/capacity/index.d.ts.map +1 -0
  11. package/dist/capacity/index.js +15 -0
  12. package/dist/capacity/index.js.map +1 -0
  13. package/dist/capacity/resume-controller.d.ts +150 -0
  14. package/dist/capacity/resume-controller.d.ts.map +1 -0
  15. package/dist/capacity/resume-controller.js +229 -0
  16. package/dist/capacity/resume-controller.js.map +1 -0
  17. package/dist/capacity/resume-policy.d.ts +101 -0
  18. package/dist/capacity/resume-policy.d.ts.map +1 -0
  19. package/dist/capacity/resume-policy.js +80 -0
  20. package/dist/capacity/resume-policy.js.map +1 -0
  21. package/dist/engines/account-identity.d.ts +54 -0
  22. package/dist/engines/account-identity.d.ts.map +1 -0
  23. package/dist/engines/account-identity.js +93 -0
  24. package/dist/engines/account-identity.js.map +1 -0
  25. package/dist/engines/account-probe.d.ts +124 -0
  26. package/dist/engines/account-probe.d.ts.map +1 -0
  27. package/dist/engines/account-probe.js +134 -0
  28. package/dist/engines/account-probe.js.map +1 -0
  29. package/dist/engines/claude-code-chat.d.ts +15 -1
  30. package/dist/engines/claude-code-chat.d.ts.map +1 -1
  31. package/dist/engines/claude-code-chat.js +118 -1
  32. package/dist/engines/claude-code-chat.js.map +1 -1
  33. package/dist/engines/codex-chat.d.ts.map +1 -1
  34. package/dist/engines/codex-chat.js +23 -0
  35. package/dist/engines/codex-chat.js.map +1 -1
  36. package/dist/engines/gemini.d.ts.map +1 -1
  37. package/dist/engines/gemini.js +20 -0
  38. package/dist/engines/gemini.js.map +1 -1
  39. package/dist/engines/types.d.ts +43 -0
  40. package/dist/engines/types.d.ts.map +1 -1
  41. package/dist/task-runner.d.ts.map +1 -1
  42. package/dist/task-runner.js +17 -0
  43. package/dist/task-runner.js.map +1 -1
  44. package/dist/watch.d.ts +11 -1
  45. package/dist/watch.d.ts.map +1 -1
  46. package/dist/watch.js +25 -3
  47. package/dist/watch.js.map +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-controller.js","sourceRoot":"","sources":["../../src/capacity/resume-controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * AS-3 (RESUME): the PURE pause-until decision core.
3
+ *
4
+ * Replaces the fixed pause-until-reset timer (watch.ts's single `setTimeout`
5
+ * keyed to the capped account's stated reset) with
6
+ *
7
+ * pause-until ( current account's reset elapsed
8
+ * OR the logged-in account CHANGED
9
+ * OR a cheap probe ping says we can serve )
10
+ *
11
+ * plus the operator's symmetric requirement: swapping to a DIFFERENT account
12
+ * that is itself still-capped in memory RE-PAUSES from memory (no request
13
+ * burned) rather than resuming into an account that will just 429 again.
14
+ *
15
+ * Flap protection (reuse of the CC-3A pattern — api/_lib/capacity/
16
+ * identity-capacity.ts `cooldownMinDwellMs: 60_000`): an account-change
17
+ * adoption (resume or re-pause) is DEBOUNCED — a second account-change within
18
+ * `adoptionDebounceMs` of the last adoption is DEFERRED, so a keychain that
19
+ * flaps A→B→A→B faster than the debounce collapses to a single adoption instead
20
+ * of thrashing resume/pause. The capacity-window hysteresis (never reopen a
21
+ * capped window on a null-reset guess) lives in the fold (account-window.ts).
22
+ *
23
+ * PURE by construction: no clock, no timer, no I/O — every input is passed in,
24
+ * so the whole decision surface is exercised with a fake clock in unit tests.
25
+ */
26
+ import { type AccountWindow } from "./account-window.js";
27
+ /** Whether the runner is currently claiming (`active`) or capacity-paused (`paused`). */
28
+ export type CapacityMode = "active" | "paused";
29
+ /** Inputs for one resume/re-pause decision. */
30
+ export interface ResumeEvalInput {
31
+ /** Current runner mode. */
32
+ mode: CapacityMode;
33
+ /** Wall-clock now (epoch ms) from the injected clock. */
34
+ nowMs: number;
35
+ /**
36
+ * The account the probe currently observes (its `synthetic_account`), or
37
+ * `null` when the probe cannot introspect one this tick.
38
+ */
39
+ observedAccountKey: string | null;
40
+ /**
41
+ * The account the current state is anchored to: the capped account while
42
+ * paused, or the last-known serving account while active. `null` before the
43
+ * first observation.
44
+ */
45
+ anchorAccountKey: string | null;
46
+ /** Per-account remembered capacity windows (keyed on `synthetic_account`). */
47
+ windows: ReadonlyMap<string, AccountWindow>;
48
+ /**
49
+ * Result of a cheap probe ping for the observed account THIS tick:
50
+ * true = the account can serve now (resume),
51
+ * false = still capped,
52
+ * null = not probed / can't tell.
53
+ * Only consulted while paused on the same account.
54
+ */
55
+ pingOk: boolean | null;
56
+ /**
57
+ * Epoch ms of the last account-change adoption (resume or re-pause), or
58
+ * `null` if none yet — the initial cap-pause is NOT an adoption, so the first
59
+ * swap after a pause is never debounced.
60
+ */
61
+ lastAdoptionAtMs: number | null;
62
+ /** Minimum gap between account-change adoptions (flap protection). */
63
+ adoptionDebounceMs: number;
64
+ }
65
+ /** Why a resume fired. */
66
+ export type ResumeReason = "reset" | "account-change" | "ping-ok";
67
+ /** The decision the controller acts on. */
68
+ export type ResumeDecision =
69
+ /** Stay in the current mode. */
70
+ {
71
+ action: "stay";
72
+ }
73
+ /** Leave the pause and resume claiming, on `account`. */
74
+ | {
75
+ action: "resume";
76
+ reason: ResumeReason;
77
+ account: string;
78
+ }
79
+ /** Re-pause: the observed account is a KNOWN still-capped account (from memory). */
80
+ | {
81
+ action: "pause";
82
+ reason: "returning-capped-account";
83
+ account: string;
84
+ resetAtMs: number | null;
85
+ }
86
+ /** An account change is held back by the adoption debounce; retry after the hold. */
87
+ | {
88
+ action: "defer";
89
+ reason: "adoption-debounce";
90
+ account: string;
91
+ };
92
+ /**
93
+ * Decide the next action. Pure — the controller supplies every input and
94
+ * executes the returned action (arming timers, calling seams). Ordering:
95
+ * 1. account swap (highest signal) — debounced, then resume-or-re-pause by
96
+ * the new account's remembered window;
97
+ * 2. same account while paused — reset elapsed, else a positive ping;
98
+ * 3. otherwise stay.
99
+ */
100
+ export declare function evaluateResume(input: ResumeEvalInput): ResumeDecision;
101
+ //# sourceMappingURL=resume-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-policy.d.ts","sourceRoot":"","sources":["../../src/capacity/resume-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAExE,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/C,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,8EAA8E;IAC9E,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,0BAA0B;AAC1B,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAElE,2CAA2C;AAC3C,MAAM,MAAM,cAAc;AACxB,gCAAgC;AAC9B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;AACpB,yDAAyD;GACvD;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAC7D,oFAAoF;GAClF;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,0BAA0B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AACpG,qFAAqF;GACnF;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAOtE;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,CA4CrE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * AS-3 (RESUME): the PURE pause-until decision core.
3
+ *
4
+ * Replaces the fixed pause-until-reset timer (watch.ts's single `setTimeout`
5
+ * keyed to the capped account's stated reset) with
6
+ *
7
+ * pause-until ( current account's reset elapsed
8
+ * OR the logged-in account CHANGED
9
+ * OR a cheap probe ping says we can serve )
10
+ *
11
+ * plus the operator's symmetric requirement: swapping to a DIFFERENT account
12
+ * that is itself still-capped in memory RE-PAUSES from memory (no request
13
+ * burned) rather than resuming into an account that will just 429 again.
14
+ *
15
+ * Flap protection (reuse of the CC-3A pattern — api/_lib/capacity/
16
+ * identity-capacity.ts `cooldownMinDwellMs: 60_000`): an account-change
17
+ * adoption (resume or re-pause) is DEBOUNCED — a second account-change within
18
+ * `adoptionDebounceMs` of the last adoption is DEFERRED, so a keychain that
19
+ * flaps A→B→A→B faster than the debounce collapses to a single adoption instead
20
+ * of thrashing resume/pause. The capacity-window hysteresis (never reopen a
21
+ * capped window on a null-reset guess) lives in the fold (account-window.ts).
22
+ *
23
+ * PURE by construction: no clock, no timer, no I/O — every input is passed in,
24
+ * so the whole decision surface is exercised with a fake clock in unit tests.
25
+ */
26
+ import { isStillCapped } from "./account-window.js";
27
+ /** True when the observed account differs from the anchor (a login swap). */
28
+ function accountSwapped(input) {
29
+ return input.observedAccountKey !== null && input.observedAccountKey !== input.anchorAccountKey;
30
+ }
31
+ /**
32
+ * Decide the next action. Pure — the controller supplies every input and
33
+ * executes the returned action (arming timers, calling seams). Ordering:
34
+ * 1. account swap (highest signal) — debounced, then resume-or-re-pause by
35
+ * the new account's remembered window;
36
+ * 2. same account while paused — reset elapsed, else a positive ping;
37
+ * 3. otherwise stay.
38
+ */
39
+ export function evaluateResume(input) {
40
+ if (accountSwapped(input)) {
41
+ const account = input.observedAccountKey;
42
+ // Flap protection: coalesce rapid account-change adoptions.
43
+ if (input.lastAdoptionAtMs !== null &&
44
+ input.nowMs - input.lastAdoptionAtMs < input.adoptionDebounceMs) {
45
+ return { action: "defer", reason: "adoption-debounce", account };
46
+ }
47
+ const window = input.windows.get(account) ?? null;
48
+ if (isStillCapped(window, input.nowMs)) {
49
+ // Swapped to a known still-capped account → re-pause from memory.
50
+ if (input.mode === "paused" && input.anchorAccountKey === account) {
51
+ return { action: "stay" };
52
+ }
53
+ return {
54
+ action: "pause",
55
+ reason: "returning-capped-account",
56
+ account,
57
+ resetAtMs: window ? window.resetAtMs : null,
58
+ };
59
+ }
60
+ // Fresh / unknown account → its quota is available; resume when paused.
61
+ if (input.mode === "paused") {
62
+ return { action: "resume", reason: "account-change", account };
63
+ }
64
+ return { action: "stay" };
65
+ }
66
+ if (input.mode === "paused") {
67
+ const key = input.observedAccountKey ?? input.anchorAccountKey;
68
+ const window = key ? input.windows.get(key) ?? null : null;
69
+ // Reset elapsed on the capped account's known window.
70
+ if (window && window.state === "capped" && window.resetAtMs !== null && input.nowMs >= window.resetAtMs) {
71
+ return { action: "resume", reason: "reset", account: key };
72
+ }
73
+ // Cheap probe ping says the window reopened early.
74
+ if (input.pingOk === true && key !== null) {
75
+ return { action: "resume", reason: "ping-ok", account: key };
76
+ }
77
+ }
78
+ return { action: "stay" };
79
+ }
80
+ //# sourceMappingURL=resume-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-policy.js","sourceRoot":"","sources":["../../src/capacity/resume-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,aAAa,EAAsB,MAAM,qBAAqB,CAAC;AAwDxE,6EAA6E;AAC7E,SAAS,cAAc,CAAC,KAAsB;IAC5C,OAAO,KAAK,CAAC,kBAAkB,KAAK,IAAI,IAAI,KAAK,CAAC,kBAAkB,KAAK,KAAK,CAAC,gBAAgB,CAAC;AAClG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,kBAA4B,CAAC;QACnD,4DAA4D;QAC5D,IACE,KAAK,CAAC,gBAAgB,KAAK,IAAI;YAC/B,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,gBAAgB,GAAG,KAAK,CAAC,kBAAkB,EAC/D,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAC;QACnE,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAClD,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,kEAAkE;YAClE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,gBAAgB,KAAK,OAAO,EAAE,CAAC;gBAClE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAC5B,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,0BAA0B;gBAClC,OAAO;gBACP,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;aAC5C,CAAC;QACJ,CAAC;QACD,wEAAwE;QACxE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;QACjE,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,kBAAkB,IAAI,KAAK,CAAC,gBAAgB,CAAC;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,sDAAsD;QACtD,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACxG,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAG,GAAc,EAAE,CAAC;QACzE,CAAC;QACD,mDAAmD;QACnD,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC1C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { EngineAccountIdentity, EngineAuthMode, EnginePlanHint } from "./types.js";
2
+ /**
3
+ * Derive the stable synthetic account id.
4
+ * - With an email: `acct:<lowercased-trimmed-email>` — the SAME login on two
5
+ * hosts/channels collapses to one id, which is exactly the convergence the
6
+ * broadcaster wants.
7
+ * - Without an email: `host:<hostname>/<engineId>/<channel>` — stable for a
8
+ * given runner install + channel, distinct across hosts, and never null.
9
+ */
10
+ export declare function syntheticAccountId(args: {
11
+ engineId: string;
12
+ channel: EngineAuthMode;
13
+ email?: string | null;
14
+ hostname?: string;
15
+ }): string;
16
+ /**
17
+ * Build the HONEST "unknown-account" identity an adapter reports when it cannot
18
+ * introspect a real account (codex/gemini today, or claude-code in api-key mode
19
+ * where a bare key carries no email). Never fabricates an email or a plan.
20
+ */
21
+ export declare function unknownAccountIdentity(args: {
22
+ engineId: string;
23
+ channel: EngineAuthMode;
24
+ planHint?: EnginePlanHint;
25
+ detail: string;
26
+ hostname?: string;
27
+ }): EngineAccountIdentity;
28
+ /**
29
+ * Build a RESOLVED account identity from an introspected email (+ optional
30
+ * plan). The synthetic id is email-derived so it converges across hosts.
31
+ */
32
+ export declare function resolvedAccountIdentity(args: {
33
+ engineId: string;
34
+ channel: EngineAuthMode;
35
+ email: string;
36
+ planHint?: EnginePlanHint;
37
+ detail: string;
38
+ }): EngineAccountIdentity;
39
+ /**
40
+ * The identity used for change detection / broadcast keying. Two probes name the
41
+ * same account iff their `synthetic_account` matches (email-derived when known,
42
+ * host-derived otherwise) — so a login swap on one host, an email that appears
43
+ * where there was none, or a channel flip are all detected, while a plain
44
+ * cadence refresh that resolves the same account is a no-op.
45
+ */
46
+ export declare function accountKey(identity: EngineAccountIdentity): string;
47
+ /**
48
+ * True when `next` names a different account than `prev` (or there was no prior
49
+ * account). The broadcaster audits on `true` and last-writer-wins-skips on
50
+ * `false`. A plan_hint-only change (same account, upgraded plan) also counts as
51
+ * a change worth re-broadcasting so downstream cap logic sees the new plan.
52
+ */
53
+ export declare function accountChanged(prev: EngineAccountIdentity | null, next: EngineAccountIdentity): boolean;
54
+ //# sourceMappingURL=account-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-identity.d.ts","sourceRoot":"","sources":["../../src/engines/account-identity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EACd,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAKT;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,qBAAqB,CAYxB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,CAaxB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAElE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,qBAAqB,GAAG,IAAI,EAClC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAGT"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * AS-1 (ACCOUNT-PROBE): shared, side-effect-free helpers for the engine
3
+ * `accountIdentity()` surface (types.ts). Two concerns live here so every
4
+ * adapter (claude-code / codex / gemini) spells them identically:
5
+ *
6
+ * 1. Synthetic-account derivation — a STABLE id an engine reports so the
7
+ * identity broadcaster always has a concrete participant to converge on,
8
+ * even when the real account email is unintrospectable. Email-derived when
9
+ * an email resolves (same login → same id across hosts); host+engine+channel
10
+ * derived otherwise (an unknown-account runner is still stable + distinct
11
+ * rather than a flapping null).
12
+ * 2. Change detection / audit — `accountChanged()` compares the last-broadcast
13
+ * identity against a freshly-probed one so the last-writer-wins broadcaster
14
+ * can decide whether an emit is a no-op refresh or a real login swap worth
15
+ * auditing (the ajay→gaurav evidence that motivated the task).
16
+ *
17
+ * PURE by construction (the one impurity — hostname — is passed in), so the
18
+ * cadence + parsing logic is unit-testable without touching `os` or a clock.
19
+ */
20
+ import os from "node:os";
21
+ /**
22
+ * Derive the stable synthetic account id.
23
+ * - With an email: `acct:<lowercased-trimmed-email>` — the SAME login on two
24
+ * hosts/channels collapses to one id, which is exactly the convergence the
25
+ * broadcaster wants.
26
+ * - Without an email: `host:<hostname>/<engineId>/<channel>` — stable for a
27
+ * given runner install + channel, distinct across hosts, and never null.
28
+ */
29
+ export function syntheticAccountId(args) {
30
+ const email = (args.email ?? "").trim().toLowerCase();
31
+ if (email)
32
+ return `acct:${email}`;
33
+ const host = (args.hostname ?? os.hostname() ?? "").trim() || "unknown-host";
34
+ return `host:${host}/${args.engineId}/${args.channel}`;
35
+ }
36
+ /**
37
+ * Build the HONEST "unknown-account" identity an adapter reports when it cannot
38
+ * introspect a real account (codex/gemini today, or claude-code in api-key mode
39
+ * where a bare key carries no email). Never fabricates an email or a plan.
40
+ */
41
+ export function unknownAccountIdentity(args) {
42
+ return {
43
+ account_email: null,
44
+ plan_hint: args.planHint ?? "unknown",
45
+ channel: args.channel,
46
+ synthetic_account: syntheticAccountId({
47
+ engineId: args.engineId,
48
+ channel: args.channel,
49
+ hostname: args.hostname,
50
+ }),
51
+ detail: args.detail,
52
+ };
53
+ }
54
+ /**
55
+ * Build a RESOLVED account identity from an introspected email (+ optional
56
+ * plan). The synthetic id is email-derived so it converges across hosts.
57
+ */
58
+ export function resolvedAccountIdentity(args) {
59
+ const email = args.email.trim();
60
+ return {
61
+ account_email: email,
62
+ plan_hint: args.planHint ?? "unknown",
63
+ channel: args.channel,
64
+ synthetic_account: syntheticAccountId({
65
+ engineId: args.engineId,
66
+ channel: args.channel,
67
+ email,
68
+ }),
69
+ detail: args.detail,
70
+ };
71
+ }
72
+ /**
73
+ * The identity used for change detection / broadcast keying. Two probes name the
74
+ * same account iff their `synthetic_account` matches (email-derived when known,
75
+ * host-derived otherwise) — so a login swap on one host, an email that appears
76
+ * where there was none, or a channel flip are all detected, while a plain
77
+ * cadence refresh that resolves the same account is a no-op.
78
+ */
79
+ export function accountKey(identity) {
80
+ return identity.synthetic_account;
81
+ }
82
+ /**
83
+ * True when `next` names a different account than `prev` (or there was no prior
84
+ * account). The broadcaster audits on `true` and last-writer-wins-skips on
85
+ * `false`. A plan_hint-only change (same account, upgraded plan) also counts as
86
+ * a change worth re-broadcasting so downstream cap logic sees the new plan.
87
+ */
88
+ export function accountChanged(prev, next) {
89
+ if (!prev)
90
+ return true;
91
+ return accountKey(prev) !== accountKey(next) || prev.plan_hint !== next.plan_hint;
92
+ }
93
+ //# sourceMappingURL=account-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-identity.js","sourceRoot":"","sources":["../../src/engines/account-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AAOzB;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAKlC;IACC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,KAAK;QAAE,OAAO,QAAQ,KAAK,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,cAAc,CAAC;IAC7E,OAAO,QAAQ,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAMtC;IACC,OAAO;QACL,aAAa,EAAE,IAAI;QACnB,SAAS,EAAE,IAAI,CAAC,QAAQ,IAAI,SAAS;QACrC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,iBAAiB,EAAE,kBAAkB,CAAC;YACpC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAMvC;IACC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,OAAO;QACL,aAAa,EAAE,KAAK;QACpB,SAAS,EAAE,IAAI,CAAC,QAAQ,IAAI,SAAS;QACrC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,iBAAiB,EAAE,kBAAkB,CAAC;YACpC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK;SACN,CAAC;QACF,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,QAA+B;IACxD,OAAO,QAAQ,CAAC,iBAAiB,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAkC,EAClC,IAA2B;IAE3B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC;AACpF,CAAC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * AS-1 (ACCOUNT-PROBE): the account-probe CADENCE scheduler.
3
+ *
4
+ * Extends the engine credential probe with a WHO-am-I probe fired on four
5
+ * triggers so a mid-flight login swap can never go undetected (operator
6
+ * evidence: swapping the keychain login ajay→gaurav went undetected while the
7
+ * runner sat paused on the old account's reset timer):
8
+ *
9
+ * 1. startup — one probe the moment the scheduler starts.
10
+ * 2. pause-tick — every ~60s WHILE the runner is capacity-paused (the exact
11
+ * window the ajay→gaurav swap slipped through: a paused
12
+ * runner otherwise only re-probed on the 5-min heartbeat).
13
+ * 3. heartbeat — every ~5min while healthy (unpaused).
14
+ * 4. auth-failure — immediately after any 401/403-class engine failure
15
+ * (debounced so a burst of 401s is a single probe).
16
+ *
17
+ * Each probe result is handed to `emit` with a MONOTONIC sequence + wall-clock
18
+ * stamp so the identity broadcaster resolves concurrent writers last-writer-wins,
19
+ * and a `changed` flag so a real login swap is audited while a plain cadence
20
+ * refresh of the same account is a cheap no-op.
21
+ *
22
+ * FULLY injectable (probe / emit / clock / timer) so the four-trigger cadence is
23
+ * exercised with a fake clock — no real binary, no wall-clock sleep, no network.
24
+ * Wiring this scheduler onto the companion's identity broadcaster is a follow-up
25
+ * (companion.ts is outside this track's file scope); this module is the engine
26
+ * primitive that follow-up drives.
27
+ */
28
+ import type { EngineAccountIdentity } from "./types.js";
29
+ /** Which cadence trigger fired a given probe. */
30
+ export type AccountProbeTrigger = "startup" | "pause-tick" | "heartbeat" | "auth-failure";
31
+ /** One account-probe broadcast handed to the identity broadcaster. */
32
+ export interface AccountProbeBroadcast {
33
+ /** The probed account identity. */
34
+ identity: EngineAccountIdentity;
35
+ /** Which cadence trigger produced this probe. */
36
+ trigger: AccountProbeTrigger;
37
+ /**
38
+ * True when this probe named a DIFFERENT account (or plan) than the last
39
+ * broadcast — the audit signal a login swap fires on. False on a plain
40
+ * same-account cadence refresh.
41
+ */
42
+ changed: boolean;
43
+ /** Monotonic 1-based sequence for last-writer-wins resolution on the topic. */
44
+ seq: number;
45
+ /** Wall-clock ms of the probe (from the injected clock). */
46
+ at: number;
47
+ }
48
+ /** Timer seam so the cadence is drivable by a fake clock in tests. */
49
+ export interface ProbeTimer {
50
+ /** Schedule `fn` after `ms`; returns an opaque handle. */
51
+ setTimer(fn: () => void, ms: number): unknown;
52
+ /** Cancel a scheduled handle. */
53
+ clearTimer(handle: unknown): void;
54
+ }
55
+ export interface AccountProbeSchedulerDeps {
56
+ /** Probe the engine account. Should itself degrade to an honest `"unknown"` identity rather than throw. */
57
+ probe: () => Promise<EngineAccountIdentity>;
58
+ /** Emit a probed identity on the identity broadcaster (last-writer-wins keyed on `seq`). */
59
+ emit: (broadcast: AccountProbeBroadcast) => void | Promise<void>;
60
+ /** Healthy heartbeat cadence (ms). Default 5min. */
61
+ heartbeatMs?: number;
62
+ /** While-paused probe cadence (ms). Default 60s. */
63
+ pauseTickMs?: number;
64
+ /** Min gap between auth-failure probes (ms) so a 401 burst is one probe. Default 1s. */
65
+ authFailureDebounceMs?: number;
66
+ /** Clock (test seam). Default `Date.now`. */
67
+ now?: () => number;
68
+ /** Timer seam (test seam). Default `setTimeout`/`clearTimeout`. */
69
+ timer?: ProbeTimer;
70
+ /** Best-effort audit/log sink for detected swaps + probe errors. */
71
+ onLog?: (line: string) => void;
72
+ }
73
+ /**
74
+ * Drives the four-trigger account-probe cadence. Probes are SERIALIZED onto one
75
+ * chain so sequence numbers are strictly monotonic and two triggers never race
76
+ * to emit an out-of-order identity. Call `start()` to fire the startup probe and
77
+ * arm the recurring timer, `setPaused()` on capacity transitions, and
78
+ * `onAuthFailure()` from the engine failure path.
79
+ */
80
+ export declare class AccountProbeScheduler {
81
+ private readonly deps;
82
+ private readonly heartbeatMs;
83
+ private readonly pauseTickMs;
84
+ private readonly authFailureDebounceMs;
85
+ private readonly now;
86
+ private readonly timer;
87
+ private started;
88
+ private stopped;
89
+ private paused;
90
+ private recurringHandle;
91
+ private seq;
92
+ private last;
93
+ private lastAuthFailureAt;
94
+ /** Serial probe chain: every probe awaits the prior one so `seq` stays ordered. */
95
+ private tail;
96
+ constructor(deps: AccountProbeSchedulerDeps);
97
+ /** The last identity broadcast (null before the first probe). */
98
+ lastIdentity(): EngineAccountIdentity | null;
99
+ /** Fire the startup probe and arm the recurring cadence timer. Idempotent. */
100
+ start(): void;
101
+ /**
102
+ * Note a capacity pause/resume transition. Re-arms the recurring timer to the
103
+ * matching cadence (~60s paused, ~5min healthy). A no-op when the state is
104
+ * unchanged, so repeated pause ticks don't reset the timer out from under
105
+ * themselves.
106
+ */
107
+ setPaused(paused: boolean): void;
108
+ /**
109
+ * Probe immediately after a 401/403-class engine failure — the single most
110
+ * likely moment the underlying login just changed. Debounced so a burst of
111
+ * auth failures collapses to one probe.
112
+ */
113
+ onAuthFailure(): void;
114
+ /** Cancel the recurring timer; in-flight probes still settle. */
115
+ stop(): void;
116
+ /** Resolve once every queued probe has settled (test seam). */
117
+ settle(): Promise<void>;
118
+ private armRecurring;
119
+ private onRecurringFire;
120
+ /** Chain a probe onto the serial tail so sequence order is guaranteed. */
121
+ private enqueue;
122
+ private runProbe;
123
+ }
124
+ //# sourceMappingURL=account-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-probe.d.ts","sourceRoot":"","sources":["../../src/engines/account-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxD,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAE1F,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,iDAAiD;IACjD,OAAO,EAAE,mBAAmB,CAAC;IAC7B;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,iCAAiC;IACjC,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAOD,MAAM,WAAW,yBAAyB;IACxC,2GAA2G;IAC3G,KAAK,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5C,4FAA4F;IAC5F,IAAI,EAAE,CAAC,SAAS,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,mEAAmE;IACnE,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED;;;;;;GAMG;AACH,qBAAa,qBAAqB;IAiBpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAhBjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IAEnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,IAAI,CAAsC;IAClD,OAAO,CAAC,iBAAiB,CAA4B;IACrD,mFAAmF;IACnF,OAAO,CAAC,IAAI,CAAoC;gBAEnB,IAAI,EAAE,yBAAyB;IAQ5D,iEAAiE;IACjE,YAAY,IAAI,qBAAqB,GAAG,IAAI;IAI5C,8EAA8E;IAC9E,KAAK,IAAI,IAAI;IAOb;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAMhC;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAQrB,iEAAiE;IACjE,IAAI,IAAI,IAAI;IAQZ,+DAA+D;IACzD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,eAAe;IAQvB,0EAA0E;IAC1E,OAAO,CAAC,OAAO;YAID,QAAQ;CA2BvB"}
@@ -0,0 +1,134 @@
1
+ import { accountChanged } from "./account-identity.js";
2
+ const DEFAULT_TIMER = {
3
+ setTimer: (fn, ms) => setTimeout(fn, ms),
4
+ clearTimer: (h) => clearTimeout(h),
5
+ };
6
+ /**
7
+ * Drives the four-trigger account-probe cadence. Probes are SERIALIZED onto one
8
+ * chain so sequence numbers are strictly monotonic and two triggers never race
9
+ * to emit an out-of-order identity. Call `start()` to fire the startup probe and
10
+ * arm the recurring timer, `setPaused()` on capacity transitions, and
11
+ * `onAuthFailure()` from the engine failure path.
12
+ */
13
+ export class AccountProbeScheduler {
14
+ deps;
15
+ heartbeatMs;
16
+ pauseTickMs;
17
+ authFailureDebounceMs;
18
+ now;
19
+ timer;
20
+ started = false;
21
+ stopped = false;
22
+ paused = false;
23
+ recurringHandle = null;
24
+ seq = 0;
25
+ last = null;
26
+ lastAuthFailureAt = Number.NEGATIVE_INFINITY;
27
+ /** Serial probe chain: every probe awaits the prior one so `seq` stays ordered. */
28
+ tail = Promise.resolve();
29
+ constructor(deps) {
30
+ this.deps = deps;
31
+ this.heartbeatMs = Math.max(1, deps.heartbeatMs ?? 5 * 60_000);
32
+ this.pauseTickMs = Math.max(1, deps.pauseTickMs ?? 60_000);
33
+ this.authFailureDebounceMs = Math.max(0, deps.authFailureDebounceMs ?? 1_000);
34
+ this.now = deps.now ?? Date.now;
35
+ this.timer = deps.timer ?? DEFAULT_TIMER;
36
+ }
37
+ /** The last identity broadcast (null before the first probe). */
38
+ lastIdentity() {
39
+ return this.last;
40
+ }
41
+ /** Fire the startup probe and arm the recurring cadence timer. Idempotent. */
42
+ start() {
43
+ if (this.started || this.stopped)
44
+ return;
45
+ this.started = true;
46
+ this.enqueue("startup");
47
+ this.armRecurring();
48
+ }
49
+ /**
50
+ * Note a capacity pause/resume transition. Re-arms the recurring timer to the
51
+ * matching cadence (~60s paused, ~5min healthy). A no-op when the state is
52
+ * unchanged, so repeated pause ticks don't reset the timer out from under
53
+ * themselves.
54
+ */
55
+ setPaused(paused) {
56
+ if (this.stopped || paused === this.paused)
57
+ return;
58
+ this.paused = paused;
59
+ if (this.started)
60
+ this.armRecurring();
61
+ }
62
+ /**
63
+ * Probe immediately after a 401/403-class engine failure — the single most
64
+ * likely moment the underlying login just changed. Debounced so a burst of
65
+ * auth failures collapses to one probe.
66
+ */
67
+ onAuthFailure() {
68
+ if (this.stopped || !this.started)
69
+ return;
70
+ const t = this.now();
71
+ if (t - this.lastAuthFailureAt < this.authFailureDebounceMs)
72
+ return;
73
+ this.lastAuthFailureAt = t;
74
+ this.enqueue("auth-failure");
75
+ }
76
+ /** Cancel the recurring timer; in-flight probes still settle. */
77
+ stop() {
78
+ this.stopped = true;
79
+ if (this.recurringHandle !== null) {
80
+ this.timer.clearTimer(this.recurringHandle);
81
+ this.recurringHandle = null;
82
+ }
83
+ }
84
+ /** Resolve once every queued probe has settled (test seam). */
85
+ async settle() {
86
+ await this.tail;
87
+ }
88
+ armRecurring() {
89
+ if (this.recurringHandle !== null) {
90
+ this.timer.clearTimer(this.recurringHandle);
91
+ this.recurringHandle = null;
92
+ }
93
+ if (this.stopped)
94
+ return;
95
+ const ms = this.paused ? this.pauseTickMs : this.heartbeatMs;
96
+ this.recurringHandle = this.timer.setTimer(() => this.onRecurringFire(), ms);
97
+ }
98
+ onRecurringFire() {
99
+ if (this.stopped)
100
+ return;
101
+ // Re-arm BEFORE the async probe so the cadence is decoupled from probe
102
+ // latency and stays deterministic under a fake clock.
103
+ this.armRecurring();
104
+ this.enqueue(this.paused ? "pause-tick" : "heartbeat");
105
+ }
106
+ /** Chain a probe onto the serial tail so sequence order is guaranteed. */
107
+ enqueue(trigger) {
108
+ this.tail = this.tail.then(() => this.runProbe(trigger));
109
+ }
110
+ async runProbe(trigger) {
111
+ let identity;
112
+ try {
113
+ identity = await this.deps.probe();
114
+ }
115
+ catch (err) {
116
+ this.deps.onLog?.(`[acc-runner] account probe (${trigger}) failed: ${err?.message ?? String(err)}`);
117
+ return;
118
+ }
119
+ const changed = accountChanged(this.last, identity);
120
+ const seq = ++this.seq;
121
+ const at = this.now();
122
+ if (changed && this.last) {
123
+ this.deps.onLog?.(`[acc-runner] account swap detected (${trigger}): ${this.last.synthetic_account} → ${identity.synthetic_account}`);
124
+ }
125
+ try {
126
+ await this.deps.emit({ identity, trigger, changed, seq, at });
127
+ }
128
+ catch (err) {
129
+ this.deps.onLog?.(`[acc-runner] account identity emit failed (${trigger}): ${err?.message ?? String(err)}`);
130
+ }
131
+ this.last = identity;
132
+ }
133
+ }
134
+ //# sourceMappingURL=account-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-probe.js","sourceRoot":"","sources":["../../src/engines/account-probe.ts"],"names":[],"mappings":"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"}