@sentropic/h2a-runtime 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/dist/account-pool.d.ts +168 -0
  2. package/dist/account-pool.d.ts.map +1 -0
  3. package/dist/account-pool.js +385 -0
  4. package/dist/account-pool.js.map +1 -0
  5. package/dist/agents-projection.d.ts +56 -0
  6. package/dist/agents-projection.d.ts.map +1 -0
  7. package/dist/agents-projection.js +82 -0
  8. package/dist/agents-projection.js.map +1 -0
  9. package/dist/attach.d.ts +75 -0
  10. package/dist/attach.d.ts.map +1 -0
  11. package/dist/attach.js +432 -0
  12. package/dist/attach.js.map +1 -0
  13. package/dist/auth-bundle.d.ts +21 -0
  14. package/dist/auth-bundle.d.ts.map +1 -0
  15. package/dist/auth-bundle.js +112 -0
  16. package/dist/auth-bundle.js.map +1 -0
  17. package/dist/auth-diagnostics.d.ts +18 -0
  18. package/dist/auth-diagnostics.d.ts.map +1 -0
  19. package/dist/auth-diagnostics.js +23 -0
  20. package/dist/auth-diagnostics.js.map +1 -0
  21. package/dist/auth-login.d.ts +9 -0
  22. package/dist/auth-login.d.ts.map +1 -0
  23. package/dist/auth-login.js +21 -0
  24. package/dist/auth-login.js.map +1 -0
  25. package/dist/auth-refresh.d.ts +32 -0
  26. package/dist/auth-refresh.d.ts.map +1 -0
  27. package/dist/auth-refresh.js +83 -0
  28. package/dist/auth-refresh.js.map +1 -0
  29. package/dist/auth-tools.d.ts +55 -0
  30. package/dist/auth-tools.d.ts.map +1 -0
  31. package/dist/auth-tools.js +163 -0
  32. package/dist/auth-tools.js.map +1 -0
  33. package/dist/browser-bridge-local.d.ts +27 -0
  34. package/dist/browser-bridge-local.d.ts.map +1 -0
  35. package/dist/browser-bridge-local.js +51 -0
  36. package/dist/browser-bridge-local.js.map +1 -0
  37. package/dist/browser.d.ts +53 -0
  38. package/dist/browser.d.ts.map +1 -0
  39. package/dist/browser.js +61 -0
  40. package/dist/browser.js.map +1 -0
  41. package/dist/conductor-launch.d.ts +179 -0
  42. package/dist/conductor-launch.d.ts.map +1 -0
  43. package/dist/conductor-launch.js +414 -0
  44. package/dist/conductor-launch.js.map +1 -0
  45. package/dist/config.d.ts +182 -0
  46. package/dist/config.d.ts.map +1 -0
  47. package/dist/config.js +334 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/conv-guard.d.ts +76 -0
  50. package/dist/conv-guard.d.ts.map +1 -0
  51. package/dist/conv-guard.js +131 -0
  52. package/dist/conv-guard.js.map +1 -0
  53. package/dist/convsync.d.ts +24 -0
  54. package/dist/convsync.d.ts.map +1 -0
  55. package/dist/convsync.js +108 -0
  56. package/dist/convsync.js.map +1 -0
  57. package/dist/cred-health.d.ts +96 -0
  58. package/dist/cred-health.d.ts.map +1 -0
  59. package/dist/cred-health.js +170 -0
  60. package/dist/cred-health.js.map +1 -0
  61. package/dist/delegate.d.ts +406 -0
  62. package/dist/delegate.d.ts.map +1 -0
  63. package/dist/delegate.js +739 -0
  64. package/dist/delegate.js.map +1 -0
  65. package/dist/enroll.d.ts +91 -0
  66. package/dist/enroll.d.ts.map +1 -0
  67. package/dist/enroll.js +248 -0
  68. package/dist/enroll.js.map +1 -0
  69. package/dist/fanout.d.ts +63 -0
  70. package/dist/fanout.d.ts.map +1 -0
  71. package/dist/fanout.js +115 -0
  72. package/dist/fanout.js.map +1 -0
  73. package/dist/forward.d.ts +66 -0
  74. package/dist/forward.d.ts.map +1 -0
  75. package/dist/forward.js +141 -0
  76. package/dist/forward.js.map +1 -0
  77. package/dist/gitdiff.d.ts +31 -0
  78. package/dist/gitdiff.d.ts.map +1 -0
  79. package/dist/gitdiff.js +133 -0
  80. package/dist/gitdiff.js.map +1 -0
  81. package/dist/h2a-bridge.d.ts +102 -0
  82. package/dist/h2a-bridge.d.ts.map +1 -0
  83. package/dist/h2a-bridge.js +333 -0
  84. package/dist/h2a-bridge.js.map +1 -0
  85. package/dist/h2a-jobs.d.ts +235 -0
  86. package/dist/h2a-jobs.d.ts.map +1 -0
  87. package/dist/h2a-jobs.js +388 -0
  88. package/dist/h2a-jobs.js.map +1 -0
  89. package/dist/h2a-ping.d.ts +38 -0
  90. package/dist/h2a-ping.d.ts.map +1 -0
  91. package/dist/h2a-ping.js +58 -0
  92. package/dist/h2a-ping.js.map +1 -0
  93. package/dist/index.d.ts +98 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +5887 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/interactive-throttle.d.ts +164 -0
  98. package/dist/interactive-throttle.d.ts.map +1 -0
  99. package/dist/interactive-throttle.js +194 -0
  100. package/dist/interactive-throttle.js.map +1 -0
  101. package/dist/lineage-client.d.ts +77 -0
  102. package/dist/lineage-client.d.ts.map +1 -0
  103. package/dist/lineage-client.js +117 -0
  104. package/dist/lineage-client.js.map +1 -0
  105. package/dist/lineage-lease.d.ts +152 -0
  106. package/dist/lineage-lease.d.ts.map +1 -0
  107. package/dist/lineage-lease.js +303 -0
  108. package/dist/lineage-lease.js.map +1 -0
  109. package/dist/llm-gateway-runtime/accounts.d.ts +48 -0
  110. package/dist/llm-gateway-runtime/accounts.d.ts.map +1 -0
  111. package/dist/llm-gateway-runtime/accounts.js +226 -0
  112. package/dist/llm-gateway-runtime/accounts.js.map +1 -0
  113. package/dist/llm-gateway-runtime/index.d.ts +4 -0
  114. package/dist/llm-gateway-runtime/index.d.ts.map +1 -0
  115. package/dist/llm-gateway-runtime/index.js +58 -0
  116. package/dist/llm-gateway-runtime/index.js.map +1 -0
  117. package/dist/llm-gateway-runtime/model-catalog.d.ts +36 -0
  118. package/dist/llm-gateway-runtime/model-catalog.d.ts.map +1 -0
  119. package/dist/llm-gateway-runtime/model-catalog.js +146 -0
  120. package/dist/llm-gateway-runtime/model-catalog.js.map +1 -0
  121. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts +3 -0
  122. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts.map +1 -0
  123. package/dist/llm-gateway-runtime/proxy-anthropic.js +139 -0
  124. package/dist/llm-gateway-runtime/proxy-anthropic.js.map +1 -0
  125. package/dist/llm-gateway-runtime/proxy-openai.d.ts +117 -0
  126. package/dist/llm-gateway-runtime/proxy-openai.d.ts.map +1 -0
  127. package/dist/llm-gateway-runtime/proxy-openai.js +1132 -0
  128. package/dist/llm-gateway-runtime/proxy-openai.js.map +1 -0
  129. package/dist/llm-gateway-runtime/session-ledger.d.ts +32 -0
  130. package/dist/llm-gateway-runtime/session-ledger.d.ts.map +1 -0
  131. package/dist/llm-gateway-runtime/session-ledger.js +62 -0
  132. package/dist/llm-gateway-runtime/session-ledger.js.map +1 -0
  133. package/dist/llm-gateway-runtime/sticky.d.ts +40 -0
  134. package/dist/llm-gateway-runtime/sticky.d.ts.map +1 -0
  135. package/dist/llm-gateway-runtime/sticky.js +254 -0
  136. package/dist/llm-gateway-runtime/sticky.js.map +1 -0
  137. package/dist/llm-mesh.d.ts +102 -0
  138. package/dist/llm-mesh.d.ts.map +1 -0
  139. package/dist/llm-mesh.js +433 -0
  140. package/dist/llm-mesh.js.map +1 -0
  141. package/dist/migrate-candidates.d.ts +35 -0
  142. package/dist/migrate-candidates.d.ts.map +1 -0
  143. package/dist/migrate-candidates.js +128 -0
  144. package/dist/migrate-candidates.js.map +1 -0
  145. package/dist/migrate.d.ts +125 -0
  146. package/dist/migrate.d.ts.map +1 -0
  147. package/dist/migrate.js +561 -0
  148. package/dist/migrate.js.map +1 -0
  149. package/dist/plugin-manifest.d.ts +143 -0
  150. package/dist/plugin-manifest.d.ts.map +1 -0
  151. package/dist/plugin-manifest.js +233 -0
  152. package/dist/plugin-manifest.js.map +1 -0
  153. package/dist/plugin.d.ts +274 -0
  154. package/dist/plugin.d.ts.map +1 -0
  155. package/dist/plugin.js +919 -0
  156. package/dist/plugin.js.map +1 -0
  157. package/dist/pod-liveness.d.ts +51 -0
  158. package/dist/pod-liveness.d.ts.map +1 -0
  159. package/dist/pod-liveness.js +80 -0
  160. package/dist/pod-liveness.js.map +1 -0
  161. package/dist/profile-menu.d.ts +7 -0
  162. package/dist/profile-menu.d.ts.map +1 -0
  163. package/dist/profile-menu.js +52 -0
  164. package/dist/profile-menu.js.map +1 -0
  165. package/dist/profiles.d.ts +23 -0
  166. package/dist/profiles.d.ts.map +1 -0
  167. package/dist/profiles.js +66 -0
  168. package/dist/profiles.js.map +1 -0
  169. package/dist/protocol-local.d.ts +27 -0
  170. package/dist/protocol-local.d.ts.map +1 -0
  171. package/dist/protocol-local.js +12 -0
  172. package/dist/protocol-local.js.map +1 -0
  173. package/dist/pty.d.ts +26 -0
  174. package/dist/pty.d.ts.map +1 -0
  175. package/dist/pty.js +45 -0
  176. package/dist/pty.js.map +1 -0
  177. package/dist/readiness.d.ts +38 -0
  178. package/dist/readiness.d.ts.map +1 -0
  179. package/dist/readiness.js +169 -0
  180. package/dist/readiness.js.map +1 -0
  181. package/dist/registry.d.ts +258 -0
  182. package/dist/registry.d.ts.map +1 -0
  183. package/dist/registry.js +570 -0
  184. package/dist/registry.js.map +1 -0
  185. package/dist/relaunch.d.ts +52 -0
  186. package/dist/relaunch.d.ts.map +1 -0
  187. package/dist/relaunch.js +76 -0
  188. package/dist/relaunch.js.map +1 -0
  189. package/dist/restore.d.ts +165 -0
  190. package/dist/restore.d.ts.map +1 -0
  191. package/dist/restore.js +530 -0
  192. package/dist/restore.js.map +1 -0
  193. package/dist/run.d.ts +29 -0
  194. package/dist/run.d.ts.map +1 -0
  195. package/dist/run.js +206 -0
  196. package/dist/run.js.map +1 -0
  197. package/dist/secrets.d.ts +25 -0
  198. package/dist/secrets.d.ts.map +1 -0
  199. package/dist/secrets.js +98 -0
  200. package/dist/secrets.js.map +1 -0
  201. package/dist/session-restore.d.ts +26 -0
  202. package/dist/session-restore.d.ts.map +1 -0
  203. package/dist/session-restore.js +122 -0
  204. package/dist/session-restore.js.map +1 -0
  205. package/dist/skills-sync.d.ts +126 -0
  206. package/dist/skills-sync.d.ts.map +1 -0
  207. package/dist/skills-sync.js +255 -0
  208. package/dist/skills-sync.js.map +1 -0
  209. package/dist/smoke.d.ts +23 -0
  210. package/dist/smoke.d.ts.map +1 -0
  211. package/dist/smoke.js +138 -0
  212. package/dist/smoke.js.map +1 -0
  213. package/dist/soft-refresh.d.ts +107 -0
  214. package/dist/soft-refresh.d.ts.map +1 -0
  215. package/dist/soft-refresh.js +426 -0
  216. package/dist/soft-refresh.js.map +1 -0
  217. package/dist/sync-status.d.ts +22 -0
  218. package/dist/sync-status.d.ts.map +1 -0
  219. package/dist/sync-status.js +41 -0
  220. package/dist/sync-status.js.map +1 -0
  221. package/dist/sync.d.ts +77 -0
  222. package/dist/sync.d.ts.map +1 -0
  223. package/dist/sync.js +302 -0
  224. package/dist/sync.js.map +1 -0
  225. package/dist/throttle-signatures.d.ts +43 -0
  226. package/dist/throttle-signatures.d.ts.map +1 -0
  227. package/dist/throttle-signatures.js +116 -0
  228. package/dist/throttle-signatures.js.map +1 -0
  229. package/dist/tmux.d.ts +286 -0
  230. package/dist/tmux.d.ts.map +1 -0
  231. package/dist/tmux.js +970 -0
  232. package/dist/tmux.js.map +1 -0
  233. package/dist/tunnel.d.ts +25 -0
  234. package/dist/tunnel.d.ts.map +1 -0
  235. package/dist/tunnel.js +127 -0
  236. package/dist/tunnel.js.map +1 -0
  237. package/dist/workspace-merge.d.ts +17 -0
  238. package/dist/workspace-merge.d.ts.map +1 -0
  239. package/dist/workspace-merge.js +153 -0
  240. package/dist/workspace-merge.js.map +1 -0
  241. package/dist/workspace-sync-incremental.d.ts +64 -0
  242. package/dist/workspace-sync-incremental.d.ts.map +1 -0
  243. package/dist/workspace-sync-incremental.js +199 -0
  244. package/dist/workspace-sync-incremental.js.map +1 -0
  245. package/dist/workspace-sync.d.ts +3 -0
  246. package/dist/workspace-sync.d.ts.map +1 -0
  247. package/dist/workspace-sync.js +187 -0
  248. package/dist/workspace-sync.js.map +1 -0
  249. package/dist/workspace.d.ts +70 -0
  250. package/dist/workspace.d.ts.map +1 -0
  251. package/dist/workspace.js +173 -0
  252. package/dist/workspace.js.map +1 -0
  253. package/package.json +44 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Rate-limit ("throttled") AUTO-RESUME for INTERACTIVE LOCAL tmux sessions
