@komaa/standin-sdk 0.1.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/ambient.d.ts +80 -0
  4. package/dist/ambient.d.ts.map +1 -0
  5. package/dist/ambient.js +281 -0
  6. package/dist/ambient.js.map +1 -0
  7. package/dist/attachments.d.ts +149 -0
  8. package/dist/attachments.d.ts.map +1 -0
  9. package/dist/attachments.js +451 -0
  10. package/dist/attachments.js.map +1 -0
  11. package/dist/audio.d.ts +80 -0
  12. package/dist/audio.d.ts.map +1 -0
  13. package/dist/audio.js +170 -0
  14. package/dist/audio.js.map +1 -0
  15. package/dist/avatar.d.ts +111 -0
  16. package/dist/avatar.d.ts.map +1 -0
  17. package/dist/avatar.js +201 -0
  18. package/dist/avatar.js.map +1 -0
  19. package/dist/callServer.d.ts +151 -0
  20. package/dist/callServer.d.ts.map +1 -0
  21. package/dist/callServer.js +1097 -0
  22. package/dist/callServer.js.map +1 -0
  23. package/dist/callTools.d.ts +154 -0
  24. package/dist/callTools.d.ts.map +1 -0
  25. package/dist/callTools.js +255 -0
  26. package/dist/callTools.js.map +1 -0
  27. package/dist/chat.d.ts +226 -0
  28. package/dist/chat.d.ts.map +1 -0
  29. package/dist/chat.js +574 -0
  30. package/dist/chat.js.map +1 -0
  31. package/dist/config.d.ts +32 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +86 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/consult.d.ts +147 -0
  36. package/dist/consult.d.ts.map +1 -0
  37. package/dist/consult.js +388 -0
  38. package/dist/consult.js.map +1 -0
  39. package/dist/delivery.d.ts +81 -0
  40. package/dist/delivery.d.ts.map +1 -0
  41. package/dist/delivery.js +187 -0
  42. package/dist/delivery.js.map +1 -0
  43. package/dist/echoGuard.d.ts +56 -0
  44. package/dist/echoGuard.d.ts.map +1 -0
  45. package/dist/echoGuard.js +57 -0
  46. package/dist/echoGuard.js.map +1 -0
  47. package/dist/errors.d.ts +5 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +10 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/fetch.d.ts +40 -0
  52. package/dist/fetch.d.ts.map +1 -0
  53. package/dist/fetch.js +240 -0
  54. package/dist/fetch.js.map +1 -0
  55. package/dist/gate.d.ts +80 -0
  56. package/dist/gate.d.ts.map +1 -0
  57. package/dist/gate.js +249 -0
  58. package/dist/gate.js.map +1 -0
  59. package/dist/handler.d.ts +283 -0
  60. package/dist/handler.d.ts.map +1 -0
  61. package/dist/handler.js +4 -0
  62. package/dist/handler.js.map +1 -0
  63. package/dist/hmac.d.ts +32 -0
  64. package/dist/hmac.d.ts.map +1 -0
  65. package/dist/hmac.js +108 -0
  66. package/dist/hmac.js.map +1 -0
  67. package/dist/index.d.ts +59 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +60 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/lane.d.ts +92 -0
  72. package/dist/lane.d.ts.map +1 -0
  73. package/dist/lane.js +269 -0
  74. package/dist/lane.js.map +1 -0
  75. package/dist/lipsync.d.ts +136 -0
  76. package/dist/lipsync.d.ts.map +1 -0
  77. package/dist/lipsync.js +310 -0
  78. package/dist/lipsync.js.map +1 -0
  79. package/dist/log.d.ts +18 -0
  80. package/dist/log.d.ts.map +1 -0
  81. package/dist/log.js +20 -0
  82. package/dist/log.js.map +1 -0
  83. package/dist/media.d.ts +43 -0
  84. package/dist/media.d.ts.map +1 -0
  85. package/dist/media.js +190 -0
  86. package/dist/media.js.map +1 -0
  87. package/dist/minutes.d.ts +398 -0
  88. package/dist/minutes.d.ts.map +1 -0
  89. package/dist/minutes.js +805 -0
  90. package/dist/minutes.js.map +1 -0
  91. package/dist/outbound.d.ts +388 -0
  92. package/dist/outbound.d.ts.map +1 -0
  93. package/dist/outbound.js +933 -0
  94. package/dist/outbound.js.map +1 -0
  95. package/dist/plugins/cartesia/agent.d.ts +58 -0
  96. package/dist/plugins/cartesia/agent.d.ts.map +1 -0
  97. package/dist/plugins/cartesia/agent.js +254 -0
  98. package/dist/plugins/cartesia/agent.js.map +1 -0
  99. package/dist/plugins/cartesia/cli.d.ts +3 -0
  100. package/dist/plugins/cartesia/cli.d.ts.map +1 -0
  101. package/dist/plugins/cartesia/cli.js +10 -0
  102. package/dist/plugins/cartesia/cli.js.map +1 -0
  103. package/dist/plugins/cartesia/config.d.ts +30 -0
  104. package/dist/plugins/cartesia/config.d.ts.map +1 -0
  105. package/dist/plugins/cartesia/config.js +29 -0
  106. package/dist/plugins/cartesia/config.js.map +1 -0
  107. package/dist/plugins/cartesia/handler.d.ts +40 -0
  108. package/dist/plugins/cartesia/handler.d.ts.map +1 -0
  109. package/dist/plugins/cartesia/handler.js +142 -0
  110. package/dist/plugins/cartesia/handler.js.map +1 -0
  111. package/dist/plugins/cartesia/index.d.ts +6 -0
  112. package/dist/plugins/cartesia/index.d.ts.map +1 -0
  113. package/dist/plugins/cartesia/index.js +54 -0
  114. package/dist/plugins/cartesia/index.js.map +1 -0
  115. package/dist/plugins/deepgram/agent.d.ts +60 -0
  116. package/dist/plugins/deepgram/agent.d.ts.map +1 -0
  117. package/dist/plugins/deepgram/agent.js +302 -0
  118. package/dist/plugins/deepgram/agent.js.map +1 -0
  119. package/dist/plugins/deepgram/cli.d.ts +3 -0
  120. package/dist/plugins/deepgram/cli.d.ts.map +1 -0
  121. package/dist/plugins/deepgram/cli.js +10 -0
  122. package/dist/plugins/deepgram/cli.js.map +1 -0
  123. package/dist/plugins/deepgram/config.d.ts +39 -0
  124. package/dist/plugins/deepgram/config.d.ts.map +1 -0
  125. package/dist/plugins/deepgram/config.js +66 -0
  126. package/dist/plugins/deepgram/config.js.map +1 -0
  127. package/dist/plugins/deepgram/handler.d.ts +87 -0
  128. package/dist/plugins/deepgram/handler.d.ts.map +1 -0
  129. package/dist/plugins/deepgram/handler.js +268 -0
  130. package/dist/plugins/deepgram/handler.js.map +1 -0
  131. package/dist/plugins/deepgram/index.d.ts +11 -0
  132. package/dist/plugins/deepgram/index.d.ts.map +1 -0
  133. package/dist/plugins/deepgram/index.js +76 -0
  134. package/dist/plugins/deepgram/index.js.map +1 -0
  135. package/dist/plugins/echo/cli.d.ts +3 -0
  136. package/dist/plugins/echo/cli.d.ts.map +1 -0
  137. package/dist/plugins/echo/cli.js +10 -0
  138. package/dist/plugins/echo/cli.js.map +1 -0
  139. package/dist/plugins/echo/index.d.ts +37 -0
  140. package/dist/plugins/echo/index.d.ts.map +1 -0
  141. package/dist/plugins/echo/index.js +61 -0
  142. package/dist/plugins/echo/index.js.map +1 -0
  143. package/dist/plugins/elevenlabs/agent.d.ts +72 -0
  144. package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
  145. package/dist/plugins/elevenlabs/agent.js +305 -0
  146. package/dist/plugins/elevenlabs/agent.js.map +1 -0
  147. package/dist/plugins/elevenlabs/cli.d.ts +3 -0
  148. package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
  149. package/dist/plugins/elevenlabs/cli.js +10 -0
  150. package/dist/plugins/elevenlabs/cli.js.map +1 -0
  151. package/dist/plugins/elevenlabs/config.d.ts +27 -0
  152. package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
  153. package/dist/plugins/elevenlabs/config.js +42 -0
  154. package/dist/plugins/elevenlabs/config.js.map +1 -0
  155. package/dist/plugins/elevenlabs/handler.d.ts +67 -0
  156. package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
  157. package/dist/plugins/elevenlabs/handler.js +419 -0
  158. package/dist/plugins/elevenlabs/handler.js.map +1 -0
  159. package/dist/plugins/elevenlabs/index.d.ts +11 -0
  160. package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
  161. package/dist/plugins/elevenlabs/index.js +72 -0
  162. package/dist/plugins/elevenlabs/index.js.map +1 -0
  163. package/dist/plugins/livekit/cli.d.ts +3 -0
  164. package/dist/plugins/livekit/cli.d.ts.map +1 -0
  165. package/dist/plugins/livekit/cli.js +10 -0
  166. package/dist/plugins/livekit/cli.js.map +1 -0
  167. package/dist/plugins/livekit/config.d.ts +47 -0
  168. package/dist/plugins/livekit/config.d.ts.map +1 -0
  169. package/dist/plugins/livekit/config.js +46 -0
  170. package/dist/plugins/livekit/config.js.map +1 -0
  171. package/dist/plugins/livekit/handler.d.ts +59 -0
  172. package/dist/plugins/livekit/handler.d.ts.map +1 -0
  173. package/dist/plugins/livekit/handler.js +211 -0
  174. package/dist/plugins/livekit/handler.js.map +1 -0
  175. package/dist/plugins/livekit/index.d.ts +6 -0
  176. package/dist/plugins/livekit/index.d.ts.map +1 -0
  177. package/dist/plugins/livekit/index.js +68 -0
  178. package/dist/plugins/livekit/index.js.map +1 -0
  179. package/dist/plugins/livekit/room.d.ts +117 -0
  180. package/dist/plugins/livekit/room.d.ts.map +1 -0
  181. package/dist/plugins/livekit/room.js +449 -0
  182. package/dist/plugins/livekit/room.js.map +1 -0
  183. package/dist/plugins/openai/agent.d.ts +74 -0
  184. package/dist/plugins/openai/agent.d.ts.map +1 -0
  185. package/dist/plugins/openai/agent.js +264 -0
  186. package/dist/plugins/openai/agent.js.map +1 -0
  187. package/dist/plugins/openai/cli.d.ts +3 -0
  188. package/dist/plugins/openai/cli.d.ts.map +1 -0
  189. package/dist/plugins/openai/cli.js +10 -0
  190. package/dist/plugins/openai/cli.js.map +1 -0
  191. package/dist/plugins/openai/config.d.ts +29 -0
  192. package/dist/plugins/openai/config.d.ts.map +1 -0
  193. package/dist/plugins/openai/config.js +37 -0
  194. package/dist/plugins/openai/config.js.map +1 -0
  195. package/dist/plugins/openai/handler.d.ts +65 -0
  196. package/dist/plugins/openai/handler.d.ts.map +1 -0
  197. package/dist/plugins/openai/handler.js +297 -0
  198. package/dist/plugins/openai/handler.js.map +1 -0
  199. package/dist/plugins/openai/index.d.ts +6 -0
  200. package/dist/plugins/openai/index.d.ts.map +1 -0
  201. package/dist/plugins/openai/index.js +72 -0
  202. package/dist/plugins/openai/index.js.map +1 -0
  203. package/dist/plugins/openclaw/allowlist.d.ts +9 -0
  204. package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
  205. package/dist/plugins/openclaw/allowlist.js +11 -0
  206. package/dist/plugins/openclaw/allowlist.js.map +1 -0
  207. package/dist/plugins/openclaw/config.d.ts +50 -0
  208. package/dist/plugins/openclaw/config.d.ts.map +1 -0
  209. package/dist/plugins/openclaw/config.js +53 -0
  210. package/dist/plugins/openclaw/config.js.map +1 -0
  211. package/dist/plugins/openclaw/handler.d.ts +90 -0
  212. package/dist/plugins/openclaw/handler.d.ts.map +1 -0
  213. package/dist/plugins/openclaw/handler.js +146 -0
  214. package/dist/plugins/openclaw/handler.js.map +1 -0
  215. package/dist/plugins/openclaw/index.d.ts +55 -0
  216. package/dist/plugins/openclaw/index.d.ts.map +1 -0
  217. package/dist/plugins/openclaw/index.js +89 -0
  218. package/dist/plugins/openclaw/index.js.map +1 -0
  219. package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
  220. package/dist/plugins/openclaw/package.json +14 -0
  221. package/dist/plugins/openclaw/realtime.d.ts +82 -0
  222. package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
  223. package/dist/plugins/openclaw/realtime.js +238 -0
  224. package/dist/plugins/openclaw/realtime.js.map +1 -0
  225. package/dist/plugins/openclaw/runtime.d.ts +19 -0
  226. package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
  227. package/dist/plugins/openclaw/runtime.js +160 -0
  228. package/dist/plugins/openclaw/runtime.js.map +1 -0
  229. package/dist/policy.d.ts +38 -0
  230. package/dist/policy.d.ts.map +1 -0
  231. package/dist/policy.js +95 -0
  232. package/dist/policy.js.map +1 -0
  233. package/dist/protocol.d.ts +44 -0
  234. package/dist/protocol.d.ts.map +1 -0
  235. package/dist/protocol.js +77 -0
  236. package/dist/protocol.js.map +1 -0
  237. package/dist/protocolRuntime.d.ts +19 -0
  238. package/dist/protocolRuntime.d.ts.map +1 -0
  239. package/dist/protocolRuntime.js +78 -0
  240. package/dist/protocolRuntime.js.map +1 -0
  241. package/dist/smoke.d.ts +65 -0
  242. package/dist/smoke.d.ts.map +1 -0
  243. package/dist/smoke.js +293 -0
  244. package/dist/smoke.js.map +1 -0
  245. package/dist/startup.d.ts +74 -0
  246. package/dist/startup.d.ts.map +1 -0
  247. package/dist/startup.js +112 -0
  248. package/dist/startup.js.map +1 -0
  249. package/dist/tile.d.ts +101 -0
  250. package/dist/tile.d.ts.map +1 -0
  251. package/dist/tile.js +195 -0
  252. package/dist/tile.js.map +1 -0
  253. package/dist/version.d.ts +2 -0
  254. package/dist/version.d.ts.map +1 -0
  255. package/dist/version.js +4 -0
  256. package/dist/version.js.map +1 -0
  257. package/dist/vision.d.ts +160 -0
  258. package/dist/vision.d.ts.map +1 -0
  259. package/dist/vision.js +301 -0
  260. package/dist/vision.js.map +1 -0
  261. package/dist/visionTools.d.ts +237 -0
  262. package/dist/visionTools.d.ts.map +1 -0
  263. package/dist/visionTools.js +578 -0
  264. package/dist/visionTools.js.map +1 -0
  265. package/dist/voice.d.ts +143 -0
  266. package/dist/voice.d.ts.map +1 -0
  267. package/dist/voice.js +403 -0
  268. package/dist/voice.js.map +1 -0
  269. package/package.json +129 -0
