@bd7pil/ocrc 0.9.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 (292) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +330 -0
  3. package/dist/cli/host-backends.js +216 -0
  4. package/dist/cli/host-backends.js.map +1 -0
  5. package/dist/cli/host.js +185 -0
  6. package/dist/cli/host.js.map +1 -0
  7. package/dist/cli/index.js +67 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/init.js +68 -0
  10. package/dist/cli/init.js.map +1 -0
  11. package/dist/cli/install.js +220 -0
  12. package/dist/cli/install.js.map +1 -0
  13. package/dist/cli/pair.js +11 -0
  14. package/dist/cli/pair.js.map +1 -0
  15. package/dist/cli/uninstall.js +75 -0
  16. package/dist/cli/uninstall.js.map +1 -0
  17. package/dist/connectivity/auth/cf-access.js +20 -0
  18. package/dist/connectivity/auth/cf-access.js.map +1 -0
  19. package/dist/connectivity/auth/index.js +2 -0
  20. package/dist/connectivity/auth/index.js.map +1 -0
  21. package/dist/connectivity/auth/select.js +19 -0
  22. package/dist/connectivity/auth/select.js.map +1 -0
  23. package/dist/connectivity/auth/token.js +157 -0
  24. package/dist/connectivity/auth/token.js.map +1 -0
  25. package/dist/connectivity/exposure/cloudflared.js +77 -0
  26. package/dist/connectivity/exposure/cloudflared.js.map +1 -0
  27. package/dist/connectivity/exposure/index.js +2 -0
  28. package/dist/connectivity/exposure/index.js.map +1 -0
  29. package/dist/connectivity/exposure/providers.js +49 -0
  30. package/dist/connectivity/exposure/providers.js.map +1 -0
  31. package/dist/connectivity/pairing.js +29 -0
  32. package/dist/connectivity/pairing.js.map +1 -0
  33. package/dist/core/agent/acp-backend.js +539 -0
  34. package/dist/core/agent/acp-backend.js.map +1 -0
  35. package/dist/core/agent/acp-connect.js +92 -0
  36. package/dist/core/agent/acp-connect.js.map +1 -0
  37. package/dist/core/agent/acp-normalizer.js +81 -0
  38. package/dist/core/agent/acp-normalizer.js.map +1 -0
  39. package/dist/core/agent/acp-replay.js +89 -0
  40. package/dist/core/agent/acp-replay.js.map +1 -0
  41. package/dist/core/agent/acp-store.js +129 -0
  42. package/dist/core/agent/acp-store.js.map +1 -0
  43. package/dist/core/agent/backend.js +2 -0
  44. package/dist/core/agent/backend.js.map +1 -0
  45. package/dist/core/agent/diff-util.js +70 -0
  46. package/dist/core/agent/diff-util.js.map +1 -0
  47. package/dist/core/agent/event.js +12 -0
  48. package/dist/core/agent/event.js.map +1 -0
  49. package/dist/core/agent/opencode-backend.js +598 -0
  50. package/dist/core/agent/opencode-backend.js.map +1 -0
  51. package/dist/core/agent/opencode-normalizer.js +69 -0
  52. package/dist/core/agent/opencode-normalizer.js.map +1 -0
  53. package/dist/core/agent/registry.js +55 -0
  54. package/dist/core/agent/registry.js.map +1 -0
  55. package/dist/core/agent/v2-backend.js +176 -0
  56. package/dist/core/agent/v2-backend.js.map +1 -0
  57. package/dist/core/card-bus.js +74 -0
  58. package/dist/core/card-bus.js.map +1 -0
  59. package/dist/core/channels.js +95 -0
  60. package/dist/core/channels.js.map +1 -0
  61. package/dist/core/history.js +91 -0
  62. package/dist/core/history.js.map +1 -0
  63. package/dist/core/list-files.js +50 -0
  64. package/dist/core/list-files.js.map +1 -0
  65. package/dist/core/opencode-events.js +29 -0
  66. package/dist/core/opencode-events.js.map +1 -0
  67. package/dist/core/primary-election.js +91 -0
  68. package/dist/core/primary-election.js.map +1 -0
  69. package/dist/core/push.js +152 -0
  70. package/dist/core/push.js.map +1 -0
  71. package/dist/core/relay.js +400 -0
  72. package/dist/core/relay.js.map +1 -0
  73. package/dist/core/scheduler.js +150 -0
  74. package/dist/core/scheduler.js.map +1 -0
  75. package/dist/core/state.js +222 -0
  76. package/dist/core/state.js.map +1 -0
  77. package/dist/core/stream-accumulator.js +76 -0
  78. package/dist/core/stream-accumulator.js.map +1 -0
  79. package/dist/core/structured-card.js +2 -0
  80. package/dist/core/structured-card.js.map +1 -0
  81. package/dist/core/types.js +2 -0
  82. package/dist/core/types.js.map +1 -0
  83. package/dist/index.js +3 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/opencode/global-events.js +57 -0
  86. package/dist/opencode/global-events.js.map +1 -0
  87. package/dist/opencode/list-sessions.js +122 -0
  88. package/dist/opencode/list-sessions.js.map +1 -0
  89. package/dist/opencode/submit.js +39 -0
  90. package/dist/opencode/submit.js.map +1 -0
  91. package/dist/opencode/workspaces.js +51 -0
  92. package/dist/opencode/workspaces.js.map +1 -0
  93. package/dist/plugin/config.js +128 -0
  94. package/dist/plugin/config.js.map +1 -0
  95. package/dist/plugin/control-plane.js +133 -0
  96. package/dist/plugin/control-plane.js.map +1 -0
  97. package/dist/plugin/entry.js +409 -0
  98. package/dist/plugin/entry.js.map +1 -0
  99. package/dist/plugin/v2/event-map.js +193 -0
  100. package/dist/plugin/v2/event-map.js.map +1 -0
  101. package/dist/plugin/v2/types.js +15 -0
  102. package/dist/plugin/v2/types.js.map +1 -0
  103. package/dist/transport/interface.js +2 -0
  104. package/dist/transport/interface.js.map +1 -0
  105. package/dist/transport/telegram/esc.js +9 -0
  106. package/dist/transport/telegram/esc.js.map +1 -0
  107. package/dist/transport/telegram/handlers/info-commands.js +101 -0
  108. package/dist/transport/telegram/handlers/info-commands.js.map +1 -0
  109. package/dist/transport/telegram/handlers.js +1348 -0
  110. package/dist/transport/telegram/handlers.js.map +1 -0
  111. package/dist/transport/telegram/i18n/en.js +538 -0
  112. package/dist/transport/telegram/i18n/en.js.map +1 -0
  113. package/dist/transport/telegram/i18n/index.js +83 -0
  114. package/dist/transport/telegram/i18n/index.js.map +1 -0
  115. package/dist/transport/telegram/i18n/zh.js +538 -0
  116. package/dist/transport/telegram/i18n/zh.js.map +1 -0
  117. package/dist/transport/telegram/index.js +792 -0
  118. package/dist/transport/telegram/index.js.map +1 -0
  119. package/dist/transport/telegram/main-keyboard.js +36 -0
  120. package/dist/transport/telegram/main-keyboard.js.map +1 -0
  121. package/dist/transport/telegram/managers/abort-suppression-manager.js +33 -0
  122. package/dist/transport/telegram/managers/abort-suppression-manager.js.map +1 -0
  123. package/dist/transport/telegram/managers/external-input-suppression-manager.js +51 -0
  124. package/dist/transport/telegram/managers/external-input-suppression-manager.js.map +1 -0
  125. package/dist/transport/telegram/managers/interaction-manager.js +254 -0
  126. package/dist/transport/telegram/managers/interaction-manager.js.map +1 -0
  127. package/dist/transport/telegram/managers/permission-manager.js +242 -0
  128. package/dist/transport/telegram/managers/permission-manager.js.map +1 -0
  129. package/dist/transport/telegram/managers/question-manager.js +191 -0
  130. package/dist/transport/telegram/managers/question-manager.js.map +1 -0
  131. package/dist/transport/telegram/managers/rename-manager.js +56 -0
  132. package/dist/transport/telegram/managers/rename-manager.js.map +1 -0
  133. package/dist/transport/telegram/menus.js +77 -0
  134. package/dist/transport/telegram/menus.js.map +1 -0
  135. package/dist/transport/telegram/permission-flow.js +156 -0
  136. package/dist/transport/telegram/permission-flow.js.map +1 -0
  137. package/dist/transport/telegram/question-flow.js +224 -0
  138. package/dist/transport/telegram/question-flow.js.map +1 -0
  139. package/dist/transport/telegram/render/block-parser.js +365 -0
  140. package/dist/transport/telegram/render/block-parser.js.map +1 -0
  141. package/dist/transport/telegram/render/block-plain-text.js +87 -0
  142. package/dist/transport/telegram/render/block-plain-text.js.map +1 -0
  143. package/dist/transport/telegram/render/block-splitter.js +152 -0
  144. package/dist/transport/telegram/render/block-splitter.js.map +1 -0
  145. package/dist/transport/telegram/render/chunker.js +71 -0
  146. package/dist/transport/telegram/render/chunker.js.map +1 -0
  147. package/dist/transport/telegram/render/limits.js +13 -0
  148. package/dist/transport/telegram/render/limits.js.map +1 -0
  149. package/dist/transport/telegram/render/markdown-normalizer.js +102 -0
  150. package/dist/transport/telegram/render/markdown-normalizer.js.map +1 -0
  151. package/dist/transport/telegram/render/markdown-to-telegram-v2.js +116 -0
  152. package/dist/transport/telegram/render/markdown-to-telegram-v2.js.map +1 -0
  153. package/dist/transport/telegram/render/part-signature.js +14 -0
  154. package/dist/transport/telegram/render/part-signature.js.map +1 -0
  155. package/dist/transport/telegram/render/pipeline.js +23 -0
  156. package/dist/transport/telegram/render/pipeline.js.map +1 -0
  157. package/dist/transport/telegram/render/rich-blocks.js +311 -0
  158. package/dist/transport/telegram/render/rich-blocks.js.map +1 -0
  159. package/dist/transport/telegram/render/text-splitter.js +64 -0
  160. package/dist/transport/telegram/render/text-splitter.js.map +1 -0
  161. package/dist/transport/telegram/render/types.js +2 -0
  162. package/dist/transport/telegram/render/types.js.map +1 -0
  163. package/dist/transport/telegram/renderer.js +305 -0
  164. package/dist/transport/telegram/renderer.js.map +1 -0
  165. package/dist/transport/telegram/streaming/duration-formatter.js +68 -0
  166. package/dist/transport/telegram/streaming/duration-formatter.js.map +1 -0
  167. package/dist/transport/telegram/streaming/finalize-assistant-response.js +37 -0
  168. package/dist/transport/telegram/streaming/finalize-assistant-response.js.map +1 -0
  169. package/dist/transport/telegram/streaming/response-streamer.js +400 -0
  170. package/dist/transport/telegram/streaming/response-streamer.js.map +1 -0
  171. package/dist/transport/telegram/streaming/running-tool-tracker.js +150 -0
  172. package/dist/transport/telegram/streaming/running-tool-tracker.js.map +1 -0
  173. package/dist/transport/telegram/streaming/stream-throttle.js +57 -0
  174. package/dist/transport/telegram/streaming/stream-throttle.js.map +1 -0
  175. package/dist/transport/telegram/streaming/tool-call-streamer.js +390 -0
  176. package/dist/transport/telegram/streaming/tool-call-streamer.js.map +1 -0
  177. package/dist/transport/telegram/streaming-render.js +182 -0
  178. package/dist/transport/telegram/streaming-render.js.map +1 -0
  179. package/dist/transport/telegram/types/interaction.js +2 -0
  180. package/dist/transport/telegram/types/interaction.js.map +1 -0
  181. package/dist/transport/telegram/types/model.js +25 -0
  182. package/dist/transport/telegram/types/model.js.map +1 -0
  183. package/dist/transport/telegram/types/permission.js +2 -0
  184. package/dist/transport/telegram/types/permission.js.map +1 -0
  185. package/dist/transport/telegram/types/question.js +2 -0
  186. package/dist/transport/telegram/types/question.js.map +1 -0
  187. package/dist/transport/telegram/types/rename.js +2 -0
  188. package/dist/transport/telegram/types/rename.js.map +1 -0
  189. package/dist/transport/telegram/types/scheduled-task.js +23 -0
  190. package/dist/transport/telegram/types/scheduled-task.js.map +1 -0
  191. package/dist/transport/telegram/types/session.js +2 -0
  192. package/dist/transport/telegram/types/session.js.map +1 -0
  193. package/dist/transport/telegram/ui.js +28 -0
  194. package/dist/transport/telegram/ui.js.map +1 -0
  195. package/dist/transport/web/index.js +148 -0
  196. package/dist/transport/web/index.js.map +1 -0
  197. package/dist/transport/web/middleware/cf-access.js +104 -0
  198. package/dist/transport/web/middleware/cf-access.js.map +1 -0
  199. package/dist/transport/web/opencode-config.js +15 -0
  200. package/dist/transport/web/opencode-config.js.map +1 -0
  201. package/dist/transport/web/routes/abort.js +12 -0
  202. package/dist/transport/web/routes/abort.js.map +1 -0
  203. package/dist/transport/web/routes/approval.js +16 -0
  204. package/dist/transport/web/routes/approval.js.map +1 -0
  205. package/dist/transport/web/routes/catalog.js +11 -0
  206. package/dist/transport/web/routes/catalog.js.map +1 -0
  207. package/dist/transport/web/routes/channels.js +53 -0
  208. package/dist/transport/web/routes/channels.js.map +1 -0
  209. package/dist/transport/web/routes/commands.js +15 -0
  210. package/dist/transport/web/routes/commands.js.map +1 -0
  211. package/dist/transport/web/routes/context.js +17 -0
  212. package/dist/transport/web/routes/context.js.map +1 -0
  213. package/dist/transport/web/routes/controls.js +34 -0
  214. package/dist/transport/web/routes/controls.js.map +1 -0
  215. package/dist/transport/web/routes/create-session.js +21 -0
  216. package/dist/transport/web/routes/create-session.js.map +1 -0
  217. package/dist/transport/web/routes/diff.js +7 -0
  218. package/dist/transport/web/routes/diff.js.map +1 -0
  219. package/dist/transport/web/routes/files.js +22 -0
  220. package/dist/transport/web/routes/files.js.map +1 -0
  221. package/dist/transport/web/routes/logs.js +9 -0
  222. package/dist/transport/web/routes/logs.js.map +1 -0
  223. package/dist/transport/web/routes/m8.js +69 -0
  224. package/dist/transport/web/routes/m8.js.map +1 -0
  225. package/dist/transport/web/routes/mcp.js +7 -0
  226. package/dist/transport/web/routes/mcp.js.map +1 -0
  227. package/dist/transport/web/routes/message.js +32 -0
  228. package/dist/transport/web/routes/message.js.map +1 -0
  229. package/dist/transport/web/routes/overrides.js +20 -0
  230. package/dist/transport/web/routes/overrides.js.map +1 -0
  231. package/dist/transport/web/routes/question.js +32 -0
  232. package/dist/transport/web/routes/question.js.map +1 -0
  233. package/dist/transport/web/routes/rename.js +12 -0
  234. package/dist/transport/web/routes/rename.js.map +1 -0
  235. package/dist/transport/web/routes/schedules.js +28 -0
  236. package/dist/transport/web/routes/schedules.js.map +1 -0
  237. package/dist/transport/web/routes/session.js +20 -0
  238. package/dist/transport/web/routes/session.js.map +1 -0
  239. package/dist/transport/web/routes/sessions.js +25 -0
  240. package/dist/transport/web/routes/sessions.js.map +1 -0
  241. package/dist/transport/web/routes/subagents.js +11 -0
  242. package/dist/transport/web/routes/subagents.js.map +1 -0
  243. package/dist/transport/web/routes/suggestions.js +7 -0
  244. package/dist/transport/web/routes/suggestions.js.map +1 -0
  245. package/dist/transport/web/routes/todo.js +7 -0
  246. package/dist/transport/web/routes/todo.js.map +1 -0
  247. package/dist/transport/web/routes/version.js +5 -0
  248. package/dist/transport/web/routes/version.js.map +1 -0
  249. package/dist/transport/web/routes/workspaces.js +7 -0
  250. package/dist/transport/web/routes/workspaces.js.map +1 -0
  251. package/dist/transport/web/server.js +111 -0
  252. package/dist/transport/web/server.js.map +1 -0
  253. package/dist/transport/web/session-summary.js +46 -0
  254. package/dist/transport/web/session-summary.js.map +1 -0
  255. package/dist/transport/web/ws-hub.js +83 -0
  256. package/dist/transport/web/ws-hub.js.map +1 -0
  257. package/dist/utils/logger.js +105 -0
  258. package/dist/utils/logger.js.map +1 -0
  259. package/dist/utils/markdown.js +129 -0
  260. package/dist/utils/markdown.js.map +1 -0
  261. package/dist/utils/paths.js +18 -0
  262. package/dist/utils/paths.js.map +1 -0
  263. package/dist/utils/version.js +44 -0
  264. package/dist/utils/version.js.map +1 -0
  265. package/package.json +91 -0
  266. package/web/dist/_app/env.js +1 -0
  267. package/web/dist/_app/immutable/assets/0.DxN3RsuG.css +1 -0
  268. package/web/dist/_app/immutable/assets/2.BNuGfmZP.css +1 -0
  269. package/web/dist/_app/immutable/assets/3.CTDprntu.css +1 -0
  270. package/web/dist/_app/immutable/chunks/BUpwHpMJ.js +1 -0
  271. package/web/dist/_app/immutable/chunks/BjY4dxcB.js +2 -0
  272. package/web/dist/_app/immutable/chunks/C2FuqD3s.js +1 -0
  273. package/web/dist/_app/immutable/chunks/CUHGq08X.js +2 -0
  274. package/web/dist/_app/immutable/chunks/CWj6FrbW.js +1 -0
  275. package/web/dist/_app/immutable/chunks/ClvoqtGj.js +1 -0
  276. package/web/dist/_app/immutable/chunks/DIEDQ_hk.js +1 -0
  277. package/web/dist/_app/immutable/entry/app.BoRpcVMj.js +2 -0
  278. package/web/dist/_app/immutable/entry/start.Cj-PQNTq.js +1 -0
  279. package/web/dist/_app/immutable/nodes/0.y9YEms5p.js +3 -0
  280. package/web/dist/_app/immutable/nodes/1.CPhhiRM2.js +1 -0
  281. package/web/dist/_app/immutable/nodes/2.B_c84Mc9.js +1 -0
  282. package/web/dist/_app/immutable/nodes/3.ClTd0J3D.js +64 -0
  283. package/web/dist/_app/version.json +1 -0
  284. package/web/dist/apple-touch-icon.png +0 -0
  285. package/web/dist/favicon.png +0 -0
  286. package/web/dist/favicon.svg +17 -0
  287. package/web/dist/icon-192.png +0 -0
  288. package/web/dist/icon-512-maskable.png +0 -0
  289. package/web/dist/icon-512.png +0 -0
  290. package/web/dist/index.html +73 -0
  291. package/web/dist/manifest.webmanifest +28 -0
  292. package/web/dist/service-worker.js +1 -0