3
+ * (reliability slice 2 — throttle phase 2).
4
+ *
5
+ * Slice 1 handled HEADLESS delegated jobs (they exit ≠0 on a transient provider
6
+ * rate-limit; the conductor relaunches them with a continue flag). INTERACTIVE
7
+ * `remote run` sessions (claude / codex / agy in a live tmux pane) do NOT exit —
8
+ * they STALL: the provider returns `API Error: Server is temporarily limiting
9
+ * requests · Rate limited` and the agent sits there until a human pokes it. This
10
+ * module decides which stalled-and-throttled sessions to nudge back to life,
11
+ * staggered (AIMD cap, oldest-first, backoff), and — CRUCIALLY — NEVER touches a
12
+ * pane a human is attached to.
13
+ *
14
+ * SAFETY IS PARAMOUNT (we send keys to LIVE sessions a human may be driving):
15
+ * - HARD attached guard: a session whose tmux `#{session_attached}` is NOT 0 is
16
+ * classified `throttled` for the advisory but is NEVER in `toResume`. We only
17
+ * ever nudge a DETACHED pane.
18
+ * - STALL corroboration: a throttle SIGNATURE in the pane tail is necessary but
19
+ * not sufficient — the agent may have printed the error and then recovered on
20
+ * its own. We require the pane to ALSO be stalled (idle shell OR an unchanged
21
+ * tail since the previous pass) before nudging, so we never interrupt an agent
22
+ * that is actively working.
23
+ * - BACKOFF + attempt cap: each nudge schedules `nextRetryAt = now +
24
+ * jitteredBackoffMs(attempts)`; a session is only resumed once it is past
25
+ * `nextRetryAt`, and after `maxAttempts` nudges we give up (advise only).
26
+ * - STAGGERED: at most the AIMD effective cap of sessions per pass, oldest-first
27
+ * (the session whose throttle is oldest goes first), so a fleet-wide limit is
28
+ * not hammered by resuming everyone at once.
29
+ *
30
+ * This module is PURE (no tmux/clock/IO): the caller captures pane tails, reads
31
+ * `session_attached`, probes idleness, and carries the per-session throttle
32
+ * bookkeeping across passes. The thin executor (index.ts) feeds those in, applies
33
+ * the plan with a minimal `send-keys` / relaunch nudge to the detached pane, and
34
+ * is opt-in (default dry-run behind `--resume-throttled`).
35
+ */
36
+ import { type DelegateType } from "./delegate.js";
37
+ /**
38
+ * The minimal per-session shape the planner needs. Mirrors `LocalSession` from
39
+ * tmux.ts (name/slug/profile) plus the bits that make staggering deterministic.
40
+ */
41
+ export type InteractiveSession = {
42
+ /** Full tmux session name (e.g. `remote-surch`) — the resume target. */
43
+ name: string;
44
+ /** The delegate tool driving the pane (selects the detectThrottle signature table). */
45
+ type: DelegateType;
46
+ /**
47
+ * When this session was first seen (ISO or ms). Used ONLY to break ties when
48
+ * two sessions throttled at the same instant — the throttle's own `firstAt`
49
+ * (oldest-throttle-first) is the primary ordering.
50
+ */
51
+ startedAt: string | number;
52
+ };
53
+ /** Per-session throttle bookkeeping the executor carries across passes. */
54
+ export type InteractiveThrottleInfo = {
55
+ /** How many resume nudges this session has received (drives the backoff). */
56
+ attempts: number;
57
+ /** ISO ts of the FIRST throttle observed (oldest-first ordering + history). */
58
+ firstAt: string;
59
+ /** ISO ts the session may be nudged at (now + jitteredBackoffMs(attempts)). */
60
+ nextRetryAt: string;
61
+ /** The signature tag that classified the last throttle. */
62
+ lastSignature?: string;
63
+ };
64
+ /** A single session the planner decided to nudge this pass. */
65
+ export type InteractiveResume = {
66
+ name: string;
67
+ type: DelegateType;
68
+ /** The matched throttle signature (for the advisory + bookkeeping). */
69
+ signature?: string;
70
+ /** 0-based attempt index used for THIS nudge's backoff (prior attempts). */
71
+ attempt: number;
72
+ /** The next bookkeeping to persist after the nudge (attempts bumped, retry rescheduled). */
73
+ next: InteractiveThrottleInfo;
74
+ };
75
+ /** A session classified `throttled` this pass (resumed or merely advised). */
76
+ export type InteractiveThrottled = {
77
+ name: string;
78
+ type: DelegateType;
79
+ signature?: string;
80
+ /** True when the pane is ATTACHED (a human is there) → never nudged, advise only. */
81
+ attached: boolean;
82
+ /** True when the pane is corroborated stalled (idle / unchanged tail). */
83
+ stalled: boolean;
84
+ };
85
+ export type InteractiveResumePlan = {
86
+ /** Every session classified throttled this pass (attached or detached). */
87
+ throttled: InteractiveThrottled[];
88
+ /** The DETACHED, stalled, due, under-cap sessions to nudge (≤ cap, oldest-first). */
89
+ toResume: InteractiveResume[];
90
+ /** One human-readable line per decision (advisory / would-resume / skip reason). */
91
+ advisories: string[];
92
+ };
93
+ export type PlanInteractiveResumeOpts = {
94
+ /** The live interactive sessions to consider. */
95
+ sessions: ReadonlyArray<InteractiveSession>;
96
+ /** Now (ms epoch) — injected so the plan is deterministic. */
97
+ now: number;
98
+ /** Per-session prior throttle bookkeeping (by session name), carried across passes. */
99
+ throttleState: Readonly<Record<string, InteractiveThrottleInfo | undefined>>;
100
+ /**
101
+ * Per-session `session_attached` (by name): tmux `#{session_attached}` — 0 =
102
+ * detached, anything else = a client is attached. A MISSING entry is treated
103
+ * as ATTACHED (conservative: unknown → never touch).
104
+ */
105
+ attachedMap: Readonly<Record<string, number | undefined>>;
106
+ /** Per-session captured pane tail (by name) — scanned by detectThrottle. */
107
+ paneTails: Readonly<Record<string, string | undefined>>;
108
+ /**
109
+ * Per-session stall corroboration (by name): true when the pane is an idle
110
+ * shell OR its tail hash is unchanged since the previous pass. A MISSING entry
111
+ * is treated as NOT stalled (conservative: unknown → assume the agent is busy,
112
+ * never interrupt).
113
+ */
114
+ stalledMap: Readonly<Record<string, boolean | undefined>>;
115
+ /** AIMD effective cap — at most this many sessions are resumed per pass. */
116
+ cap: number;
117
+ /** Max resume nudges before we give up on a session (advise only). */
118
+ maxAttempts?: number;
119
+ /** Backoff base/cap (ms) + injectable jitter, forwarded to jitteredBackoffMs. */
120
+ backoffBaseMs?: number;
121
+ backoffCapMs?: number;
122
+ rand?: () => number;
123
+ };
124
+ /** Is `attached` value a DETACHED pane (session_attached === 0)? Strict. */
125
+ export declare function isDetached(attached: number | undefined): boolean;
126
+ /** Is a throttled session DUE for a nudge (now >= nextRetryAt)? Missing → due. */
127
+ export declare function isInteractiveResumeDue(prior: InteractiveThrottleInfo | undefined, nowMs: number): boolean;
128
+ /**
129
+ * Minutes-until-retry label for an advisory (e.g. "retry in 3m" / "retry now").
130
+ * Rounds UP so a sub-minute wait still reads as 1m. Pure, exported for tests.
131
+ */
132
+ export declare function interactiveRetryLabel(prior: InteractiveThrottleInfo | undefined, nowMs: number): string;
133
+ /**
134
+ * THE pure planner. Classify each session as throttled (a provider rate-limit
135
+ * signature in its pane tail) and decide which DETACHED, stalled, due,
136
+ * under-attempt-cap sessions to nudge this pass — at most `cap`, oldest-throttle
137
+ * first. Returns the full throttled set (for advisories), the nudge plan, and one
138
+ * advisory line per decision. NO IO — the caller supplies tails / attached /
139
+ * stall / clock and persists the returned `next` bookkeeping.
140
+ *
141
+ * Decision order per session:
142
+ * 1. detectThrottle(tail, type) — no signature ⇒ not throttled, ignored.
143
+ * 2. ATTACHED (session_attached !== 0, or unknown) ⇒ advise only, NEVER nudge.
144
+ * 3. NOT stalled (agent still working / unknown) ⇒ advise only, NEVER nudge.
145
+ * 4. NOT due (within backoff window) ⇒ advise "retry in Xm", skip.
146
+ * 5. attempts >= maxAttempts ⇒ advise "gave up", skip.
147
+ * 6. else CANDIDATE — sorted oldest-throttle-first, take up to `cap`.
148
+ */
149
+ export declare function planInteractiveResume(opts: PlanInteractiveResumeOpts): InteractiveResumePlan;
150
+ /**
151
+ * The MINIMAL "continue" nudge to wake a stalled agent CLI in its pane, by type.
152
+ * Pure, exported for tests. The argv is sent via tmux `send-keys` as a SINGLE
153
+ * literal token followed by Enter — never a shell string, never multiple keys
154
+ * the agent could misread. The nudge is the most benign "carry on" each CLI
155
+ * understands when sitting at its prompt after a transient error:
156
+ * - claude / agy: the word `continue` (a plain message at the prompt).
157
+ * - codex: `continue` likewise.
158
+ *
159
+ * All three take the SAME literal because, for an INTERACTIVE pane sitting idle
160
+ * after a rate-limit, the safe universal action is to type a short "continue"
161
+ * message and submit it — anything fancier risks being interpreted as a command.
162
+ */
163
+ export declare function interactiveResumeNudge(_type: DelegateType): string;
164
+ //# sourceMappingURL=interactive-throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-throttle.d.ts","sourceRoot":"","sources":["../src/interactive-throttle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Rate-limit ("throttled") AUTO-RESUME for INTERACTIVE LOCAL tmux sessions
3
+ * (reliability slice 2 — throttle phase 2).
4
+ *
5
+ * Slice 1 handled HEADLESS delegated jobs (they exit ≠0 on a transient provider
6
+ * rate-limit; the conductor relaunches them with a continue flag). INTERACTIVE
7
+ * `remote run` sessions (claude / codex / agy in a live tmux pane) do NOT exit —
8
+ * they STALL: the provider returns `API Error: Server is temporarily limiting
9
+ * requests · Rate limited` and the agent sits there until a human pokes it. This
10
+ * module decides which stalled-and-throttled sessions to nudge back to life,
11
+ * staggered (AIMD cap, oldest-first, backoff), and — CRUCIALLY — NEVER touches a
12
+ * pane a human is attached to.
13
+ *
14
+ * SAFETY IS PARAMOUNT (we send keys to LIVE sessions a human may be driving):
15
+ * - HARD attached guard: a session whose tmux `#{session_attached}` is NOT 0 is
16
+ * classified `throttled` for the advisory but is NEVER in `toResume`. We only
17
+ * ever nudge a DETACHED pane.
18
+ * - STALL corroboration: a throttle SIGNATURE in the pane tail is necessary but
19
+ * not sufficient — the agent may have printed the error and then recovered on
20
+ * its own. We require the pane to ALSO be stalled (idle shell OR an unchanged
21
+ * tail since the previous pass) before nudging, so we never interrupt an agent
22
+ * that is actively working.
23
+ * - BACKOFF + attempt cap: each nudge schedules `nextRetryAt = now +
24
+ * jitteredBackoffMs(attempts)`; a session is only resumed once it is past
25
+ * `nextRetryAt`, and after `maxAttempts` nudges we give up (advise only).
26
+ * - STAGGERED: at most the AIMD effective cap of sessions per pass, oldest-first
27
+ * (the session whose throttle is oldest goes first), so a fleet-wide limit is
28
+ * not hammered by resuming everyone at once.
29
+ *
30
+ * This module is PURE (no tmux/clock/IO): the caller captures pane tails, reads
31
+ * `session_attached`, probes idleness, and carries the per-session throttle
32
+ * bookkeeping across passes. The thin executor (index.ts) feeds those in, applies
33
+ * the plan with a minimal `send-keys` / relaunch nudge to the detached pane, and
34
+ * is opt-in (default dry-run behind `--resume-throttled`).
35
+ */
36
+ import { detectThrottle } from "./throttle-signatures.js";
37
+ import { jitteredBackoffMs, THROTTLE_BACKOFF_BASE_MS, THROTTLE_BACKOFF_CAP_MS, THROTTLE_MAX_ATTEMPTS, } from "./delegate.js";
38
+ /** Is `attached` value a DETACHED pane (session_attached === 0)? Strict. */
39
+ export function isDetached(attached) {
40
+ return attached === 0;
41
+ }
42
+ /** ms epoch from an ISO string or a number; NaN-safe (undefined → +Inf, sorts last). */
43
+ function toMs(v) {
44
+ if (v === undefined)
45
+ return Number.POSITIVE_INFINITY;
46
+ const ms = typeof v === "number" ? v : Date.parse(v);
47
+ return Number.isFinite(ms) ? ms : Number.POSITIVE_INFINITY;
48
+ }
49
+ /** Is a throttled session DUE for a nudge (now >= nextRetryAt)? Missing → due. */
50
+ export function isInteractiveResumeDue(prior, nowMs) {
51
+ const at = prior?.nextRetryAt;
52
+ if (at === undefined)
53
+ return true;
54
+ const ms = Date.parse(at);
55
+ if (!Number.isFinite(ms))
56
+ return true;
57
+ return nowMs >= ms;
58
+ }
59
+ /**
60
+ * Minutes-until-retry label for an advisory (e.g. "retry in 3m" / "retry now").
61
+ * Rounds UP so a sub-minute wait still reads as 1m. Pure, exported for tests.
62
+ */
63
+ export function interactiveRetryLabel(prior, nowMs) {
64
+ const at = prior?.nextRetryAt;
65
+ if (at === undefined)
66
+ return "retry now";
67
+ const ms = Date.parse(at);
68
+ if (!Number.isFinite(ms) || ms <= nowMs)
69
+ return "retry now";
70
+ return `retry in ${Math.ceil((ms - nowMs) / 60_000)}m`;
71
+ }
72
+ /**
73
+ * THE pure planner. Classify each session as throttled (a provider rate-limit
74
+ * signature in its pane tail) and decide which DETACHED, stalled, due,
75
+ * under-attempt-cap sessions to nudge this pass — at most `cap`, oldest-throttle
76
+ * first. Returns the full throttled set (for advisories), the nudge plan, and one
77
+ * advisory line per decision. NO IO — the caller supplies tails / attached /
78
+ * stall / clock and persists the returned `next` bookkeeping.
79
+ *
80
+ * Decision order per session:
81
+ * 1. detectThrottle(tail, type) — no signature ⇒ not throttled, ignored.
82
+ * 2. ATTACHED (session_attached !== 0, or unknown) ⇒ advise only, NEVER nudge.
83
+ * 3. NOT stalled (agent still working / unknown) ⇒ advise only, NEVER nudge.
84
+ * 4. NOT due (within backoff window) ⇒ advise "retry in Xm", skip.
85
+ * 5. attempts >= maxAttempts ⇒ advise "gave up", skip.
86
+ * 6. else CANDIDATE — sorted oldest-throttle-first, take up to `cap`.
87
+ */
88
+ export function planInteractiveResume(opts) {
89
+ const { sessions, now, throttleState, attachedMap, paneTails, stalledMap, } = opts;
90
+ const maxAttempts = opts.maxAttempts ?? THROTTLE_MAX_ATTEMPTS;
91
+ const baseMs = opts.backoffBaseMs ?? THROTTLE_BACKOFF_BASE_MS;
92
+ const capMs = opts.backoffCapMs ?? THROTTLE_BACKOFF_CAP_MS;
93
+ const rand = opts.rand ?? Math.random;
94
+ const cap = opts.cap;
95
+ const throttled = [];
96
+ const advisories = [];
97
+ // Candidates that PASSED every guard (detached + stalled + due + under cap):
98
+ // we collect them with their ordering key, then sort + take up to `cap`.
99
+ const candidates = [];
100
+ for (const s of sessions) {
101
+ const tail = paneTails[s.name] ?? "";
102
+ const verdict = detectThrottle(tail, s.type);
103
+ if (!verdict.throttled)
104
+ continue;
105
+ const attached = !isDetached(attachedMap[s.name]);
106
+ const stalled = stalledMap[s.name] === true;
107
+ throttled.push({
108
+ name: s.name,
109
+ type: s.type,
110
+ ...(verdict.signature !== undefined ? { signature: verdict.signature } : {}),
111
+ attached,
112
+ stalled,
113
+ });
114
+ const sig = verdict.signature ?? "rate-limited";
115
+ if (attached) {
116
+ // HARD guard — a human is on this pane. Detect + advise, NEVER touch.
117
+ advisories.push(`[remote] ${s.name} (${s.type}) throttled (${sig}) — ATTACHED, not touching it (resume it yourself, or detach to let auto-resume nudge it)`);
118
+ continue;
119
+ }
120
+ if (!stalled) {
121
+ // Signature present but the pane is still moving (or stall unknown) — the
122
+ // agent likely recovered on its own. Advise only; do NOT interrupt.
123
+ advisories.push(`[remote] ${s.name} (${s.type}) saw a rate-limit (${sig}) but is still active — leaving it alone`);
124
+ continue;
125
+ }
126
+ const prior = throttleState[s.name];
127
+ if (!isInteractiveResumeDue(prior, now)) {
128
+ advisories.push(`[remote] ${s.name} (${s.type}) throttled (${sig}) — backing off, ${interactiveRetryLabel(prior, now)}`);
129
+ continue;
130
+ }
131
+ const priorAttempts = prior?.attempts ?? 0;
132
+ if (priorAttempts >= maxAttempts) {
133
+ advisories.push(`[remote] ${s.name} (${s.type}) throttled (${sig}) — gave up after ${priorAttempts} resume attempt(s); resume it manually`);
134
+ continue;
135
+ }
136
+ candidates.push({
137
+ session: s,
138
+ ...(verdict.signature !== undefined ? { signature: verdict.signature } : {}),
139
+ prior,
140
+ // Oldest-throttle first: a session WITH prior bookkeeping orders by its
141
+ // firstAt; a never-throttled-before session uses `now` (just entered).
142
+ orderKey: prior ? toMs(prior.firstAt) : now,
143
+ tieKey: toMs(s.startedAt),
144
+ });
145
+ }
146
+ // Stagger: oldest throttle first, then oldest session, then name (stable).
147
+ candidates.sort((a, b) => a.orderKey - b.orderKey ||
148
+ a.tieKey - b.tieKey ||
149
+ a.session.name.localeCompare(b.session.name));
150
+ const toResume = [];
151
+ const limit = cap > 0 ? cap : 0;
152
+ for (const c of candidates) {
153
+ if (toResume.length >= limit) {
154
+ advisories.push(`[remote] ${c.session.name} (${c.session.type}) throttled — deferred (AIMD cap ${limit} reached this pass)`);
155
+ continue;
156
+ }
157
+ const priorAttempts = c.prior?.attempts ?? 0;
158
+ const nowIso = new Date(now).toISOString();
159
+ // Backoff for the NEXT retry uses the post-nudge attempt count.
160
+ const delay = jitteredBackoffMs(priorAttempts + 1, baseMs, capMs, rand);
161
+ const next = {
162
+ attempts: priorAttempts + 1,
163
+ firstAt: c.prior?.firstAt ?? nowIso,
164
+ nextRetryAt: new Date(now + delay).toISOString(),
165
+ ...(c.signature !== undefined ? { lastSignature: c.signature } : {}),
166
+ };
167
+ toResume.push({
168
+ name: c.session.name,
169
+ type: c.session.type,
170
+ ...(c.signature !== undefined ? { signature: c.signature } : {}),
171
+ attempt: priorAttempts,
172
+ next,
173
+ });
174
+ advisories.push(`[remote] ${c.session.name} (${c.session.type}) throttled (${c.signature ?? "rate-limited"}) — resuming (detached, attempt ${priorAttempts + 1}/${maxAttempts})`);
175
+ }
176
+ return { throttled, toResume, advisories };
177
+ }
178
+ /**
179
+ * The MINIMAL "continue" nudge to wake a stalled agent CLI in its pane, by type.
180
+ * Pure, exported for tests. The argv is sent via tmux `send-keys` as a SINGLE
181
+ * literal token followed by Enter — never a shell string, never multiple keys
182
+ * the agent could misread. The nudge is the most benign "carry on" each CLI
183
+ * understands when sitting at its prompt after a transient error:
184
+ * - claude / agy: the word `continue` (a plain message at the prompt).
185
+ * - codex: `continue` likewise.
186
+ *
187
+ * All three take the SAME literal because, for an INTERACTIVE pane sitting idle
188
+ * after a rate-limit, the safe universal action is to type a short "continue"
189
+ * message and submit it — anything fancier risks being interpreted as a command.
190
+ */
191
+ export function interactiveResumeNudge(_type) {
192
+ return "continue";
193
+ }
194
+ //# sourceMappingURL=interactive-throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-throttle.js","sourceRoot":"","sources":["../src/interactive-throttle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * lineage-client.ts — Phase A0c
3
+ *
4
+ * Client-side helpers for the lineage-lease system:
5
+ * - leaseHeaders(): attaches fencing token to mutating HTTP requests
6
+ * - HTTP client functions for acquire/renew/handoff/release/read via the CP
7
+ */
8
+ export type LeaseLocation = "local" | "remote";
9
+ export type LineageLease = {
10
+ lineageId: string;
11
+ epoch: number;
12
+ holder: string;
13
+ incarnationId: string;
14
+ location: LeaseLocation;
15
+ expiresAt: string;
16
+ };
17
+ /**
18
+ * Returns headers to include in requests that mutate workspace state.
19
+ * Pass undefined to get empty headers (no lineage active).
20
+ */
21
+ export declare function leaseHeaders(lease: {
22
+ lineageId: string;
23
+ epoch: number;
24
+ } | undefined): Record<string, string>;
25
+ /**
26
+ * Acquire (or take over an expired) lineage lease.
27
+ * Returns the lease on success, or { error: "conflict", current } on 409.
28
+ */
29
+ export declare function acquireLineageLease(baseUrl: string, body: {
30
+ lineageId: string;
31
+ holder: string;
32
+ incarnationId: string;
33
+ location: LeaseLocation;
34
+ ttlMs?: number;
35
+ }, fetchImpl?: typeof fetch): Promise<LineageLease | {
36
+ error: "conflict";
37
+ current: LineageLease;
38
+ }>;
39
+ /**
40
+ * Renew a held lease (push the expiry forward).
41
+ * Returns the updated lease, or { error: "stale_epoch" } on 409.
42
+ */
43
+ export declare function renewLineageLease(baseUrl: string, lineageId: string, body: {
44
+ holder: string;
45
+ expectedEpoch: number;
46
+ ttlMs?: number;
47
+ }, fetchImpl?: typeof fetch): Promise<LineageLease | {
48
+ error: "stale_epoch";
49
+ }>;
50
+ /**
51
+ * Hand off a lease to a new holder (increments epoch — fencing token advances).
52
+ * Returns the updated lease, or { error: "stale_epoch" } on 409.
53
+ */
54
+ export declare function handoffLineageLease(baseUrl: string, lineageId: string, body: {
55
+ fromHolder: string;
56
+ expectedEpoch: number;
57
+ toHolder: string;
58
+ toIncarnationId: string;
59
+ toLocation: LeaseLocation;
60
+ ttlMs?: number;
61
+ }, fetchImpl?: typeof fetch): Promise<LineageLease | {
62
+ error: "stale_epoch";
63
+ }>;
64
+ /**
65
+ * Release (delete) a held lease.
66
+ * Idempotent — a second call after release does not throw.
67
+ */
68
+ export declare function releaseLineageLease(baseUrl: string, lineageId: string, body: {
69
+ holder: string;
70
+ expectedEpoch: number;
71
+ }, fetchImpl?: typeof fetch): Promise<void>;
72
+ /**
73
+ * Read the current lease without mutating it.
74
+ * Returns null when no lease exists (404).
75
+ */
76
+ export declare function readLineageLease(baseUrl: string, lineageId: string, fetchImpl?: typeof fetch): Promise<LineageLease | null>;
77
+ //# sourceMappingURL=lineage-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineage-client.d.ts","sourceRoot":"","sources":["../src/lineage-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,aAAa,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACtD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAMD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,EACD,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,YAAY,GAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CActE;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/D,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,YAAY,GAAG;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC,CAiBlD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IACJ,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,aAAa,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,EACD,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,YAAY,GAAG;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC,CAoBlD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAC/C,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAa9B"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * lineage-client.ts — Phase A0c
3
+ *
4
+ * Client-side helpers for the lineage-lease system:
5
+ * - leaseHeaders(): attaches fencing token to mutating HTTP requests
6
+ * - HTTP client functions for acquire/renew/handoff/release/read via the CP
7
+ */
8
+ import { authHeaders } from "./config.js";
9
+ function joinUrl(base, path) {
10
+ return `${base.replace(/\/$/, "")}${path}`;
11
+ }
12
+ // ---------------------------------------------------------------------------
13
+ // Header helper
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Returns headers to include in requests that mutate workspace state.
17
+ * Pass undefined to get empty headers (no lineage active).
18
+ */
19
+ export function leaseHeaders(lease) {
20
+ if (!lease)
21
+ return {};
22
+ return {
23
+ "X-Lineage-Id": lease.lineageId,
24
+ "X-Lineage-Epoch": String(lease.epoch),
25
+ };
26
+ }
27
+ // ---------------------------------------------------------------------------
28
+ // HTTP client — calls the CP /lineage-leases routes
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Acquire (or take over an expired) lineage lease.
32
+ * Returns the lease on success, or { error: "conflict", current } on 409.
33
+ */
34
+ export async function acquireLineageLease(baseUrl, body, fetchImpl = fetch) {
35
+ const res = await fetchImpl(joinUrl(baseUrl, "/lineage-leases/acquire"), {
36
+ method: "POST",
37
+ headers: { "content-type": "application/json", ...authHeaders() },
38
+ body: JSON.stringify(body),
39
+ });
40
+ const json = (await res.json());
41
+ if (res.status === 409) {
42
+ return { error: "conflict", current: json["current"] };
43
+ }
44
+ if (!res.ok) {
45
+ throw new Error(`acquireLineageLease: ${res.status} ${res.statusText}`);
46
+ }
47
+ return json;
48
+ }
49
+ /**
50
+ * Renew a held lease (push the expiry forward).
51
+ * Returns the updated lease, or { error: "stale_epoch" } on 409.
52
+ */
53
+ export async function renewLineageLease(baseUrl, lineageId, body, fetchImpl = fetch) {
54
+ const res = await fetchImpl(joinUrl(baseUrl, `/lineage-leases/${encodeURIComponent(lineageId)}/renew`), {
55
+ method: "POST",
56
+ headers: { "content-type": "application/json", ...authHeaders() },
57
+ body: JSON.stringify(body),
58
+ });
59
+ const json = (await res.json());
60
+ if (res.status === 409) {
61
+ return { error: json["error"] };
62
+ }
63
+ if (!res.ok) {
64
+ throw new Error(`renewLineageLease: ${res.status} ${res.statusText}`);
65
+ }
66
+ return json;
67
+ }
68
+ /**
69
+ * Hand off a lease to a new holder (increments epoch — fencing token advances).
70
+ * Returns the updated lease, or { error: "stale_epoch" } on 409.
71
+ */
72
+ export async function handoffLineageLease(baseUrl, lineageId, body, fetchImpl = fetch) {
73
+ const res = await fetchImpl(joinUrl(baseUrl, `/lineage-leases/${encodeURIComponent(lineageId)}/handoff`), {
74
+ method: "POST",
75
+ headers: { "content-type": "application/json", ...authHeaders() },
76
+ body: JSON.stringify(body),
77
+ });
78
+ const json = (await res.json());
79
+ if (res.status === 409) {
80
+ return { error: json["error"] };
81
+ }
82
+ if (!res.ok) {
83
+ throw new Error(`handoffLineageLease: ${res.status} ${res.statusText}`);
84
+ }
85
+ return json;
86
+ }
87
+ /**
88
+ * Release (delete) a held lease.
89
+ * Idempotent — a second call after release does not throw.
90
+ */
91
+ export async function releaseLineageLease(baseUrl, lineageId, body, fetchImpl = fetch) {
92
+ const res = await fetchImpl(joinUrl(baseUrl, `/lineage-leases/${encodeURIComponent(lineageId)}`), {
93
+ method: "DELETE",
94
+ headers: { "content-type": "application/json", ...authHeaders() },
95
+ body: JSON.stringify(body),
96
+ });
97
+ if (!res.ok && res.status !== 409) {
98
+ throw new Error(`releaseLineageLease: ${res.status} ${res.statusText}`);
99
+ }
100
+ }
101
+ /**
102
+ * Read the current lease without mutating it.
103
+ * Returns null when no lease exists (404).
104
+ */
105
+ export async function readLineageLease(baseUrl, lineageId, fetchImpl = fetch) {
106
+ const res = await fetchImpl(joinUrl(baseUrl, `/lineage-leases/${encodeURIComponent(lineageId)}`), {
107
+ method: "GET",
108
+ headers: { ...authHeaders() },
109
+ });
110
+ if (res.status === 404)
111
+ return null;
112
+ if (!res.ok) {
113
+ throw new Error(`readLineageLease: ${res.status} ${res.statusText}`);
114
+ }
115
+ return (await res.json());
116
+ }
117
+ //# sourceMappingURL=lineage-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineage-client.js","sourceRoot":"","sources":["../src/lineage-client.ts"],"names":[],"mappings":"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"}