@oar-sh/oar 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 (293) hide show
  1. package/.github/extensions/web-relay/README.md +190 -0
  2. package/.github/extensions/web-relay/extension.mjs +972 -0
  3. package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
  4. package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
  5. package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
  6. package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
  7. package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
  8. package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
  9. package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
  10. package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
  11. package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
  12. package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
  13. package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
  14. package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
  15. package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
  16. package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
  17. package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
  18. package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
  19. package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
  20. package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
  21. package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
  22. package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
  23. package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
  24. package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
  25. package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
  26. package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
  27. package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
  28. package/CHANGELOG.md +25 -0
  29. package/LICENSE +21 -0
  30. package/README.md +717 -0
  31. package/bin/bat/oar.bat +46 -0
  32. package/bin/oar.js +665 -0
  33. package/package.json +68 -0
  34. package/server/README.md +1379 -0
  35. package/server/claude-worker/claude-attachments.mjs +134 -0
  36. package/server/claude-worker/claude-preview-tool.mjs +57 -0
  37. package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
  38. package/server/claude-worker/claude-session-process.mjs +2391 -0
  39. package/server/claude-worker/claude-session-test-harness.mjs +236 -0
  40. package/server/claude-worker/claude-session-worker.mjs +186 -0
  41. package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
  42. package/server/claude-worker/claude-turn-publisher.mjs +291 -0
  43. package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
  44. package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
  45. package/server/claude-worker/workflow-progress-digest.mjs +269 -0
  46. package/server/copilot-sdk-runtime.mjs +126 -0
  47. package/server/copilot-worker/copilot-attachments.mjs +97 -0
  48. package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
  49. package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
  50. package/server/copilot-worker/copilot-plan-board.mjs +110 -0
  51. package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
  52. package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
  53. package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
  54. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
  55. package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
  56. package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
  57. package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
  58. package/server/copilot-worker/fixtures/abort-turn.json +588 -0
  59. package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
  60. package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
  61. package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
  62. package/server/copilot-worker/fixtures/happy-turn.json +566 -0
  63. package/server/copilot-worker/fixtures/quota-turn.json +647 -0
  64. package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
  65. package/server/copilot-worker/fixtures/resume-turn.json +700 -0
  66. package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
  67. package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
  68. package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
  69. package/server/cursor-worker/cursor-attachments.mjs +88 -0
  70. package/server/cursor-worker/cursor-context-usage.mjs +85 -0
  71. package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
  72. package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
  73. package/server/cursor-worker/cursor-session-worker.mjs +145 -0
  74. package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
  75. package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
  76. package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
  77. package/server/db-schema.mjs +932 -0
  78. package/server/features.mjs +106 -0
  79. package/server/grok-worker/acp-client.mjs +434 -0
  80. package/server/grok-worker/acp-host-services.mjs +277 -0
  81. package/server/grok-worker/grok-context-usage.mjs +77 -0
  82. package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
  83. package/server/grok-worker/grok-session-worker.mjs +145 -0
  84. package/server/grok-worker/grok-turn-runner.mjs +523 -0
  85. package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
  86. package/server/migrations/0001-add-structured-answer.mjs +80 -0
  87. package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
  88. package/server/migrations/0003-push-subscriptions.mjs +87 -0
  89. package/server/public/app/action-confirmations.js +267 -0
  90. package/server/public/app/activity-replay-state.mjs +149 -0
  91. package/server/public/app/api-client.js +1082 -0
  92. package/server/public/app/ask-user-view.js +545 -0
  93. package/server/public/app/attachments-view.js +2112 -0
  94. package/server/public/app/auto-compact-window-options.mjs +59 -0
  95. package/server/public/app/background-tasks-view.mjs +494 -0
  96. package/server/public/app/bootstrap.js +4347 -0
  97. package/server/public/app/claude-auth-ui.js +394 -0
  98. package/server/public/app/claude-thinking-options.mjs +38 -0
  99. package/server/public/app/cli-install-ui.js +455 -0
  100. package/server/public/app/code-copy.mjs +85 -0
  101. package/server/public/app/composer-attachment-cache.mjs +151 -0
  102. package/server/public/app/composer-control-state.mjs +107 -0
  103. package/server/public/app/composer-paste.mjs +132 -0
  104. package/server/public/app/context-tier-options.mjs +68 -0
  105. package/server/public/app/context-usage-view.mjs +338 -0
  106. package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
  107. package/server/public/app/conversation-preferences.mjs +91 -0
  108. package/server/public/app/conversation-provider-indicator.mjs +88 -0
  109. package/server/public/app/conversation-view.js +2898 -0
  110. package/server/public/app/cwd-menu-placement.mjs +85 -0
  111. package/server/public/app/cwd-picker.js +605 -0
  112. package/server/public/app/emoji-view.js +101 -0
  113. package/server/public/app/external-link-policy.mjs +80 -0
  114. package/server/public/app/file-preview-navigation.mjs +51 -0
  115. package/server/public/app/font-scaling.js +262 -0
  116. package/server/public/app/git-changes-view.js +339 -0
  117. package/server/public/app/git-diff-model.mjs +109 -0
  118. package/server/public/app/grok-auth-ui.js +387 -0
  119. package/server/public/app/image-downscale.mjs +174 -0
  120. package/server/public/app/in-flight-snapshot.mjs +40 -0
  121. package/server/public/app/infinite-loader.js +164 -0
  122. package/server/public/app/journal-view.js +1141 -0
  123. package/server/public/app/known-cwd-options.mjs +41 -0
  124. package/server/public/app/launch-cwd-path.mjs +23 -0
  125. package/server/public/app/live-message-dedupe.mjs +57 -0
  126. package/server/public/app/message-search-view.js +242 -0
  127. package/server/public/app/model-selector-options.mjs +31 -0
  128. package/server/public/app/model-vision-support.mjs +56 -0
  129. package/server/public/app/new-conversation-model-choice.mjs +67 -0
  130. package/server/public/app/plan-usage-view.mjs +307 -0
  131. package/server/public/app/preview-cards.mjs +273 -0
  132. package/server/public/app/preview-command.mjs +103 -0
  133. package/server/public/app/push-settings.js +370 -0
  134. package/server/public/app/pwa-install.js +456 -0
  135. package/server/public/app/question-schema-view.mjs +71 -0
  136. package/server/public/app/reasoning-effort-labels.mjs +42 -0
  137. package/server/public/app/relay-board-view.js +117 -0
  138. package/server/public/app/relay-dot-state.mjs +69 -0
  139. package/server/public/app/relay-error-ctas.mjs +52 -0
  140. package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
  141. package/server/public/app/relay-thoughts.mjs +98 -0
  142. package/server/public/app/relay-watchdog-policy.mjs +29 -0
  143. package/server/public/app/repo-browser-preferences.mjs +79 -0
  144. package/server/public/app/repo-browser-tree-state.mjs +97 -0
  145. package/server/public/app/router.js +657 -0
  146. package/server/public/app/selection-guard.mjs +93 -0
  147. package/server/public/app/settings-modal.js +1484 -0
  148. package/server/public/app/settings-tabs.js +129 -0
  149. package/server/public/app/slash-autocomplete.mjs +188 -0
  150. package/server/public/app/slash-commands.mjs +183 -0
  151. package/server/public/app/socket-handlers.js +703 -0
  152. package/server/public/app/status-store.mjs +242 -0
  153. package/server/public/app/status-view.mjs +315 -0
  154. package/server/public/app/store.js +1454 -0
  155. package/server/public/app/stream-state.mjs +73 -0
  156. package/server/public/app/streaming-dom-patch.mjs +23 -0
  157. package/server/public/app/sync-outbox.mjs +208 -0
  158. package/server/public/app/tap-actions.js +62 -0
  159. package/server/public/app/thread-order.mjs +54 -0
  160. package/server/public/app/tmux-inspector-view.js +422 -0
  161. package/server/public/app/transcript-separators.mjs +282 -0
  162. package/server/public/app-icon-192.png +0 -0
  163. package/server/public/app-icon-512.png +0 -0
  164. package/server/public/app-icon.svg +4 -0
  165. package/server/public/favicon.ico +0 -0
  166. package/server/public/index.html +5743 -0
  167. package/server/public/manifest.webmanifest +32 -0
  168. package/server/public/sw.js +307 -0
  169. package/server/relay-exit-codes.mjs +4 -0
  170. package/server/relay-self-restart.mjs +170 -0
  171. package/server/relay-tools.md +27 -0
  172. package/server/relay.mjs +1026 -0
  173. package/server/repositories/image-conversation-repository.mjs +317 -0
  174. package/server/repositories/message-repository.mjs +434 -0
  175. package/server/repositories/question-repository.mjs +161 -0
  176. package/server/repositories/session-repository.mjs +320 -0
  177. package/server/repositories/share-visibility-statements.mjs +24 -0
  178. package/server/routes/ask-user-routes.mjs +292 -0
  179. package/server/routes/cache-routes.mjs +44 -0
  180. package/server/routes/git-routes.mjs +92 -0
  181. package/server/routes/messages-routes-test-harness.mjs +111 -0
  182. package/server/routes/messages-routes.mjs +7107 -0
  183. package/server/routes/preview-routes.mjs +108 -0
  184. package/server/routes/push-routes.mjs +59 -0
  185. package/server/routes/relay-board-routes.mjs +268 -0
  186. package/server/routes/sessions-routes.mjs +6150 -0
  187. package/server/server-runtime.mjs +7463 -0
  188. package/server/server.js +24 -0
  189. package/server/services/ask-user-routing-service.mjs +117 -0
  190. package/server/services/cache-rebuild-service.mjs +236 -0
  191. package/server/services/claude-auth-service.mjs +578 -0
  192. package/server/services/claude-context-usage.mjs +240 -0
  193. package/server/services/claude-session-root-service.mjs +228 -0
  194. package/server/services/cli-install-service.mjs +1152 -0
  195. package/server/services/cli-process-runner.mjs +181 -0
  196. package/server/services/cloudflared-tunnel-service.mjs +358 -0
  197. package/server/services/context-snapshot-service.mjs +499 -0
  198. package/server/services/context-usage-view.mjs +160 -0
  199. package/server/services/conversation-preferences-service.mjs +208 -0
  200. package/server/services/cursor-dashboard-usage.mjs +217 -0
  201. package/server/services/cursor-session-root-service.mjs +62 -0
  202. package/server/services/delete-archive-service.mjs +154 -0
  203. package/server/services/drives-path-helpers.mjs +66 -0
  204. package/server/services/fixtures/claude-auth-stub.sh +62 -0
  205. package/server/services/fixtures/cli-install-stub.sh +75 -0
  206. package/server/services/fixtures/grok-stub.sh +86 -0
  207. package/server/services/generated-image-cleanup-service.mjs +114 -0
  208. package/server/services/git-changes-service.mjs +195 -0
  209. package/server/services/github-billing-usage.mjs +133 -0
  210. package/server/services/grok-auth-service.mjs +593 -0
  211. package/server/services/grok-billing-usage.mjs +120 -0
  212. package/server/services/image-context-service.mjs +106 -0
  213. package/server/services/image-operation-service.mjs +135 -0
  214. package/server/services/keyed-mutex-service.mjs +82 -0
  215. package/server/services/mime-sniffer.mjs +118 -0
  216. package/server/services/oar-cli-helpers.mjs +109 -0
  217. package/server/services/oar-state-migration-service.mjs +243 -0
  218. package/server/services/plan-usage-claude.mjs +387 -0
  219. package/server/services/plan-usage-contract.mjs +271 -0
  220. package/server/services/plan-usage-copilot.mjs +421 -0
  221. package/server/services/plan-usage-cursor.mjs +566 -0
  222. package/server/services/plan-usage-grok.mjs +436 -0
  223. package/server/services/plan-usage-service.mjs +487 -0
  224. package/server/services/preview-health-probe.mjs +82 -0
  225. package/server/services/preview-proxy-server.mjs +498 -0
  226. package/server/services/preview-registry-service.mjs +457 -0
  227. package/server/services/preview-static-handler.mjs +237 -0
  228. package/server/services/provider-model-selection.mjs +91 -0
  229. package/server/services/provider-reasoning-effort.mjs +94 -0
  230. package/server/services/push-active-device-service.mjs +57 -0
  231. package/server/services/push-dispatch-service.mjs +459 -0
  232. package/server/services/rate-limit-service.mjs +63 -0
  233. package/server/services/relay-bridge-owner-service.mjs +84 -0
  234. package/server/services/relay-cli-launcher-service.mjs +209 -0
  235. package/server/services/relay-prompt-sanitizer.mjs +137 -0
  236. package/server/services/relay-queue-gate-service.mjs +38 -0
  237. package/server/services/relay-restart-orchestrator-service.mjs +935 -0
  238. package/server/services/relay-singleton-guard.mjs +124 -0
  239. package/server/services/safe-served-content.mjs +57 -0
  240. package/server/services/sdk-session-import-service.mjs +271 -0
  241. package/server/services/sdk-session-sync-service.mjs +207 -0
  242. package/server/services/session-history-refresh-service.mjs +497 -0
  243. package/server/services/session-transcript-service.mjs +419 -0
  244. package/server/services/session-worker-launch-service.mjs +786 -0
  245. package/server/services/session-worker-process-service.mjs +319 -0
  246. package/server/services/session-worker-registry-service.mjs +194 -0
  247. package/server/services/session-worker-stop-service.mjs +141 -0
  248. package/server/services/session-worker-supervisor-service.mjs +898 -0
  249. package/server/services/session-worker-websocket-service.mjs +483 -0
  250. package/server/services/ssh-tunnel-manager-service.mjs +423 -0
  251. package/server/services/status-event-service.mjs +158 -0
  252. package/server/services/tmux-inspector-access-policy.mjs +77 -0
  253. package/server/services/tmux-inspector-socket-service.mjs +191 -0
  254. package/server/services/tmux-inspector-stream-service.mjs +552 -0
  255. package/server/services/tunnel-worker-path-guard.mjs +107 -0
  256. package/server/services/upload-sweep.mjs +60 -0
  257. package/server/services/usage-snapshot-helpers.mjs +209 -0
  258. package/server/services/windows-autostart-service.mjs +115 -0
  259. package/server/services/workspace-root-defaults-service.mjs +34 -0
  260. package/server/services/workspace-root-path-policy.mjs +196 -0
  261. package/server/services/workspace-root-relaunch-service.mjs +132 -0
  262. package/server/services/workspace-symlink-guard.mjs +54 -0
  263. package/server/tools/capture-proxy.mjs +173 -0
  264. package/server/tty-console-bootstrap.mjs +75 -0
  265. package/server/uploads/README.md +36 -0
  266. package/server/uploads/image_input.md +540 -0
  267. package/server/windows-terminal-launcher.mjs +29 -0
  268. package/server/workspace-root.mjs +87 -0
  269. package/shared/ask-user-bridge.mjs +131 -0
  270. package/shared/auto-compact-window.mjs +116 -0
  271. package/shared/background-task-timeout.mjs +52 -0
  272. package/shared/claude-thinking.mjs +101 -0
  273. package/shared/context-window-fallbacks.mjs +148 -0
  274. package/shared/control-poller.mjs +115 -0
  275. package/shared/empty-turn-completion.mjs +20 -0
  276. package/shared/model-catalog-freshness.mjs +11 -0
  277. package/shared/model-descriptors.mjs +118 -0
  278. package/shared/model-id.mjs +147 -0
  279. package/shared/model-refresh.mjs +24 -0
  280. package/shared/openai-reasoning.mjs +13 -0
  281. package/shared/plan-lines.mjs +13 -0
  282. package/shared/preview-instructions.mjs +77 -0
  283. package/shared/preview-tool-core.mjs +191 -0
  284. package/shared/provider-routing.mjs +29 -0
  285. package/shared/question-schema.mjs +315 -0
  286. package/shared/question-timeout.mjs +18 -0
  287. package/shared/stream-emit-gating.mjs +28 -0
  288. package/shared/subagent-run-id.mjs +17 -0
  289. package/shared/thought-cap.mjs +13 -0
  290. package/shared/tool-activity.mjs +98 -0
  291. package/shared/turn-ceiling.mjs +53 -0
  292. package/shared/worker-bootstrap.mjs +44 -0
  293. package/shared/worker-crash-guard.mjs +60 -0
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { isExternallySupervised, isRelayRuntimeInvocation, runDirectRelaySupervisor } from './relay-self-restart.mjs';
5
+
6
+ const scriptPath = fileURLToPath(import.meta.url);
7
+ const runtimeArgs = process.argv.slice(2);
8
+ const interactiveStdio = process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY;
9
+
10
+ // `--relay-runtime` marks the child this script spawned. `--supervised` means an
11
+ // outer supervisor (the CLI extension) owns restarts and needs to observe exit
12
+ // code 75 itself, so the runtime runs in this process instead of a grandchild.
13
+ if (isRelayRuntimeInvocation(runtimeArgs) || isExternallySupervised(runtimeArgs)) {
14
+ await import('./server-runtime.mjs');
15
+ } else {
16
+ await runDirectRelaySupervisor({
17
+ scriptPath,
18
+ args: runtimeArgs,
19
+ cwd: process.cwd(),
20
+ env: process.env,
21
+ stdio: interactiveStdio ? 'inherit' : ['ignore', 'pipe', 'pipe'],
22
+ logger: console,
23
+ });
24
+ }
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ function normalizeId(value) {
4
+ const text = String(value || '').trim();
5
+ return text || null;
6
+ }
7
+
8
+ function loadRelayQuestionColumns(db) {
9
+ try {
10
+ const rows = db.prepare(`PRAGMA table_info(relay_questions)`).all();
11
+ return new Set(rows.map((row) => String(row?.name || '').trim()).filter(Boolean));
12
+ } catch {
13
+ return new Set();
14
+ }
15
+ }
16
+
17
+ export function createAskUserRoutingService(db, options = {}) {
18
+ const continuationRoutingEnabled = options?.continuationRoutingEnabled === true;
19
+ const sessionWorkerRegistry = options?.sessionWorkerRegistry || null;
20
+ const relayQuestionColumns = loadRelayQuestionColumns(db);
21
+ const hasContinuationColumns =
22
+ relayQuestionColumns.has('continuation_id')
23
+ && relayQuestionColumns.has('continuation_question_id');
24
+ const hasStructuredAnswerColumn = relayQuestionColumns.has('structured_answer');
25
+
26
+ const findQuestion = db.prepare(`SELECT * FROM relay_questions WHERE id = ?`);
27
+ const updateAnswered = db.prepare(`
28
+ UPDATE relay_questions
29
+ SET status = 'answered', answer = ?, answered_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
30
+ WHERE id = ?
31
+ `);
32
+ const updateAnsweredStructured = hasStructuredAnswerColumn
33
+ ? db.prepare(`
34
+ UPDATE relay_questions
35
+ SET status = 'answered', answer = ?, structured_answer = ?, answered_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
36
+ WHERE id = ?
37
+ `)
38
+ : null;
39
+ const findBySdkSessionAndMessage = db.prepare(`
40
+ SELECT * FROM relay_questions
41
+ WHERE sdk_session_id = ? AND message_id = ?
42
+ LIMIT 1
43
+ `);
44
+ const findByContinuation = hasContinuationColumns
45
+ ? db.prepare(`
46
+ SELECT *
47
+ FROM relay_questions
48
+ WHERE continuation_id = ?
49
+ AND continuation_question_id = ?
50
+ ORDER BY created_at DESC
51
+ LIMIT 1
52
+ `)
53
+ : null;
54
+
55
+ function routeAnswer({ question_id, sdk_session_id, answer, structured_answer, conversation_id, continuation_id, continuation_question_id }) {
56
+ const row = findQuestion.get(question_id);
57
+ if (!row) return { ok: false, error: 'Question not found' };
58
+
59
+ const rowSession = normalizeId(row.sdk_session_id);
60
+ const incomingSession = normalizeId(sdk_session_id);
61
+ const rowConversation = normalizeId(row.conversation_id);
62
+ const incomingConversation = normalizeId(conversation_id);
63
+ const rowContinuationId = normalizeId(row.continuation_id);
64
+ const incomingContinuationId = normalizeId(continuation_id);
65
+ const rowContinuationQuestionId = normalizeId(row.continuation_question_id);
66
+ const incomingContinuationQuestionId = normalizeId(continuation_question_id);
67
+ const ownerWorkerId = normalizeId(row.owner_worker_id);
68
+ const workerOwnerSessionId = ownerWorkerId
69
+ ? normalizeId(sessionWorkerRegistry?.getWorkerByWorkerId?.(ownerWorkerId)?.sdkSessionId)
70
+ : null;
71
+
72
+ if (continuationRoutingEnabled) {
73
+ if (rowContinuationId && incomingContinuationId && rowContinuationId !== incomingContinuationId) {
74
+ return { ok: false, error: 'continuation mismatch' };
75
+ }
76
+ if (rowContinuationQuestionId && incomingContinuationQuestionId && rowContinuationQuestionId !== incomingContinuationQuestionId) {
77
+ return { ok: false, error: 'continuation question mismatch' };
78
+ }
79
+ if (rowConversation && incomingConversation && rowConversation !== incomingConversation) {
80
+ return { ok: false, error: 'conversation mismatch' };
81
+ }
82
+ if (rowSession && workerOwnerSessionId && rowSession !== workerOwnerSessionId) {
83
+ return { ok: false, error: 'owner mismatch' };
84
+ }
85
+ }
86
+
87
+ const expectedSession = rowSession || workerOwnerSessionId;
88
+ if (expectedSession || incomingSession) {
89
+ if (!expectedSession || !incomingSession || expectedSession !== incomingSession) {
90
+ return { ok: false, error: 'session mismatch' };
91
+ }
92
+ }
93
+
94
+ if (structured_answer && updateAnsweredStructured) {
95
+ updateAnsweredStructured.run(answer, structured_answer, question_id);
96
+ } else {
97
+ updateAnswered.run(answer, question_id);
98
+ }
99
+ return { ok: true };
100
+ }
101
+
102
+ function lookupQuestion({ sdk_session_id, message_id, continuation_id, continuation_question_id }) {
103
+ const continuationId = normalizeId(continuation_id);
104
+ const continuationQuestionId = normalizeId(continuation_question_id);
105
+ if (hasContinuationColumns && continuationId && continuationQuestionId) {
106
+ const continuationMatch = findByContinuation?.get(continuationId, continuationQuestionId) || null;
107
+ if (continuationMatch) return continuationMatch;
108
+ }
109
+
110
+ const sessionId = normalizeId(sdk_session_id);
111
+ const messageId = normalizeId(message_id);
112
+ if (!sessionId || !messageId) return null;
113
+ return findBySdkSessionAndMessage.get(sessionId, messageId) || null;
114
+ }
115
+
116
+ return { routeAnswer, lookupQuestion };
117
+ }
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ function normalizeId(value) {
4
+ return String(value || '').trim();
5
+ }
6
+
7
+ function buildLegacyConversationList(db) {
8
+ return db.prepare(`
9
+ SELECT id, sdk_session_id, archived, status, updated_at
10
+ FROM conversations
11
+ WHERE status != 'deleted'
12
+ AND (sdk_session_id IS NULL OR sdk_session_id = '')
13
+ ORDER BY updated_at DESC, id ASC
14
+ `);
15
+ }
16
+
17
+ function buildBoundConversationList(db) {
18
+ return db.prepare(`
19
+ SELECT id, sdk_session_id
20
+ FROM conversations
21
+ WHERE status != 'deleted'
22
+ AND sdk_session_id IS NOT NULL
23
+ AND sdk_session_id != ''
24
+ ORDER BY updated_at DESC, id ASC
25
+ `);
26
+ }
27
+
28
+ function buildConversationActivityStatements(db) {
29
+ return {
30
+ queueCount: db.prepare(`
31
+ SELECT COUNT(*) AS cnt
32
+ FROM queue
33
+ WHERE conversation_id = ?
34
+ AND status IN ('pending', 'processing', 'parked')
35
+ `),
36
+ runtimeSessionCount: db.prepare(`
37
+ SELECT COUNT(*) AS cnt
38
+ FROM runtime_sessions
39
+ WHERE conversation_id = ?
40
+ `),
41
+ pendingQuestionCount: db.prepare(`
42
+ SELECT COUNT(*) AS cnt
43
+ FROM relay_questions
44
+ WHERE conversation_id = ?
45
+ AND status = 'pending'
46
+ `),
47
+ conversationRows: db.prepare(`
48
+ SELECT id
49
+ FROM conversations
50
+ WHERE status != 'deleted'
51
+ `),
52
+ };
53
+ }
54
+
55
+ export function createCacheRebuildService({
56
+ db,
57
+ stmts,
58
+ io,
59
+ fs,
60
+ path,
61
+ uploadsDir,
62
+ bootstrapRuntimeSessionBindings,
63
+ collectOrphanedUploadsFromConversation,
64
+ deleteOrphanedUploads,
65
+ }) {
66
+ const legacyConversationList = buildLegacyConversationList(db);
67
+ const boundConversationList = buildBoundConversationList(db);
68
+ const activity = buildConversationActivityStatements(db);
69
+
70
+ function isLegacyConversationActive(conversationId) {
71
+ const sid = normalizeId(conversationId);
72
+ if (!sid) return false;
73
+ const queueCount = Number(activity.queueCount.get(sid)?.cnt || 0);
74
+ const runtimeSessionCount = Number(activity.runtimeSessionCount.get(sid)?.cnt || 0);
75
+ const pendingQuestionCount = Number(activity.pendingQuestionCount.get(sid)?.cnt || 0);
76
+ return queueCount > 0 || runtimeSessionCount > 0 || pendingQuestionCount > 0;
77
+ }
78
+
79
+ function purgeConversation(conversationId) {
80
+ const sid = normalizeId(conversationId);
81
+ if (!sid) return [];
82
+ const orphanedUploads = collectOrphanedUploadsFromConversation(sid);
83
+ db.transaction(() => {
84
+ db.prepare(`DELETE FROM relay_questions WHERE conversation_id = ?`).run(sid);
85
+ db.prepare(`DELETE FROM relay_boards WHERE conversation_id = ?`).run(sid);
86
+ db.prepare(`DELETE FROM relay_activity WHERE conversation_id = ?`).run(sid);
87
+ db.prepare(`DELETE FROM relay_stream_events WHERE conversation_id = ?`).run(sid);
88
+ db.prepare(`DELETE FROM relay_thought WHERE conversation_id = ?`).run(sid);
89
+ db.prepare(`DELETE FROM queue WHERE conversation_id = ?`).run(sid);
90
+ db.prepare(`DELETE FROM messages WHERE conversation_id = ?`).run(sid);
91
+ db.prepare(`DELETE FROM runtime_sessions WHERE conversation_id = ?`).run(sid);
92
+ db.prepare(`DELETE FROM conversations WHERE id = ?`).run(sid);
93
+ })();
94
+ deleteOrphanedUploads(orphanedUploads);
95
+ return orphanedUploads;
96
+ }
97
+
98
+ function archiveConversation(conversationId) {
99
+ const sid = normalizeId(conversationId);
100
+ if (!sid) return false;
101
+ const result = db.prepare(`UPDATE conversations SET archived = 1, updated_at = ? WHERE id = ? AND status != 'deleted'`).run(new Date().toISOString(), sid);
102
+ return Number(result?.changes || 0) > 0;
103
+ }
104
+
105
+ function fullWipeCache() {
106
+ const now = new Date().toISOString();
107
+ const conversationIds = activity.conversationRows.all().map((row) => normalizeId(row?.id)).filter(Boolean);
108
+ const manualTitleConversations = db.prepare(`
109
+ SELECT id
110
+ FROM conversations
111
+ WHERE status != 'deleted'
112
+ AND title_source = 'manual'
113
+ `).all().map((row) => normalizeId(row?.id)).filter(Boolean);
114
+ const manualTitleConversationSet = new Set(manualTitleConversations);
115
+ const uploadHashes = [];
116
+ for (const conversationId of conversationIds) {
117
+ for (const hash of collectOrphanedUploadsFromConversation(conversationId)) {
118
+ if (!uploadHashes.includes(hash)) uploadHashes.push(hash);
119
+ }
120
+ }
121
+
122
+ db.transaction(() => {
123
+ db.prepare(`DELETE FROM relay_questions`).run();
124
+ db.prepare(`DELETE FROM relay_boards`).run();
125
+ db.prepare(`DELETE FROM relay_activity`).run();
126
+ db.prepare(`DELETE FROM relay_stream_events`).run();
127
+ db.prepare(`DELETE FROM relay_thought`).run();
128
+ db.prepare(`DELETE FROM queue`).run();
129
+ db.prepare(`DELETE FROM messages`).run();
130
+ db.prepare(`DELETE FROM runtime_sessions`).run();
131
+ db.prepare(`DELETE FROM conversations WHERE status = 'deleted' OR title_source != 'manual'`).run();
132
+ for (const conversationId of manualTitleConversations) {
133
+ db.prepare(`
134
+ UPDATE conversations
135
+ SET archived = 0,
136
+ status = 'active',
137
+ compacted_into = NULL,
138
+ compacted_from = NULL,
139
+ summary_seed = NULL,
140
+ seed_pending = 0,
141
+ updated_at = ?
142
+ WHERE id = ?
143
+ `).run(now, conversationId);
144
+ }
145
+ db.prepare(`DELETE FROM deleted_sdk_sessions`).run();
146
+ db.prepare(`DELETE FROM sdk_delete_requests`).run();
147
+ db.prepare(`DELETE FROM sdk_session_imports`).run();
148
+ db.prepare(`DELETE FROM upload_refs`).run();
149
+ db.prepare(`DELETE FROM uploaded_files`).run();
150
+ })();
151
+
152
+ deleteOrphanedUploads(uploadHashes);
153
+
154
+ if (uploadsDir && fs?.existsSync?.(uploadsDir)) {
155
+ try {
156
+ for (const entry of fs.readdirSync(uploadsDir, { withFileTypes: true })) {
157
+ const entryPath = path.join(uploadsDir, entry.name);
158
+ fs.rmSync(entryPath, { recursive: true, force: true });
159
+ }
160
+ } catch {}
161
+ try {
162
+ fs.mkdirSync(uploadsDir, { recursive: true });
163
+ } catch {}
164
+ }
165
+
166
+ return {
167
+ wipedAt: now,
168
+ conversationsRemoved: conversationIds.length - manualTitleConversations.length,
169
+ manualTitleConversationsRetained: manualTitleConversationSet.size,
170
+ uploadsRemoved: uploadHashes.length,
171
+ };
172
+ }
173
+
174
+ function reconcileCache() {
175
+ const legacyRows = legacyConversationList.all();
176
+ const summary = {
177
+ purgedConversationIds: [],
178
+ archivedConversationIds: [],
179
+ retainedConversationIds: [],
180
+ runtimeSessionsBootstrapped: 0,
181
+ uploadsRemoved: 0,
182
+ };
183
+
184
+ for (const row of legacyRows) {
185
+ const conversationId = normalizeId(row?.id);
186
+ if (!conversationId) continue;
187
+
188
+ if (isLegacyConversationActive(conversationId)) {
189
+ if (archiveConversation(conversationId)) {
190
+ summary.archivedConversationIds.push(conversationId);
191
+ } else {
192
+ summary.retainedConversationIds.push(conversationId);
193
+ }
194
+ continue;
195
+ }
196
+
197
+ purgeConversation(conversationId);
198
+ summary.purgedConversationIds.push(conversationId);
199
+ }
200
+
201
+ summary.runtimeSessionsBootstrapped = bootstrapRuntimeSessionBindings();
202
+ return summary;
203
+ }
204
+
205
+ function rebuildCache({ mode = 'reconcile' } = {}) {
206
+ const normalizedMode = String(mode || 'reconcile').trim().toLowerCase();
207
+ if (normalizedMode === 'full' || normalizedMode === 'wipe' || normalizedMode === 'rebuild') {
208
+ return {
209
+ mode: 'full',
210
+ summary: fullWipeCache(),
211
+ };
212
+ }
213
+
214
+ if (normalizedMode !== 'reconcile') {
215
+ const error = new Error(`Unsupported cache rebuild mode: ${normalizedMode || 'empty'}`);
216
+ error.statusCode = 400;
217
+ throw error;
218
+ }
219
+
220
+ return {
221
+ mode: 'reconcile',
222
+ summary: reconcileCache(),
223
+ };
224
+ }
225
+
226
+ return {
227
+ rebuildCache,
228
+ reconcileCache,
229
+ fullWipeCache,
230
+ purgeConversation,
231
+ archiveConversation,
232
+ isLegacyConversationActive,
233
+ listLegacyConversations: () => legacyConversationList.all(),
234
+ listBoundConversations: () => boundConversationList.all(),
235
+ };
236
+ }