@@ -0,0 +1,400 @@
1
+ import { createStreamAccumulator } from './stream-accumulator.js';
2
+ import { singleBackendRegistry } from './agent/registry.js';
3
+ import { isEphemeralSession } from '../opencode/submit.js';
4
+ import { createLogger } from '../utils/logger.js';
5
+ const log = createLogger('relay');
6
+ const SUBMIT_MAX_RETRIES = 5;
7
+ const SUBMIT_RETRY_BASE_MS = 2000;
8
+ /** Node/undici transport error codes worth retrying. */
9
+ const NETWORK_ERROR_CODES = new Set([
10
+ 'ECONNREFUSED', 'ECONNRESET', 'ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN',
11
+ 'EPIPE', 'EHOSTUNREACH', 'ENETUNREACH',
12
+ 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT', 'UND_ERR_SOCKET',
13
+ ]);
14
+ function isNetworkError(err) {
15
+ // Prefer structured error codes (undici fetch failures carry them on cause).
16
+ const code = err.code ?? err.cause?.code;
17
+ if (code)
18
+ return NETWORK_ERROR_CODES.has(code);
19
+ // Message fallback, kept narrow — bare 'network'/'timeout' substrings would
20
+ // misclassify ordinary errors (e.g. "operation timeout from the model") and
21
+ // burn 5 retries on a non-transport failure.
22
+ const msg = err.message.toLowerCase();
23
+ return msg.includes('fetch failed') ||
24
+ msg.includes('econnrefused') ||
25
+ msg.includes('econnreset') ||
26
+ msg.includes('enotfound') ||
27
+ msg.includes('etimedout') ||
28
+ msg.includes('socket hang up') ||
29
+ msg.includes('network timeout');
30
+ }
31
+ async function submitWithRetry(backend, sessionId, opts) {
32
+ for (let i = 0; i < SUBMIT_MAX_RETRIES; i++) {
33
+ try {
34
+ await backend.prompt(sessionId, opts);
35
+ return;
36
+ }
37
+ catch (err) {
38
+ const e = err;
39
+ if (opts.signal?.aborted)
40
+ throw e;
41
+ if (i < SUBMIT_MAX_RETRIES - 1 && isNetworkError(e)) {
42
+ const delay = SUBMIT_RETRY_BASE_MS * Math.pow(2, i);
43
+ log.warn(`submit failed (attempt ${i + 1}/${SUBMIT_MAX_RETRIES}), retry in ${delay}ms: ${e.message}`);
44
+ await delayOrAbort(delay, opts.signal);
45
+ }
46
+ else {
47
+ throw e;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ function delayOrAbort(ms, signal) {
53
+ if (!signal || signal.aborted)
54
+ return Promise.resolve();
55
+ return new Promise((resolve, reject) => {
56
+ const t = setTimeout(() => {
57
+ signal.removeEventListener('abort', onAbort);
58
+ resolve();
59
+ }, ms);
60
+ const onAbort = () => { clearTimeout(t); reject(new Error('aborted')); };
61
+ signal.addEventListener('abort', onAbort, { once: true });
62
+ });
63
+ }
64
+ async function pickSessionFallback(backend) {
65
+ const sessions = await backend.listSessions();
66
+ if (sessions.length === 0)
67
+ throw new Error('No opencode sessions found — use /workspaces + /new in Telegram (or the web UI) to start one in a project');
68
+ // Prefer root sessions (no parentID) over child/subagent sessions, then most
69
+ // recently active (updatedAt over createdAt) — avoids connecting to a completed
70
+ // subagent session.
71
+ const sorted = [...sessions].sort((a, b) => {
72
+ const aIsChild = !!a.parentID;
73
+ const bIsChild = !!b.parentID;
74
+ if (aIsChild !== bIsChild)
75
+ return aIsChild ? 1 : -1;
76
+ return (b.updatedAt ?? b.createdAt ?? 0) - (a.updatedAt ?? a.createdAt ?? 0);
77
+ });
78
+ return sorted[0].id;
79
+ }
80
+ export function createRelay(deps) {
81
+ const registry = deps.registry ?? singleBackendRegistry(deps.backend ?? (() => { throw new Error('createRelay needs registry or backend'); })());
82
+ /** Per-session response contexts, keyed by session id. */
83
+ const pluginSessions = new Map();
84
+ /**
85
+ * Per-session turn queues. Two concurrent messages for the same session used
86
+ * to clobber each other (the second cleanupPluginSession killed the first
87
+ * turn's timer/abort/accumulator mid-stream); now a later message waits until
88
+ * the previous turn's ctx finishes (idle / error / abort / timeout).
89
+ */
90
+ const turnQueues = new Map();
91
+ /** Last user text per session — feeds Tier2 follow-up generation at finalize. */
92
+ const lastUserText = new Map();
93
+ function cleanupPluginSession(sessionId) {
94
+ const ctx = pluginSessions.get(sessionId);
95
+ if (ctx) {
96
+ clearTimeout(ctx.timer);
97
+ pluginSessions.delete(sessionId);
98
+ if (ctx.abortKey)
99
+ deps.state.setActiveAbort(ctx.abortKey, undefined);
100
+ ctx.releaseTurn?.();
101
+ }
102
+ }
103
+ /** The session a message will target, normalized — the turn-queue key. */
104
+ function queueKeyFor(msg) {
105
+ const raw = msg.sessionId
106
+ ?? deps.state.getPinnedSessionId()
107
+ ?? (deps.tuiVisible ? deps.state.getTuiSelectedSession() : undefined)
108
+ ?? deps.state.getLastSessionId();
109
+ return raw ? deps.state.normalizeSessionId(raw) : '__default__';
110
+ }
111
+ const relay = async function handleIncoming(msg) {
112
+ // Chain onto the previous turn for this session; errors never block the
113
+ // queue (the gate resolves in a finally) and the map entry is dropped once
114
+ // the chain settles so it can't grow unbounded.
115
+ const key = queueKeyFor(msg);
116
+ const prev = turnQueues.get(key) ?? Promise.resolve();
117
+ let release;
118
+ const gate = new Promise((res) => { release = res; });
119
+ const tail = prev.then(() => gate);
120
+ turnQueues.set(key, tail);
121
+ void tail.then(() => { if (turnQueues.get(key) === tail)
122
+ turnQueues.delete(key); });
123
+ await prev;
124
+ // Released by cleanupPluginSession once a ctx is installed; if the turn
125
+ // ends before that (error path), release here so the queue never wedges.
126
+ let ctxInstalled = false;
127
+ try {
128
+ await runTurn(msg, (ctx) => {
129
+ ctx.releaseTurn = release;
130
+ ctxInstalled = true;
131
+ });
132
+ }
133
+ finally {
134
+ if (!ctxInstalled)
135
+ release();
136
+ }
137
+ };
138
+ async function runTurn(msg, onCtxInstalled) {
139
+ const ac = new AbortController();
140
+ const timer = setTimeout(() => ac.abort(), deps.chatTimeoutMs);
141
+ // Registered under a provisional key until the real session id is known.
142
+ // After resolution the controller stays mapped under BOTH keys until the
143
+ // turn ends (cleanupPluginSession / catch below) so an abort by either id
144
+ // lands — clearing the provisional key at re-registration opened a window
145
+ // where /api/abort with the old id found nothing.
146
+ const provisionalKey = deps.state.getPinnedSessionId() ?? deps.state.getLastSessionId() ?? 'pending';
147
+ let sessionId = provisionalKey;
148
+ deps.state.setActiveAbort(provisionalKey, ac);
149
+ try {
150
+ const nextAgent = deps.state.getNextAgent();
151
+ const nextModel = deps.state.getNextModel();
152
+ const pinnedSession = deps.state.getPinnedSessionId();
153
+ const tuiSession = deps.tuiVisible ? deps.state.getTuiSelectedSession() : undefined;
154
+ const lastSession = deps.state.getLastSessionId();
155
+ // A per-message target (msg.sessionId, set by web to the session the UI is
156
+ // viewing) wins over the global pinned/tui/last fallback, so web and
157
+ // Telegram can drive different sessions at once. Any persisted target may
158
+ // point at a deleted session — validate before submitting, otherwise
159
+ // submitWithRetry burns 5 retries against a 404. A fresh
160
+ // pickSessionFallback() result is trusted as-is.
161
+ // Normalize to full ID so the cardBus / pluginSessions use the same key as
162
+ // the opencode event hook (which always delivers full session IDs).
163
+ let resolvedId = (msg.sessionId ? deps.state.normalizeSessionId(msg.sessionId) : undefined)
164
+ ?? (pinnedSession ? deps.state.normalizeSessionId(pinnedSession) : undefined)
165
+ ?? (tuiSession ? deps.state.normalizeSessionId(tuiSession) : undefined)
166
+ ?? (lastSession ? deps.state.normalizeSessionId(lastSession) : undefined);
167
+ // Pick the backend: a known target routes to its owning backend; a brand-new
168
+ // turn (no resolvable target) goes to the active backend.
169
+ let backend = resolvedId ? registry.forSession(resolvedId) : registry.active();
170
+ if (resolvedId) {
171
+ // Key any error from here on to the target session, not the provisional key.
172
+ sessionId = resolvedId;
173
+ // hasSession throws on transport errors — let it propagate to the catch
174
+ // (error card, turn ends) instead of misrouting to another session.
175
+ if (!(await backend.hasSession(resolvedId))) {
176
+ log.warn(`target session ${resolvedId.slice(-8)} no longer exists, falling back to newest`);
177
+ resolvedId = undefined;
178
+ sessionId = provisionalKey;
179
+ }
180
+ }
181
+ if (!resolvedId) {
182
+ backend = registry.active();
183
+ resolvedId = await pickSessionFallback(backend);
184
+ }
185
+ // Tag ownership so future turns/reads for this session route consistently.
186
+ registry.tag(resolvedId, backend.id);
187
+ log.info(`submitting to session=${resolvedId.slice(-8)} backend=${backend.id}, agent=${nextAgent ?? 'default'}, model=${nextModel ? `${nextModel.providerID}/${nextModel.modelID}` : 'default'}`);
188
+ if (deps.tuiVisible && backend.capabilities.tuiSelect) {
189
+ await backend.selectTuiSession?.(resolvedId, ac.signal);
190
+ }
191
+ await submitWithRetry(backend, resolvedId, {
192
+ text: msg.text,
193
+ agent: nextAgent,
194
+ model: nextModel,
195
+ images: msg.images,
196
+ signal: ac.signal,
197
+ });
198
+ sessionId = resolvedId;
199
+ lastUserText.set(sessionId, msg.text);
200
+ deps.state.setLastSessionId(sessionId);
201
+ deps.state.setActiveAbort(sessionId, ac);
202
+ // Keep the provisional key mapped to the same controller until the turn
203
+ // ends — /api/abort with either id must find it during the whole run.
204
+ // It's cleared in cleanupPluginSession / the catch below.
205
+ // Publish thinking + user cards now that sessionId is known. The user card
206
+ // id is derived from the incoming messageId so a web client's optimistic
207
+ // user card (same id) reconciles in place instead of duplicating.
208
+ deps.cardBus.publish({ kind: 'thinking', sessionId, showStop: true });
209
+ deps.cardBus.publish({ kind: 'user', sessionId, text: msg.text, ts: Date.now(), id: `user:${msg.messageId}`, origin: msg.origin });
210
+ // The response arrives asynchronously via handleEvent() (opencode plugin
211
+ // event hook). Store per-session context and return; finalization happens
212
+ // on session.idle. Clean up the ctx + abort if this run is cancelled.
213
+ cleanupPluginSession(sessionId);
214
+ const ctx = {
215
+ sessionId,
216
+ cardId: `turn:${sessionId}:${Date.now()}`,
217
+ acc: createStreamAccumulator(),
218
+ processedPartIds: new Set(),
219
+ partTextAcc: new Map(),
220
+ signal: ac.signal,
221
+ timer,
222
+ abortKey: sessionId !== provisionalKey ? provisionalKey : undefined,
223
+ };
224
+ onCtxInstalled(ctx);
225
+ pluginSessions.set(sessionId, ctx);
226
+ ac.signal.addEventListener('abort', () => {
227
+ cleanupPluginSession(sessionId);
228
+ deps.state.setActiveAbort(sessionId, undefined);
229
+ }, { once: true });
230
+ return;
231
+ }
232
+ catch (err) {
233
+ const e = err;
234
+ if (ac.signal.aborted) {
235
+ log.info('relay aborted (timeout or user stop) — push will notify on session idle');
236
+ }
237
+ else {
238
+ log.warn('relay error', e.message);
239
+ deps.cardBus.publish({ kind: 'error', sessionId, message: e.message });
240
+ }
241
+ clearTimeout(timer);
242
+ deps.state.setActiveAbort(sessionId, undefined);
243
+ if (sessionId !== provisionalKey)
244
+ deps.state.setActiveAbort(provisionalKey, undefined);
245
+ }
246
+ }
247
+ async function publishAssistantCard(params) {
248
+ const { sessionId, acc, cardId } = params;
249
+ let { assistantMessageId } = params;
250
+ const backend = registry.forSession(sessionId);
251
+ let blocks = acc.finalize();
252
+ if (blocks.length === 0 && assistantMessageId) {
253
+ blocks = await backend.getMessageBlocks(sessionId, assistantMessageId);
254
+ }
255
+ const meta = await backend.getSessionMeta(sessionId);
256
+ if (meta.cost !== undefined)
257
+ deps.state.setSessionCost(sessionId, meta.cost);
258
+ if (blocks.length === 0)
259
+ blocks = [{ type: 'text', text: '(empty response)' }];
260
+ log.info(`relay: publishing assistant card for ${sessionId}, blocks=${blocks.length}`);
261
+ deps.cardBus.publish({ kind: 'assistant', sessionId, blocks, meta, id: cardId });
262
+ // Mark delivery so the push engine doesn't also fire a "Session finished"
263
+ // notification for a session the user just watched complete.
264
+ deps.state.markAssistantDelivered(sessionId);
265
+ // Tier2 suggested follow-ups: generate after the exchange completes, from
266
+ // the last user message + this reply. Fire-and-forget, zero history
267
+ // pollution (runs in a throwaway session), result lands in state for the
268
+ // web client to pick up.
269
+ const userText = lastUserText.get(sessionId);
270
+ lastUserText.delete(sessionId);
271
+ const suggest = backend.suggestFollowUps;
272
+ if (suggest && userText) {
273
+ const assistantText = blocks
274
+ .filter((b) => b.type === 'text')
275
+ .map((b) => b.text ?? '')
276
+ .join('\n')
277
+ .slice(-1500);
278
+ void suggest
279
+ .call(backend, sessionId, { user: userText, assistant: assistantText })
280
+ .then((items) => { if (items.length > 0)
281
+ deps.state.setSessionSuggestions(sessionId, items); })
282
+ .catch(() => { });
283
+ }
284
+ }
285
+ /**
286
+ * handleEvent — opencode plugin event dispatch.
287
+ * Receives individual events from the opencode plugin event hook and drives
288
+ * streaming/finalization for the in-flight session (message.part.updated,
289
+ * message.part.delta, session.idle, session.error).
290
+ */
291
+ relay.handleEvent = async function handleEvent(e) {
292
+ const sid = e.sessionId;
293
+ let ctx = pluginSessions.get(sid);
294
+ // Adopt externally-initiated turns: a streaming event arrives for a session
295
+ // we never submitted to (a TUI/command turn), so there's no context — create
296
+ // one so it streams/renders in Web/Telegram like any other turn. The ctx gets
297
+ // a REAL timeout + abort registration: without them a leaked turn (no idle
298
+ // ever arrives) lived forever, wedged hasActiveGeneration(), and the next
299
+ // turn for the session reused its stale accumulator. Aborting only stops our
300
+ // mirroring — the TUI turn itself is owned by opencode; the card's stop
301
+ // button calls the backend's abort directly.
302
+ // Internal side calls (Tier2 suggestion generation) stream through the
303
+ // same hook; never adopt their turns into user-facing feeds.
304
+ if (isEphemeralSession(sid))
305
+ return;
306
+ if (!ctx && (e.kind === 'part' || e.kind === 'delta')) {
307
+ const ac = new AbortController();
308
+ ctx = {
309
+ sessionId: sid,
310
+ cardId: `turn:${sid}:${Date.now()}`,
311
+ acc: createStreamAccumulator(),
312
+ processedPartIds: new Set(),
313
+ partTextAcc: new Map(),
314
+ signal: ac.signal,
315
+ timer: setTimeout(() => ac.abort(), deps.chatTimeoutMs),
316
+ };
317
+ pluginSessions.set(sid, ctx);
318
+ deps.state.setActiveAbort(sid, ac);
319
+ ac.signal.addEventListener('abort', () => {
320
+ cleanupPluginSession(sid);
321
+ deps.state.setActiveAbort(sid, undefined);
322
+ }, { once: true });
323
+ }
324
+ if (e.kind === 'part') {
325
+ if (!ctx)
326
+ return;
327
+ if (!ctx.assistantMessageId && e.messageId)
328
+ ctx.assistantMessageId = e.messageId;
329
+ const part = e.part;
330
+ const isNewPart = !ctx.processedPartIds.has(part.id);
331
+ if (isNewPart)
332
+ ctx.processedPartIds.add(part.id);
333
+ const input = { id: part.id, type: part.type, text: part.text, tool: part.tool, args: part.args, status: part.status };
334
+ const blocks = ctx.acc.update([input]);
335
+ if (part.type === 'text' && isNewPart && typeof part.text === 'string') {
336
+ ctx.partTextAcc.set(part.id, part.text);
337
+ }
338
+ if (!ctx.signal.aborted) {
339
+ deps.cardBus.publish({ kind: 'streaming', sessionId: sid, blocks, id: ctx.cardId });
340
+ }
341
+ return;
342
+ }
343
+ if (e.kind === 'delta') {
344
+ if (!ctx)
345
+ return;
346
+ const prev = ctx.partTextAcc.get(e.partId) ?? '';
347
+ const fullText = prev + e.text;
348
+ ctx.partTextAcc.set(e.partId, fullText);
349
+ if (!ctx.signal.aborted) {
350
+ const blocks = ctx.acc.update([{ id: e.partId, type: 'text', text: fullText }]);
351
+ deps.cardBus.publish({ kind: 'streaming', sessionId: sid, blocks, id: ctx.cardId });
352
+ }
353
+ if (!ctx.assistantMessageId && e.messageId)
354
+ ctx.assistantMessageId = e.messageId;
355
+ return;
356
+ }
357
+ if (e.kind === 'idle') {
358
+ if (ctx) {
359
+ log.info(`[plugin] session idle: ${sid.slice(-8)}, finalizing`);
360
+ // Capture acc/msgId locally: cleanupPluginSession() runs synchronously
361
+ // below, and a new message for the same session would install a fresh ctx
362
+ // with a fresh accumulator — so this deferred finalize must read the
363
+ // snapshot it captured, not pluginSessions.get(sid).
364
+ const acc = ctx.acc;
365
+ const msgId = ctx.assistantMessageId;
366
+ const cardId = ctx.cardId;
367
+ setTimeout(async () => {
368
+ try {
369
+ await publishAssistantCard({ sessionId: sid, acc, assistantMessageId: msgId, cardId });
370
+ }
371
+ catch (err) {
372
+ log.error(`[plugin] publishAssistantCard failed`, err);
373
+ }
374
+ }, 0);
375
+ cleanupPluginSession(sid);
376
+ deps.state.setActiveAbort(sid, undefined);
377
+ }
378
+ else {
379
+ log.info(`[plugin] session idle (no ctx): ${sid.slice(-8)}`);
380
+ deps.cardBus.publish({ kind: 'status', sessionId: sid, fields: { status: 'idle' } });
381
+ }
382
+ return;
383
+ }
384
+ if (e.kind === 'error') {
385
+ log.warn(`[plugin] session error${ctx ? '' : ' (no ctx)'}: ${e.message}`);
386
+ deps.cardBus.publish({ kind: 'error', sessionId: sid, message: e.message });
387
+ if (ctx) {
388
+ cleanupPluginSession(sid);
389
+ deps.state.setActiveAbort(sid, undefined);
390
+ }
391
+ return;
392
+ }
393
+ if (e.kind === 'notice') {
394
+ deps.cardBus.publish({ kind: 'info', sessionId: sid, title: e.title, sections: [{ body: e.body }] });
395
+ return;
396
+ }
397
+ };
398
+ return relay;
399
+ }
400
+ //# sourceMappingURL=relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.js","sourceRoot":"","sources":["../../src/core/relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ // scheduler.ts — cross-channel scheduled prompts (P2b-M7).
2
+ //
3
+ // The engine is deliberately channel-free: it persists schedules to a JSON
4
+ // file, ticks on an interval, and dispatches due prompts as ordinary
5
+ // IncomingMessages through the relay — so Telegram, Web and any future
6
+ // channel are all just front-ends over the same store.
7
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
8
+ import { dirname } from 'node:path';
9
+ import { createLogger } from '../utils/logger.js';
10
+ const log = createLogger('scheduler');
11
+ const TICK_MS = 30_000;
12
+ /** Validate a spec at the boundary (TG wizard / web API both funnel here). */
13
+ export function normalizeSpec(raw) {
14
+ const r = raw;
15
+ if (!r || typeof r !== 'object')
16
+ return null;
17
+ if (r.kind === 'every') {
18
+ const m = Number(r.minutes);
19
+ if (!Number.isFinite(m) || m <= 0)
20
+ return null;
21
+ return { kind: 'every', minutes: Math.max(1, Math.floor(m)) };
22
+ }
23
+ if (r.kind === 'daily') {
24
+ const t = typeof r.time === 'string' ? r.time : '';
25
+ if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(t))
26
+ return null;
27
+ return { kind: 'daily', time: t };
28
+ }
29
+ return null;
30
+ }
31
+ function isDue(s, now) {
32
+ if (!s.enabled)
33
+ return false;
34
+ if (s.spec.kind === 'every') {
35
+ if (!s.lastRunAt)
36
+ return true; // first tick after creation fires immediately
37
+ return now.getTime() - s.lastRunAt >= s.spec.minutes * 60_000;
38
+ }
39
+ // daily: due when the current HH:MM matches and we haven't run today yet
40
+ const [hh, mm] = s.spec.time.split(':').map(Number);
41
+ const due = now.getHours() === hh && now.getMinutes() >= mm && now.getMinutes() < mm + TICK_MS / 60_000;
42
+ if (!due)
43
+ return false;
44
+ const last = s.lastRunAt ? new Date(s.lastRunAt) : undefined;
45
+ return !(last && last.toDateString() === now.toDateString());
46
+ }
47
+ export function createScheduler(opts) {
48
+ let store = { schedules: [] };
49
+ // ── persistence (atomic tmp+rename, same pattern as state.ts) ──
50
+ function load() {
51
+ if (!existsSync(opts.path))
52
+ return;
53
+ try {
54
+ const raw = JSON.parse(readFileSync(opts.path, 'utf-8'));
55
+ store.schedules = Array.isArray(raw.schedules) ? raw.schedules : [];
56
+ }
57
+ catch (err) {
58
+ log.warn(`failed to load ${opts.path}: ${err.message}`);
59
+ }
60
+ }
61
+ let writeQueued;
62
+ function persist() {
63
+ if (writeQueued)
64
+ clearTimeout(writeQueued);
65
+ writeQueued = setTimeout(() => {
66
+ try {
67
+ mkdirSync(dirname(opts.path), { recursive: true });
68
+ const tmp = `${opts.path}.tmp`;
69
+ writeFileSync(tmp, JSON.stringify({ schedules: store.schedules }, null, 2));
70
+ renameSync(tmp, opts.path);
71
+ }
72
+ catch (err) {
73
+ log.warn(`persist failed: ${err.message}`);
74
+ }
75
+ }, 100);
76
+ writeQueued.unref?.();
77
+ }
78
+ load();
79
+ let timer;
80
+ function tick(now = new Date()) {
81
+ const fired = [];
82
+ for (const s of store.schedules) {
83
+ if (!isDue(s, now))
84
+ continue;
85
+ s.lastRunAt = now.getTime();
86
+ fired.push(s);
87
+ const msg = {
88
+ userId: 'ocrc-scheduler',
89
+ chatId: 'ocrc-scheduler',
90
+ text: s.prompt,
91
+ messageId: `sched_${s.id}_${now.getTime()}`,
92
+ origin: 'scheduler',
93
+ };
94
+ void opts.dispatch(msg).catch((err) => log.error(`dispatch failed for ${s.id}`, err));
95
+ }
96
+ if (fired.length > 0)
97
+ persist();
98
+ return fired;
99
+ }
100
+ return {
101
+ start() {
102
+ if (timer)
103
+ return;
104
+ timer = setInterval(() => tick(), opts.tickMs ?? TICK_MS);
105
+ timer.unref?.();
106
+ },
107
+ stop() {
108
+ if (timer)
109
+ clearInterval(timer);
110
+ timer = undefined;
111
+ },
112
+ tick,
113
+ list: () => [...store.schedules].sort((a, b) => b.createdAt - a.createdAt),
114
+ add(input) {
115
+ const spec = normalizeSpec(input.spec);
116
+ if (!spec || !input.prompt.trim())
117
+ return null;
118
+ const s = {
119
+ id: `sch_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
120
+ name: input.name?.trim() || input.prompt.trim().slice(0, 24),
121
+ prompt: input.prompt.trim(),
122
+ spec,
123
+ enabled: input.enabled ?? true,
124
+ createdAt: Date.now(),
125
+ };
126
+ store.schedules.push(s);
127
+ persist();
128
+ return s;
129
+ },
130
+ remove(id) {
131
+ const before = store.schedules.length;
132
+ store.schedules = store.schedules.filter((s) => s.id !== id);
133
+ const removed = store.schedules.length < before;
134
+ if (removed)
135
+ persist();
136
+ return removed;
137
+ },
138
+ setEnabled(id, enabled) {
139
+ const s = store.schedules.find((x) => x.id === id);
140
+ if (!s)
141
+ return undefined;
142
+ s.enabled = enabled;
143
+ if (!enabled)
144
+ delete s.lastRunAt;
145
+ persist();
146
+ return s;
147
+ },
148
+ };
149
+ }
150
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"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"}