package/dist/config.js ADDED
@@ -0,0 +1,86 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Reading configuration, and failing usefully when it is wrong.
5
+ *
6
+ * Every plugin needs the same four things: a value that must be set, one that
7
+ * may be, a boolean, and a check that a vendor host is really that vendor's.
8
+ * Each one had written its own, so the error a user saw for a missing key
9
+ * depended on which provider they happened to pick.
10
+ *
11
+ * The host check is the one worth reading twice. Your API key travels to
12
+ * whatever host the configuration names, so a mistyped or injected host is not a
13
+ * failed call, it is credential exfiltration.
14
+ *
15
+ * Identical in shape to the Python SDK's `standin.config`.
16
+ */
17
+ import { StandInError } from "./errors.js";
18
+ /**
19
+ * Read a variable that must be set, or throw naming it.
20
+ *
21
+ * `purpose` completes the sentence "X is required to ...", so write it as a verb
22
+ * phrase: `"answer calls with ElevenLabs"`.
23
+ */
24
+ export function required(name, purpose = "") {
25
+ const value = (process.env[name] ?? "").trim();
26
+ if (!value)
27
+ throw new StandInError(`${name} is required${purpose ? ` to ${purpose}` : ""}`);
28
+ return value;
29
+ }
30
+ /** Read a variable that may be set. Blank reads as absent. */
31
+ export function optional(name, fallback) {
32
+ return (process.env[name] ?? "").trim() || fallback;
33
+ }
34
+ /**
35
+ * Read a boolean. Only `true` is true, so a typo is off rather than on.
36
+ *
37
+ * Deliberately strict. A setting that turns a guard OFF must not be turned off
38
+ * by `TRUE`, `1` or `yes` landing in a config file by accident.
39
+ */
40
+ export function flag(name, fallback = false) {
41
+ const raw = (process.env[name] ?? "").trim().toLowerCase();
42
+ if (!raw)
43
+ return fallback;
44
+ return raw === "true";
45
+ }
46
+ /**
47
+ * Read a host and refuse one that is not the vendor's.
48
+ *
49
+ * Your API key travels to this host. A mistyped or injected value is credential
50
+ * exfiltration rather than a failed call, which is why this throws instead of
51
+ * warning.
52
+ */
53
+ export function vendorHost(name, fallback, suffix) {
54
+ const host = (process.env[name] ?? "").trim() || fallback;
55
+ const bare = suffix.replace(/^\./, "");
56
+ if (host !== bare && !host.endsWith(suffix)) {
57
+ throw new StandInError(`${name} must be a ${bare} host, got ${host}`);
58
+ }
59
+ return host;
60
+ }
61
+ /**
62
+ * Read a JSON object of strings, or throw saying it must be one.
63
+ *
64
+ * Used for header maps, which carry YOUR credentials to somebody else's
65
+ * endpoint, so the value is never logged on the failure path.
66
+ */
67
+ export function jsonObject(name) {
68
+ const raw = (process.env[name] ?? "").trim();
69
+ if (!raw)
70
+ return {};
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(raw);
74
+ }
75
+ catch {
76
+ throw new StandInError(`${name} must be a JSON object`);
77
+ }
78
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
79
+ throw new StandInError(`${name} must be a JSON object`);
80
+ }
81
+ return Object.fromEntries(Object.entries(parsed).map(([k, v]) => [
82
+ k,
83
+ String(v),
84
+ ]));
85
+ }
86
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAO,GAAG,EAAE;IACjD,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,CAAC,KAAK;QACR,MAAM,IAAI,YAAY,CACpB,GAAG,IAAI,eAAe,OAAO,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACxD,CAAC;IACJ,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,QAAiB;IACtD,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,QAAQ,GAAG,KAAK;IACjD,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,OAAO,GAAG,KAAK,MAAM,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,QAAgB,EAChB,MAAc;IAEd,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,cAAc,IAAI,cAAc,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAiC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;QAChE,CAAC;QACD,MAAM,CAAC,CAAC,CAAC;KACV,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,147 @@
1
+ import type { ToolSpec } from "./callTools.js";
2
+ /**
3
+ * How long a caller will wait on the line before "let me check" stops sounding
4
+ * like thinking and starts sounding like a dropped call.
5
+ */
6
+ export declare const DEFAULT_CONSULT_TIMEOUT_MS = 45000;
7
+ /** A background task gets far longer, because nobody is listening to it. */
8
+ export declare const DEFAULT_TASK_TIMEOUT_MS = 300000;
9
+ /**
10
+ * Promises go stale. Delivering a two-hour-old answer to a question somebody has
11
+ * long since answered themselves is worse than delivering nothing.
12
+ */
13
+ export declare const DEFAULT_TASK_TTL_MS: number;
14
+ /**
15
+ * How many interrupted tasks one startup will take on. A restart storm must not
16
+ * fan out every promise the fleet ever made at once. What is left stays queued.
17
+ */
18
+ export declare const DEFAULT_RESUME_LIMIT = 5;
19
+ /** What a consultation is: a question in, an answer out. */
20
+ export type Asker = (query: string) => string | Promise<string>;
21
+ /**
22
+ * Called to build an asker, on first use and again after a timeout. It is a
23
+ * factory rather than a value because a timed-out consultation cannot be
24
+ * stopped: it runs on, and whatever state it holds is no longer yours.
25
+ */
26
+ export type AskerFactory = () => Asker;
27
+ /** Deliver a finished task's result. Returns whether it actually went out. */
28
+ export type Deliverer = (threadId: string, text: string) => Promise<boolean>;
29
+ export declare const CONSULT_TOOL: ToolSpec;
30
+ export declare const BACKGROUND_TASK_TOOL: ToolSpec;
31
+ /**
32
+ * One slow agent, reachable from a live call without breaking it.
33
+ *
34
+ * Built once per call and asked whenever the fast model delegates:
35
+ *
36
+ * ```ts
37
+ * const consultant = new Consultant(() => myAgent.run);
38
+ * const answer = await consultant.ask("what did we bill Contoso last quarter?");
39
+ * ```
40
+ *
41
+ * Everything here exists because a caller is listening while it runs.
42
+ *
43
+ * **One at a time, and the second asker is told so.** A model that can delegate
44
+ * can delegate twice before the first answer lands. Queueing the second behind
45
+ * the first means it waits out both timeouts and answers far too late, so it is
46
+ * refused immediately with something the model can say out loud.
47
+ *
48
+ * **A timeout is admitted, not papered over.** The work cannot be cancelled: a
49
+ * promise runs to its end whatever this returns. So the answer says it stopped
50
+ * rather than promising a follow-up nothing will send. Pass
51
+ * `backgroundAvailable` when you have registered {@link BACKGROUND_TASK_TOOL}
52
+ * and the answer will point the caller at it.
53
+ *
54
+ * Your asker must not block. Node has one thread, and a synchronous agent holds
55
+ * it for its whole duration, stopping the call's audio along with everything
56
+ * else. Do the work in a promise, a worker, or another process.
57
+ */
58
+ export declare class Consultant {
59
+ #private;
60
+ constructor(build: AskerFactory, timeoutMs?: number, backgroundAvailable?: boolean);
61
+ /** Whether a consultation is running right now. */
62
+ get busy(): boolean;
63
+ /**
64
+ * Put a question to the slow agent and return what to say.
65
+ *
66
+ * Never throws, and always returns something speakable. A tool result goes
67
+ * straight back to a model that will read it out, so an exception here is an
68
+ * agent that goes quiet mid-sentence.
69
+ */
70
+ ask(query: string, timeoutMs?: number): Promise<string>;
71
+ }
72
+ /** One promise, as it sits on disk. */
73
+ export interface BackgroundTask {
74
+ readonly taskId: string;
75
+ readonly query: string;
76
+ /** Where the result goes. A task with nowhere to deliver cannot be kept. */
77
+ readonly threadId: string;
78
+ readonly sessionKey?: string;
79
+ readonly createdMs?: number;
80
+ /**
81
+ * Set only on a claimed task, so {@link BackgroundTasks.finish} can put it
82
+ * back if the delivery fails.
83
+ */
84
+ readonly claimPath?: string;
85
+ }
86
+ /** Options for {@link BackgroundTasks}. */
87
+ export interface BackgroundTasksOptions {
88
+ directory?: string;
89
+ ttlMs?: number;
90
+ resumeLimit?: number;
91
+ }
92
+ /**
93
+ * Promised work that survives the process that promised it.
94
+ *
95
+ * "I will send you the result" is a promise made on a call that is about to end.
96
+ * Held in memory it lasts until the next deploy, and then it is gone with
97
+ * nothing said to the person waiting. Held here it is a small file, written
98
+ * BEFORE the work starts and removed only once the result has actually been
99
+ * delivered.
100
+ *
101
+ * The two-phase claim is what makes a restart safe. A task waiting to run is a
102
+ * `.json`; a task being run is a `.claimed`. A process that dies mid-run leaves
103
+ * a claim behind, and the next startup takes it back once it is old enough to be
104
+ * certain nobody is still working on it. A delivery that fails puts the task
105
+ * back rather than dropping it.
106
+ */
107
+ export declare class BackgroundTasks {
108
+ #private;
109
+ constructor(options?: BackgroundTasksOptions);
110
+ /**
111
+ * Write the promise down, before starting the work.
112
+ *
113
+ * Returns undefined when it could not be written, which is a task that runs
114
+ * but will not survive a restart. That is worth knowing about and worth
115
+ * continuing with: a non-durable answer still beats no answer.
116
+ */
117
+ remember(query: string, threadId: string, sessionKey?: string): BackgroundTask | undefined;
118
+ /**
119
+ * Mark a task as being worked on, so a crash mid-run is recoverable.
120
+ *
121
+ * Without this, a process that dies halfway leaves a task that looks
122
+ * untouched, and the next startup runs it again while the delivery from the
123
+ * first run may still be in flight.
124
+ */
125
+ begin(task: BackgroundTask | undefined): void;
126
+ /** The result was delivered. Retire the record, in either phase. */
127
+ done(task: BackgroundTask | undefined): void;
128
+ /**
129
+ * Take the tasks a previous process left behind.
130
+ *
131
+ * Claiming is a rename, so two workers starting together cannot take the same
132
+ * task. Everything beyond the resume limit is LEFT WHERE IT IS for the next
133
+ * cycle rather than discarded: a busy restart must not become a quiet way of
134
+ * losing promises.
135
+ */
136
+ claimPending(): BackgroundTask[];
137
+ /** Retire a claimed task, or put it back so the next cycle retries it. */
138
+ finish(task: BackgroundTask, delivered: boolean): void;
139
+ /**
140
+ * Re-run what a previous process left unfinished. Returns how many landed.
141
+ *
142
+ * Call it once at startup. Sequential on purpose: a restart storm must not fan
143
+ * out one agent per interrupted task across the whole fleet.
144
+ */
145
+ resume(build: AskerFactory, deliver: Deliverer, timeoutMs?: number): Promise<number>;
146
+ }
147
+ //# sourceMappingURL=consult.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consult.d.ts","sourceRoot":"","sources":["../src/consult.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK/C;;;GAGG;AACH,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAqB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAKtC,4DAA4D;AAC5D,MAAM,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAEhE;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,eAAO,MAAM,YAAY,EAAE,QAa1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,QAalC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,UAAU;;gBAQnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,MAAmC,EAC9C,mBAAmB,UAAQ;IAU7B,mDAAmD;IACnD,IAAI,IAAI,IAAI,OAAO,CAElB;IAED;;;;;;OAMG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA8C9D;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;;gBAKd,OAAO,GAAE,sBAA2B;IAUhD;;;;;;OAMG;IACH,QAAQ,CACN,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,SAAK,GACd,cAAc,GAAG,SAAS;IA8B7B;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI;IAY7C,oEAAoE;IACpE,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI;IAW5C;;;;;;;OAOG;IACH,YAAY,IAAI,cAAc,EAAE;IAkDhC,0EAA0E;IAC1E,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI;IAgBtD;;;;;OAKG;IACG,MAAM,CACV,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,SAAS,EAClB,SAAS,GAAE,MAAgC,GAC1C,OAAO,CAAC,MAAM,CAAC;CA+CnB"}
@@ -0,0 +1,388 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * The slow half of a two-speed agent.
5
+ *
6
+ * A voice model has to answer in under a second or the call sounds broken. Real
7
+ * work does not fit in a second. Looking something up, reading a file, driving a
8
+ * browser, running a tool: those take ten seconds, or five minutes, and a caller
9
+ * listening to silence has no way to tell the difference between thinking and
10
+ * crashed.
11
+ *
12
+ * So an agent that does real work is two agents. The fast one talks. The slow
13
+ * one works. This module is the seam between them, and it is in the core because
14
+ * the seam is the same whichever provider is doing the talking and whichever
15
+ * agent framework is doing the working.
16
+ *
17
+ * Two paths, and the difference between them is a promise:
18
+ *
19
+ * {@link Consultant} is "hold on, let me check". Time-boxed, answered in the
20
+ * same breath. The caller waits, so the box has to be small.
21
+ *
22
+ * {@link BackgroundTasks} is "I'll send you the result". The caller hangs up.
23
+ * That promise outlives the call, and therefore has to outlive the process: a
24
+ * restart between making it and keeping it is ordinary, and an in-memory task
25
+ * list breaks the promise silently. Every task is on disk before the work
26
+ * starts.
27
+ *
28
+ * Neither knows what your agent is. You supply a function; what it does inside
29
+ * is yours.
30
+ *
31
+ * Identical in shape to the Python SDK's `standin.consult`.
32
+ */
33
+ import { randomUUID } from "node:crypto";
34
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
35
+ import { join } from "node:path";
36
+ import { nowMs } from "./hmac.js";
37
+ import { logger } from "./log.js";
38
+ import { stateDir } from "./outbound.js";
39
+ /**
40
+ * How long a caller will wait on the line before "let me check" stops sounding
41
+ * like thinking and starts sounding like a dropped call.
42
+ */
43
+ export const DEFAULT_CONSULT_TIMEOUT_MS = 45_000;
44
+ /** A background task gets far longer, because nobody is listening to it. */
45
+ export const DEFAULT_TASK_TIMEOUT_MS = 300_000;
46
+ /**
47
+ * Promises go stale. Delivering a two-hour-old answer to a question somebody has
48
+ * long since answered themselves is worse than delivering nothing.
49
+ */
50
+ export const DEFAULT_TASK_TTL_MS = 2 * 60 * 60 * 1000;
51
+ /**
52
+ * How many interrupted tasks one startup will take on. A restart storm must not
53
+ * fan out every promise the fleet ever made at once. What is left stays queued.
54
+ */
55
+ export const DEFAULT_RESUME_LIMIT = 5;
56
+ /** A claim older than this belonged to a process that died holding it. */
57
+ const STALE_CLAIM_MS = 600_000;
58
+ export const CONSULT_TOOL = {
59
+ name: "consult",
60
+ description: "Hand a question or a task to your fuller agent, which can look things up, read " +
61
+ "files, browse, and run tools. Use it for anything beyond conversation. The caller " +
62
+ "waits on the line, so use it only when the answer will come back quickly.",
63
+ parameters: {
64
+ query: {
65
+ type: "string",
66
+ description: "What to look into or do, phrased as a task.",
67
+ },
68
+ },
69
+ required: ["query"],
70
+ };
71
+ export const BACKGROUND_TASK_TOOL = {
72
+ name: "background_task",
73
+ description: "Start a long job and have the result sent to the caller in chat when it is done. " +
74
+ "Use this instead of consult when the work will not finish while the caller waits. " +
75
+ "Tell the caller you are on it; the result is delivered afterwards.",
76
+ parameters: {
77
+ query: {
78
+ type: "string",
79
+ description: "The task to run in the background.",
80
+ },
81
+ },
82
+ required: ["query"],
83
+ };
84
+ /**
85
+ * One slow agent, reachable from a live call without breaking it.
86
+ *
87
+ * Built once per call and asked whenever the fast model delegates:
88
+ *
89
+ * ```ts
90
+ * const consultant = new Consultant(() => myAgent.run);
91
+ * const answer = await consultant.ask("what did we bill Contoso last quarter?");
92
+ * ```
93
+ *
94
+ * Everything here exists because a caller is listening while it runs.
95
+ *
96
+ * **One at a time, and the second asker is told so.** A model that can delegate
97
+ * can delegate twice before the first answer lands. Queueing the second behind
98
+ * the first means it waits out both timeouts and answers far too late, so it is
99
+ * refused immediately with something the model can say out loud.
100
+ *
101
+ * **A timeout is admitted, not papered over.** The work cannot be cancelled: a
102
+ * promise runs to its end whatever this returns. So the answer says it stopped
103
+ * rather than promising a follow-up nothing will send. Pass
104
+ * `backgroundAvailable` when you have registered {@link BACKGROUND_TASK_TOOL}
105
+ * and the answer will point the caller at it.
106
+ *
107
+ * Your asker must not block. Node has one thread, and a synchronous agent holds
108
+ * it for its whole duration, stopping the call's audio along with everything
109
+ * else. Do the work in a promise, a worker, or another process.
110
+ */
111
+ export class Consultant {
112
+ #build;
113
+ #timeoutMs;
114
+ #backgroundAvailable;
115
+ #asker;
116
+ #busy = false;
117
+ constructor(build, timeoutMs = DEFAULT_CONSULT_TIMEOUT_MS, backgroundAvailable = false) {
118
+ this.#build = build;
119
+ this.#timeoutMs = timeoutMs;
120
+ // What the caller is told on a timeout depends on whether there is anywhere
121
+ // to hand the work off to. Pointing at a background task this agent cannot
122
+ // start is a promise nothing keeps, so it is opt in.
123
+ this.#backgroundAvailable = backgroundAvailable;
124
+ }
125
+ /** Whether a consultation is running right now. */
126
+ get busy() {
127
+ return this.#busy;
128
+ }
129
+ /**
130
+ * Put a question to the slow agent and return what to say.
131
+ *
132
+ * Never throws, and always returns something speakable. A tool result goes
133
+ * straight back to a model that will read it out, so an exception here is an
134
+ * agent that goes quiet mid-sentence.
135
+ */
136
+ async ask(query, timeoutMs) {
137
+ const question = (query ?? "").trim();
138
+ if (question === "")
139
+ return "I did not catch what you wanted me to look into.";
140
+ if (this.#busy)
141
+ return "I am still finishing the last one. Give me a moment and ask again.";
142
+ this.#busy = true;
143
+ let timer;
144
+ try {
145
+ const timedOut = Symbol("timed out");
146
+ const deadline = new Promise((resolve) => {
147
+ timer = setTimeout(() => resolve(timedOut), timeoutMs ?? this.#timeoutMs);
148
+ timer.unref?.();
149
+ });
150
+ const answer = await Promise.race([this.#run(question), deadline]);
151
+ if (answer === timedOut) {
152
+ // The work is still running and cannot be stopped, so the asker is
153
+ // dropped: the next consultation builds a fresh one rather than sharing
154
+ // state with something nobody is waiting for any more.
155
+ this.#asker = undefined;
156
+ logger.warn("standin: a consultation ran past its time and was abandoned");
157
+ return this.#backgroundAvailable
158
+ ? "Sorry, that took too long and I had to stop. Ask me to work on it in the " +
159
+ "background and I will send you the result when it is done."
160
+ : "Sorry, that took too long and I had to stop. Ask me again and I will narrow it down.";
161
+ }
162
+ return answer.trim() || "I did not find anything to report.";
163
+ }
164
+ catch (err) {
165
+ logger.warn(`standin: a consultation failed: ${String(err)}`);
166
+ return "Sorry, I ran into a problem working on that.";
167
+ }
168
+ finally {
169
+ if (timer !== undefined)
170
+ clearTimeout(timer);
171
+ this.#busy = false;
172
+ }
173
+ }
174
+ async #run(question) {
175
+ if (this.#asker === undefined)
176
+ this.#asker = this.#build();
177
+ return String(await this.#asker(question));
178
+ }
179
+ }
180
+ /**
181
+ * Promised work that survives the process that promised it.
182
+ *
183
+ * "I will send you the result" is a promise made on a call that is about to end.
184
+ * Held in memory it lasts until the next deploy, and then it is gone with
185
+ * nothing said to the person waiting. Held here it is a small file, written
186
+ * BEFORE the work starts and removed only once the result has actually been
187
+ * delivered.
188
+ *
189
+ * The two-phase claim is what makes a restart safe. A task waiting to run is a
190
+ * `.json`; a task being run is a `.claimed`. A process that dies mid-run leaves
191
+ * a claim behind, and the next startup takes it back once it is old enough to be
192
+ * certain nobody is still working on it. A delivery that fails puts the task
193
+ * back rather than dropping it.
194
+ */
195
+ export class BackgroundTasks {
196
+ #dir;
197
+ #ttlMs;
198
+ #resumeLimit;
199
+ constructor(options = {}) {
200
+ this.#dir = options.directory ?? join(stateDir(), "tasks");
201
+ mkdirSync(this.#dir, { recursive: true, mode: 0o700 });
202
+ this.#ttlMs = options.ttlMs ?? DEFAULT_TASK_TTL_MS;
203
+ this.#resumeLimit = Math.max(1, options.resumeLimit ?? DEFAULT_RESUME_LIMIT);
204
+ }
205
+ /**
206
+ * Write the promise down, before starting the work.
207
+ *
208
+ * Returns undefined when it could not be written, which is a task that runs
209
+ * but will not survive a restart. That is worth knowing about and worth
210
+ * continuing with: a non-durable answer still beats no answer.
211
+ */
212
+ remember(query, threadId, sessionKey = "") {
213
+ const task = {
214
+ taskId: randomUUID().replace(/-/g, ""),
215
+ query,
216
+ threadId,
217
+ sessionKey,
218
+ createdMs: nowMs(),
219
+ };
220
+ const target = join(this.#dir, `${task.taskId}.json`);
221
+ const temp = `${target}.${randomUUID()}.tmp`;
222
+ try {
223
+ writeFileSync(temp, JSON.stringify(task), {
224
+ encoding: "utf8",
225
+ mode: 0o600,
226
+ });
227
+ renameSync(temp, target);
228
+ }
229
+ catch (err) {
230
+ logger.warn(`standin: a background task could not be made durable: ${String(err)}`);
231
+ try {
232
+ rmSync(temp, { force: true });
233
+ }
234
+ catch {
235
+ // Nothing left to clean up.
236
+ }
237
+ return undefined;
238
+ }
239
+ return task;
240
+ }
241
+ /**
242
+ * Mark a task as being worked on, so a crash mid-run is recoverable.
243
+ *
244
+ * Without this, a process that dies halfway leaves a task that looks
245
+ * untouched, and the next startup runs it again while the delivery from the
246
+ * first run may still be in flight.
247
+ */
248
+ begin(task) {
249
+ if (task === undefined || !task.taskId)
250
+ return;
251
+ try {
252
+ renameSync(join(this.#dir, `${task.taskId}.json`), join(this.#dir, `${task.taskId}.claimed`));
253
+ }
254
+ catch {
255
+ // Already claimed, already gone, or never durable.
256
+ }
257
+ }
258
+ /** The result was delivered. Retire the record, in either phase. */
259
+ done(task) {
260
+ if (task === undefined || !task.taskId)
261
+ return;
262
+ for (const suffix of [".json", ".claimed"]) {
263
+ try {
264
+ rmSync(join(this.#dir, `${task.taskId}${suffix}`), { force: true });
265
+ }
266
+ catch {
267
+ // Nothing to retire.
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * Take the tasks a previous process left behind.
273
+ *
274
+ * Claiming is a rename, so two workers starting together cannot take the same
275
+ * task. Everything beyond the resume limit is LEFT WHERE IT IS for the next
276
+ * cycle rather than discarded: a busy restart must not become a quiet way of
277
+ * losing promises.
278
+ */
279
+ claimPending() {
280
+ this.#recoverOrphans();
281
+ const claimed = [];
282
+ for (const name of readdirSync(this.#dir).sort()) {
283
+ if (!name.endsWith(".json"))
284
+ continue;
285
+ if (claimed.length >= this.#resumeLimit) {
286
+ logger.info("standin: resume limit reached; the remaining background tasks stay queued");
287
+ break;
288
+ }
289
+ const path = join(this.#dir, name);
290
+ let record;
291
+ try {
292
+ record = JSON.parse(readFileSync(path, "utf8"));
293
+ }
294
+ catch {
295
+ // Unreadable is unrecoverable, and leaving it means reading it again on
296
+ // every startup for ever.
297
+ rmSync(path, { force: true });
298
+ continue;
299
+ }
300
+ const ageMs = nowMs() - (record.createdMs ?? 0);
301
+ if (record.createdMs !== undefined && ageMs > this.#ttlMs) {
302
+ logger.info(`standin: dropping a background task promised ${Math.round(ageMs / 60_000)} minutes ago`);
303
+ rmSync(path, { force: true });
304
+ continue;
305
+ }
306
+ if (!record.threadId) {
307
+ logger.info("standin: dropping a background task with nowhere to deliver");
308
+ rmSync(path, { force: true });
309
+ continue;
310
+ }
311
+ const claim = path.replace(/\.json$/, ".claimed");
312
+ try {
313
+ renameSync(path, claim);
314
+ }
315
+ catch {
316
+ continue; // somebody else took it first
317
+ }
318
+ claimed.push({ ...record, claimPath: claim });
319
+ }
320
+ return claimed;
321
+ }
322
+ /** Retire a claimed task, or put it back so the next cycle retries it. */
323
+ finish(task, delivered) {
324
+ if (!task.claimPath)
325
+ return;
326
+ try {
327
+ if (delivered) {
328
+ rmSync(task.claimPath, { force: true });
329
+ }
330
+ else if (existsSync(task.claimPath)) {
331
+ renameSync(task.claimPath, task.claimPath.replace(/\.claimed$/, ".json"));
332
+ }
333
+ }
334
+ catch {
335
+ // The next cycle's orphan recovery is the backstop.
336
+ }
337
+ }
338
+ /**
339
+ * Re-run what a previous process left unfinished. Returns how many landed.
340
+ *
341
+ * Call it once at startup. Sequential on purpose: a restart storm must not fan
342
+ * out one agent per interrupted task across the whole fleet.
343
+ */
344
+ async resume(build, deliver, timeoutMs = DEFAULT_TASK_TIMEOUT_MS) {
345
+ let delivered = 0;
346
+ for (const task of this.claimPending()) {
347
+ logger.info("standin: finishing a background task left by a restart");
348
+ const answer = await new Consultant(build, timeoutMs).ask(task.query, timeoutMs);
349
+ let sent = false;
350
+ try {
351
+ sent = await deliver(task.threadId, answer);
352
+ }
353
+ catch (err) {
354
+ logger.warn(`standin: delivering a resumed background task failed: ${String(err)}`);
355
+ }
356
+ this.finish(task, sent);
357
+ if (sent)
358
+ delivered += 1;
359
+ else
360
+ logger.warn("standin: a resumed background task is kept for the next try");
361
+ }
362
+ return delivered;
363
+ }
364
+ /**
365
+ * Put back claims whose owner died.
366
+ *
367
+ * Judged by the claim's own age. A claim younger than the window may still
368
+ * have somebody working on it, and taking it would deliver the same answer
369
+ * twice.
370
+ */
371
+ #recoverOrphans() {
372
+ const cutoff = Date.now() - STALE_CLAIM_MS;
373
+ for (const name of readdirSync(this.#dir)) {
374
+ if (!name.endsWith(".claimed"))
375
+ continue;
376
+ const path = join(this.#dir, name);
377
+ try {
378
+ if (statSync(path).mtimeMs < cutoff) {
379
+ renameSync(path, path.replace(/\.claimed$/, ".json"));
380
+ }
381
+ }
382
+ catch {
383
+ // Gone, or taken by somebody else between the read and the rename.
384
+ }
385
+ }
386
+ }
387
+ }
388
+ //# sourceMappingURL=consult.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consult.js","sourceRoot":"","sources":["../src/consult.ts"],"names":[],"mappings":"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"}