@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.
- package/.github/extensions/web-relay/README.md +190 -0
- package/.github/extensions/web-relay/extension.mjs +972 -0
- package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
- package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
- package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
- package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
- package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
- package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
- package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
- package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
- package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
- package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
- package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
- package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
- package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
- package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
- package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
- package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
- package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
- package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
- package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
- package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
- package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
- package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
- package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +717 -0
- package/bin/bat/oar.bat +46 -0
- package/bin/oar.js +665 -0
- package/package.json +68 -0
- package/server/README.md +1379 -0
- package/server/claude-worker/claude-attachments.mjs +134 -0
- package/server/claude-worker/claude-preview-tool.mjs +57 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
- package/server/claude-worker/claude-session-process.mjs +2391 -0
- package/server/claude-worker/claude-session-test-harness.mjs +236 -0
- package/server/claude-worker/claude-session-worker.mjs +186 -0
- package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
- package/server/claude-worker/claude-turn-publisher.mjs +291 -0
- package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
- package/server/claude-worker/workflow-progress-digest.mjs +269 -0
- package/server/copilot-sdk-runtime.mjs +126 -0
- package/server/copilot-worker/copilot-attachments.mjs +97 -0
- package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
- package/server/copilot-worker/copilot-plan-board.mjs +110 -0
- package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
- package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
- package/server/copilot-worker/fixtures/abort-turn.json +588 -0
- package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
- package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
- package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
- package/server/copilot-worker/fixtures/happy-turn.json +566 -0
- package/server/copilot-worker/fixtures/quota-turn.json +647 -0
- package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
- package/server/copilot-worker/fixtures/resume-turn.json +700 -0
- package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
- package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
- package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
- package/server/cursor-worker/cursor-attachments.mjs +88 -0
- package/server/cursor-worker/cursor-context-usage.mjs +85 -0
- package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
- package/server/cursor-worker/cursor-session-worker.mjs +145 -0
- package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
- package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
- package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
- package/server/db-schema.mjs +932 -0
- package/server/features.mjs +106 -0
- package/server/grok-worker/acp-client.mjs +434 -0
- package/server/grok-worker/acp-host-services.mjs +277 -0
- package/server/grok-worker/grok-context-usage.mjs +77 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
- package/server/grok-worker/grok-session-worker.mjs +145 -0
- package/server/grok-worker/grok-turn-runner.mjs +523 -0
- package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
- package/server/migrations/0001-add-structured-answer.mjs +80 -0
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
- package/server/migrations/0003-push-subscriptions.mjs +87 -0
- package/server/public/app/action-confirmations.js +267 -0
- package/server/public/app/activity-replay-state.mjs +149 -0
- package/server/public/app/api-client.js +1082 -0
- package/server/public/app/ask-user-view.js +545 -0
- package/server/public/app/attachments-view.js +2112 -0
- package/server/public/app/auto-compact-window-options.mjs +59 -0
- package/server/public/app/background-tasks-view.mjs +494 -0
- package/server/public/app/bootstrap.js +4347 -0
- package/server/public/app/claude-auth-ui.js +394 -0
- package/server/public/app/claude-thinking-options.mjs +38 -0
- package/server/public/app/cli-install-ui.js +455 -0
- package/server/public/app/code-copy.mjs +85 -0
- package/server/public/app/composer-attachment-cache.mjs +151 -0
- package/server/public/app/composer-control-state.mjs +107 -0
- package/server/public/app/composer-paste.mjs +132 -0
- package/server/public/app/context-tier-options.mjs +68 -0
- package/server/public/app/context-usage-view.mjs +338 -0
- package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
- package/server/public/app/conversation-preferences.mjs +91 -0
- package/server/public/app/conversation-provider-indicator.mjs +88 -0
- package/server/public/app/conversation-view.js +2898 -0
- package/server/public/app/cwd-menu-placement.mjs +85 -0
- package/server/public/app/cwd-picker.js +605 -0
- package/server/public/app/emoji-view.js +101 -0
- package/server/public/app/external-link-policy.mjs +80 -0
- package/server/public/app/file-preview-navigation.mjs +51 -0
- package/server/public/app/font-scaling.js +262 -0
- package/server/public/app/git-changes-view.js +339 -0
- package/server/public/app/git-diff-model.mjs +109 -0
- package/server/public/app/grok-auth-ui.js +387 -0
- package/server/public/app/image-downscale.mjs +174 -0
- package/server/public/app/in-flight-snapshot.mjs +40 -0
- package/server/public/app/infinite-loader.js +164 -0
- package/server/public/app/journal-view.js +1141 -0
- package/server/public/app/known-cwd-options.mjs +41 -0
- package/server/public/app/launch-cwd-path.mjs +23 -0
- package/server/public/app/live-message-dedupe.mjs +57 -0
- package/server/public/app/message-search-view.js +242 -0
- package/server/public/app/model-selector-options.mjs +31 -0
- package/server/public/app/model-vision-support.mjs +56 -0
- package/server/public/app/new-conversation-model-choice.mjs +67 -0
- package/server/public/app/plan-usage-view.mjs +307 -0
- package/server/public/app/preview-cards.mjs +273 -0
- package/server/public/app/preview-command.mjs +103 -0
- package/server/public/app/push-settings.js +370 -0
- package/server/public/app/pwa-install.js +456 -0
- package/server/public/app/question-schema-view.mjs +71 -0
- package/server/public/app/reasoning-effort-labels.mjs +42 -0
- package/server/public/app/relay-board-view.js +117 -0
- package/server/public/app/relay-dot-state.mjs +69 -0
- package/server/public/app/relay-error-ctas.mjs +52 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
- package/server/public/app/relay-thoughts.mjs +98 -0
- package/server/public/app/relay-watchdog-policy.mjs +29 -0
- package/server/public/app/repo-browser-preferences.mjs +79 -0
- package/server/public/app/repo-browser-tree-state.mjs +97 -0
- package/server/public/app/router.js +657 -0
- package/server/public/app/selection-guard.mjs +93 -0
- package/server/public/app/settings-modal.js +1484 -0
- package/server/public/app/settings-tabs.js +129 -0
- package/server/public/app/slash-autocomplete.mjs +188 -0
- package/server/public/app/slash-commands.mjs +183 -0
- package/server/public/app/socket-handlers.js +703 -0
- package/server/public/app/status-store.mjs +242 -0
- package/server/public/app/status-view.mjs +315 -0
- package/server/public/app/store.js +1454 -0
- package/server/public/app/stream-state.mjs +73 -0
- package/server/public/app/streaming-dom-patch.mjs +23 -0
- package/server/public/app/sync-outbox.mjs +208 -0
- package/server/public/app/tap-actions.js +62 -0
- package/server/public/app/thread-order.mjs +54 -0
- package/server/public/app/tmux-inspector-view.js +422 -0
- package/server/public/app/transcript-separators.mjs +282 -0
- package/server/public/app-icon-192.png +0 -0
- package/server/public/app-icon-512.png +0 -0
- package/server/public/app-icon.svg +4 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/index.html +5743 -0
- package/server/public/manifest.webmanifest +32 -0
- package/server/public/sw.js +307 -0
- package/server/relay-exit-codes.mjs +4 -0
- package/server/relay-self-restart.mjs +170 -0
- package/server/relay-tools.md +27 -0
- package/server/relay.mjs +1026 -0
- package/server/repositories/image-conversation-repository.mjs +317 -0
- package/server/repositories/message-repository.mjs +434 -0
- package/server/repositories/question-repository.mjs +161 -0
- package/server/repositories/session-repository.mjs +320 -0
- package/server/repositories/share-visibility-statements.mjs +24 -0
- package/server/routes/ask-user-routes.mjs +292 -0
- package/server/routes/cache-routes.mjs +44 -0
- package/server/routes/git-routes.mjs +92 -0
- package/server/routes/messages-routes-test-harness.mjs +111 -0
- package/server/routes/messages-routes.mjs +7107 -0
- package/server/routes/preview-routes.mjs +108 -0
- package/server/routes/push-routes.mjs +59 -0
- package/server/routes/relay-board-routes.mjs +268 -0
- package/server/routes/sessions-routes.mjs +6150 -0
- package/server/server-runtime.mjs +7463 -0
- package/server/server.js +24 -0
- package/server/services/ask-user-routing-service.mjs +117 -0
- package/server/services/cache-rebuild-service.mjs +236 -0
- package/server/services/claude-auth-service.mjs +578 -0
- package/server/services/claude-context-usage.mjs +240 -0
- package/server/services/claude-session-root-service.mjs +228 -0
- package/server/services/cli-install-service.mjs +1152 -0
- package/server/services/cli-process-runner.mjs +181 -0
- package/server/services/cloudflared-tunnel-service.mjs +358 -0
- package/server/services/context-snapshot-service.mjs +499 -0
- package/server/services/context-usage-view.mjs +160 -0
- package/server/services/conversation-preferences-service.mjs +208 -0
- package/server/services/cursor-dashboard-usage.mjs +217 -0
- package/server/services/cursor-session-root-service.mjs +62 -0
- package/server/services/delete-archive-service.mjs +154 -0
- package/server/services/drives-path-helpers.mjs +66 -0
- package/server/services/fixtures/claude-auth-stub.sh +62 -0
- package/server/services/fixtures/cli-install-stub.sh +75 -0
- package/server/services/fixtures/grok-stub.sh +86 -0
- package/server/services/generated-image-cleanup-service.mjs +114 -0
- package/server/services/git-changes-service.mjs +195 -0
- package/server/services/github-billing-usage.mjs +133 -0
- package/server/services/grok-auth-service.mjs +593 -0
- package/server/services/grok-billing-usage.mjs +120 -0
- package/server/services/image-context-service.mjs +106 -0
- package/server/services/image-operation-service.mjs +135 -0
- package/server/services/keyed-mutex-service.mjs +82 -0
- package/server/services/mime-sniffer.mjs +118 -0
- package/server/services/oar-cli-helpers.mjs +109 -0
- package/server/services/oar-state-migration-service.mjs +243 -0
- package/server/services/plan-usage-claude.mjs +387 -0
- package/server/services/plan-usage-contract.mjs +271 -0
- package/server/services/plan-usage-copilot.mjs +421 -0
- package/server/services/plan-usage-cursor.mjs +566 -0
- package/server/services/plan-usage-grok.mjs +436 -0
- package/server/services/plan-usage-service.mjs +487 -0
- package/server/services/preview-health-probe.mjs +82 -0
- package/server/services/preview-proxy-server.mjs +498 -0
- package/server/services/preview-registry-service.mjs +457 -0
- package/server/services/preview-static-handler.mjs +237 -0
- package/server/services/provider-model-selection.mjs +91 -0
- package/server/services/provider-reasoning-effort.mjs +94 -0
- package/server/services/push-active-device-service.mjs +57 -0
- package/server/services/push-dispatch-service.mjs +459 -0
- package/server/services/rate-limit-service.mjs +63 -0
- package/server/services/relay-bridge-owner-service.mjs +84 -0
- package/server/services/relay-cli-launcher-service.mjs +209 -0
- package/server/services/relay-prompt-sanitizer.mjs +137 -0
- package/server/services/relay-queue-gate-service.mjs +38 -0
- package/server/services/relay-restart-orchestrator-service.mjs +935 -0
- package/server/services/relay-singleton-guard.mjs +124 -0
- package/server/services/safe-served-content.mjs +57 -0
- package/server/services/sdk-session-import-service.mjs +271 -0
- package/server/services/sdk-session-sync-service.mjs +207 -0
- package/server/services/session-history-refresh-service.mjs +497 -0
- package/server/services/session-transcript-service.mjs +419 -0
- package/server/services/session-worker-launch-service.mjs +786 -0
- package/server/services/session-worker-process-service.mjs +319 -0
- package/server/services/session-worker-registry-service.mjs +194 -0
- package/server/services/session-worker-stop-service.mjs +141 -0
- package/server/services/session-worker-supervisor-service.mjs +898 -0
- package/server/services/session-worker-websocket-service.mjs +483 -0
- package/server/services/ssh-tunnel-manager-service.mjs +423 -0
- package/server/services/status-event-service.mjs +158 -0
- package/server/services/tmux-inspector-access-policy.mjs +77 -0
- package/server/services/tmux-inspector-socket-service.mjs +191 -0
- package/server/services/tmux-inspector-stream-service.mjs +552 -0
- package/server/services/tunnel-worker-path-guard.mjs +107 -0
- package/server/services/upload-sweep.mjs +60 -0
- package/server/services/usage-snapshot-helpers.mjs +209 -0
- package/server/services/windows-autostart-service.mjs +115 -0
- package/server/services/workspace-root-defaults-service.mjs +34 -0
- package/server/services/workspace-root-path-policy.mjs +196 -0
- package/server/services/workspace-root-relaunch-service.mjs +132 -0
- package/server/services/workspace-symlink-guard.mjs +54 -0
- package/server/tools/capture-proxy.mjs +173 -0
- package/server/tty-console-bootstrap.mjs +75 -0
- package/server/uploads/README.md +36 -0
- package/server/uploads/image_input.md +540 -0
- package/server/windows-terminal-launcher.mjs +29 -0
- package/server/workspace-root.mjs +87 -0
- package/shared/ask-user-bridge.mjs +131 -0
- package/shared/auto-compact-window.mjs +116 -0
- package/shared/background-task-timeout.mjs +52 -0
- package/shared/claude-thinking.mjs +101 -0
- package/shared/context-window-fallbacks.mjs +148 -0
- package/shared/control-poller.mjs +115 -0
- package/shared/empty-turn-completion.mjs +20 -0
- package/shared/model-catalog-freshness.mjs +11 -0
- package/shared/model-descriptors.mjs +118 -0
- package/shared/model-id.mjs +147 -0
- package/shared/model-refresh.mjs +24 -0
- package/shared/openai-reasoning.mjs +13 -0
- package/shared/plan-lines.mjs +13 -0
- package/shared/preview-instructions.mjs +77 -0
- package/shared/preview-tool-core.mjs +191 -0
- package/shared/provider-routing.mjs +29 -0
- package/shared/question-schema.mjs +315 -0
- package/shared/question-timeout.mjs +18 -0
- package/shared/stream-emit-gating.mjs +28 -0
- package/shared/subagent-run-id.mjs +17 -0
- package/shared/thought-cap.mjs +13 -0
- package/shared/tool-activity.mjs +98 -0
- package/shared/turn-ceiling.mjs +53 -0
- package/shared/worker-bootstrap.mjs +44 -0
- package/shared/worker-crash-guard.mjs +60 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { createShareVisibilityStatements } from './share-visibility-statements.mjs';
|
|
4
|
+
import { sessionWorkerProviderSqlList } from '../../shared/provider-routing.mjs';
|
|
5
|
+
|
|
6
|
+
export function createMessageRepository(db) {
|
|
7
|
+
const shareVisibility = createShareVisibilityStatements(db);
|
|
8
|
+
const queueHasImageOperationId = db.prepare(`PRAGMA table_info(queue)`).all()
|
|
9
|
+
.some((column) => column.name === 'image_operation_id');
|
|
10
|
+
const insertQueueSql = queueHasImageOperationId
|
|
11
|
+
? `INSERT INTO queue (id, conversation_id, runtime_session_id, is_new_conversation, model, model_variant_id, reasoning_effort, context_tier, relay_mode, text, attachments, status, timestamp, retry_count, next_attempt_at, owner_sdk_session_id, owner_assigned_at, owner_lease_expires_at, owner_last_claimed_at, image_operation_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, 0, NULL, ?, ?, ?, ?, ?)`
|
|
12
|
+
: `INSERT INTO queue (id, conversation_id, runtime_session_id, is_new_conversation, model, model_variant_id, reasoning_effort, context_tier, relay_mode, text, attachments, status, timestamp, retry_count, next_attempt_at, owner_sdk_session_id, owner_assigned_at, owner_lease_expires_at, owner_last_claimed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, 0, NULL, ?, ?, ?, ?)`;
|
|
13
|
+
return {
|
|
14
|
+
// messages
|
|
15
|
+
getMessages: db.prepare(`SELECT * FROM messages WHERE conversation_id = ? ORDER BY timestamp ASC`),
|
|
16
|
+
getSharedMessages: shareVisibility.getSharedMessages,
|
|
17
|
+
getMessageByConversation: db.prepare(`SELECT * FROM messages WHERE id = ? AND conversation_id = ? LIMIT 1`),
|
|
18
|
+
setMessageShareVisibility: shareVisibility.setMessageShareVisibility,
|
|
19
|
+
getLatestConversationModel: db.prepare(`SELECT model FROM messages WHERE conversation_id = ? AND model IS NOT NULL AND model != '' ORDER BY timestamp DESC LIMIT 1`),
|
|
20
|
+
getRecentMessagesDesc: db.prepare(`SELECT role, text, timestamp FROM messages WHERE conversation_id = ? ORDER BY timestamp DESC LIMIT ?`),
|
|
21
|
+
insertMsg: db.prepare(`INSERT INTO messages (id, conversation_id, role, text, model, mode, attachments, timestamp, model_requested, model_actual, model_origin) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
22
|
+
// Provenance is derived from the authenticated responder identity at
|
|
23
|
+
// /api/response time, never from the response payload.
|
|
24
|
+
setMessageExecutedProvider: db.prepare(`UPDATE messages SET executed_provider = ? WHERE id = ?`),
|
|
25
|
+
searchMessagesCount: db.prepare(`
|
|
26
|
+
SELECT COUNT(*) AS cnt
|
|
27
|
+
FROM messages_fts fts
|
|
28
|
+
INNER JOIN messages m
|
|
29
|
+
ON m.rowid = fts.rowid
|
|
30
|
+
INNER JOIN conversations c
|
|
31
|
+
ON c.id = m.conversation_id
|
|
32
|
+
WHERE fts.text MATCH ?
|
|
33
|
+
AND c.status != 'deleted'
|
|
34
|
+
`),
|
|
35
|
+
searchMessagesPage: db.prepare(`
|
|
36
|
+
SELECT
|
|
37
|
+
m.id AS message_id,
|
|
38
|
+
m.conversation_id AS conversation_id,
|
|
39
|
+
c.title AS conversation_title,
|
|
40
|
+
m.role AS role,
|
|
41
|
+
m.timestamp AS timestamp,
|
|
42
|
+
m.text AS raw_text,
|
|
43
|
+
snippet(messages_fts, 0, '<mark>', '</mark>', '…', 18) AS snippet,
|
|
44
|
+
bm25(messages_fts) AS score
|
|
45
|
+
FROM messages_fts fts
|
|
46
|
+
INNER JOIN messages m
|
|
47
|
+
ON m.rowid = fts.rowid
|
|
48
|
+
INNER JOIN conversations c
|
|
49
|
+
ON c.id = m.conversation_id
|
|
50
|
+
WHERE fts.text MATCH ?
|
|
51
|
+
AND c.status != 'deleted'
|
|
52
|
+
ORDER BY score ASC, m.timestamp DESC, m.id DESC
|
|
53
|
+
LIMIT ? OFFSET ?
|
|
54
|
+
`),
|
|
55
|
+
upsertMessageUsageSnapshot: db.prepare(`
|
|
56
|
+
INSERT INTO message_usage_snapshots (
|
|
57
|
+
response_message_id,
|
|
58
|
+
queue_message_id,
|
|
59
|
+
conversation_id,
|
|
60
|
+
source,
|
|
61
|
+
stale,
|
|
62
|
+
premium_remaining,
|
|
63
|
+
premium_entitlement,
|
|
64
|
+
premium_used_percent,
|
|
65
|
+
premium_delta_used,
|
|
66
|
+
chat_remaining,
|
|
67
|
+
chat_entitlement,
|
|
68
|
+
chat_used_percent,
|
|
69
|
+
chat_delta_used,
|
|
70
|
+
plan_remaining,
|
|
71
|
+
plan_entitlement,
|
|
72
|
+
plan_used_percent,
|
|
73
|
+
plan_delta_used,
|
|
74
|
+
captured_at
|
|
75
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
+
ON CONFLICT(response_message_id) DO UPDATE SET
|
|
77
|
+
queue_message_id = excluded.queue_message_id,
|
|
78
|
+
conversation_id = excluded.conversation_id,
|
|
79
|
+
source = excluded.source,
|
|
80
|
+
stale = excluded.stale,
|
|
81
|
+
premium_remaining = excluded.premium_remaining,
|
|
82
|
+
premium_entitlement = excluded.premium_entitlement,
|
|
83
|
+
premium_used_percent = excluded.premium_used_percent,
|
|
84
|
+
premium_delta_used = excluded.premium_delta_used,
|
|
85
|
+
chat_remaining = excluded.chat_remaining,
|
|
86
|
+
chat_entitlement = excluded.chat_entitlement,
|
|
87
|
+
chat_used_percent = excluded.chat_used_percent,
|
|
88
|
+
chat_delta_used = excluded.chat_delta_used,
|
|
89
|
+
plan_remaining = excluded.plan_remaining,
|
|
90
|
+
plan_entitlement = excluded.plan_entitlement,
|
|
91
|
+
plan_used_percent = excluded.plan_used_percent,
|
|
92
|
+
plan_delta_used = excluded.plan_delta_used,
|
|
93
|
+
captured_at = excluded.captured_at
|
|
94
|
+
`),
|
|
95
|
+
getLatestMessageUsageSnapshotByConversation: db.prepare(`
|
|
96
|
+
SELECT *
|
|
97
|
+
FROM message_usage_snapshots
|
|
98
|
+
WHERE conversation_id = ?
|
|
99
|
+
ORDER BY captured_at DESC
|
|
100
|
+
LIMIT 1
|
|
101
|
+
`),
|
|
102
|
+
listMessageUsageSnapshotsByConversation: db.prepare(`
|
|
103
|
+
SELECT *
|
|
104
|
+
FROM message_usage_snapshots
|
|
105
|
+
WHERE conversation_id = ?
|
|
106
|
+
ORDER BY captured_at ASC
|
|
107
|
+
`),
|
|
108
|
+
|
|
109
|
+
// queue
|
|
110
|
+
insertQ: db.prepare(insertQueueSql),
|
|
111
|
+
queueHasImageOperationId,
|
|
112
|
+
// A background-continuation turn: born 'processing' and owned by its
|
|
113
|
+
// session worker — the CLI already started it on its own, so it must
|
|
114
|
+
// never be handed out as deliverable work.
|
|
115
|
+
insertContinuationQ: db.prepare(`INSERT INTO queue (id, conversation_id, runtime_session_id, is_new_conversation, model, relay_mode, text, status, kind, timestamp, processing_at, retry_count, owner_sdk_session_id, owner_assigned_at, owner_lease_expires_at, owner_last_claimed_at) VALUES (?, ?, ?, 0, ?, ?, ?, 'processing', 'continuation', ?, ?, 0, ?, ?, ?, ?)`),
|
|
116
|
+
setMessageKind: db.prepare(`UPDATE messages SET kind = ? WHERE id = ?`),
|
|
117
|
+
// Quiet teardown for a continuation whose worker died: there is no
|
|
118
|
+
// user to answer, so it fails without the terminal-failure ceremony.
|
|
119
|
+
dropStaleContinuation: db.prepare(`UPDATE queue SET status = 'failed', processing_at = NULL, next_attempt_at = NULL, owner_lease_expires_at = NULL WHERE id = ? AND status = 'processing' AND kind = 'continuation'`),
|
|
120
|
+
findPending: db.prepare(`SELECT * FROM queue WHERE status = 'pending' AND COALESCE(kind, '') != 'continuation' AND (next_attempt_at IS NULL OR next_attempt_at <= ?) ORDER BY retry_count ASC, CASE WHEN next_attempt_at IS NULL THEN 0 ELSE 1 END ASC, COALESCE(next_attempt_at, timestamp) ASC, timestamp ASC LIMIT 1`),
|
|
121
|
+
// Global fallback for requesters without a session identity (the legacy
|
|
122
|
+
// Copilot relay CLI). Conversations bound to a session-worker provider
|
|
123
|
+
// (claude/cursor/grok) are excluded outright: handing one of their turns
|
|
124
|
+
// to the relay executes it on the Copilot plan under whatever model the
|
|
125
|
+
// id happens to resolve to there, invisibly to the user.
|
|
126
|
+
//
|
|
127
|
+
// Owned rows are excluded too. An owner means a session worker is
|
|
128
|
+
// responsible for the turn, and the relay polls every 2s while a worker
|
|
129
|
+
// takes ~a minute to become ready — so without this the relay wins that
|
|
130
|
+
// race and fails a turn it cannot run (an OpenAI image model is not a
|
|
131
|
+
// Copilot model), costing a retry and a backoff. Owners are only
|
|
132
|
+
// assigned when session-worker routing is enabled
|
|
133
|
+
// (resolveInitialQueueOwnerSessionId returns null otherwise), so
|
|
134
|
+
// routing-disabled installs still hand the relay every row.
|
|
135
|
+
findPendingForLegacyRelay: db.prepare(`
|
|
136
|
+
SELECT q.*
|
|
137
|
+
FROM queue q
|
|
138
|
+
LEFT JOIN runtime_sessions rs
|
|
139
|
+
ON rs.id = q.runtime_session_id
|
|
140
|
+
LEFT JOIN runtime_sessions rsc
|
|
141
|
+
ON rsc.conversation_id = q.conversation_id
|
|
142
|
+
WHERE q.status = 'pending'
|
|
143
|
+
AND COALESCE(q.kind, '') != 'continuation'
|
|
144
|
+
AND (q.next_attempt_at IS NULL OR q.next_attempt_at <= ?)
|
|
145
|
+
AND NULLIF(q.owner_sdk_session_id, '') IS NULL
|
|
146
|
+
AND LOWER(COALESCE(
|
|
147
|
+
NULLIF(rs.provider_type, ''),
|
|
148
|
+
NULLIF(rsc.provider_type, ''),
|
|
149
|
+
'github'
|
|
150
|
+
)) NOT IN (${sessionWorkerProviderSqlList()})
|
|
151
|
+
ORDER BY
|
|
152
|
+
q.retry_count ASC,
|
|
153
|
+
CASE WHEN q.next_attempt_at IS NULL THEN 0 ELSE 1 END ASC,
|
|
154
|
+
COALESCE(q.next_attempt_at, q.timestamp) ASC,
|
|
155
|
+
q.timestamp ASC
|
|
156
|
+
LIMIT 1
|
|
157
|
+
`),
|
|
158
|
+
findPendingForWorker: db.prepare(`
|
|
159
|
+
SELECT q.*
|
|
160
|
+
FROM queue q
|
|
161
|
+
LEFT JOIN runtime_sessions rs
|
|
162
|
+
ON rs.id = q.runtime_session_id
|
|
163
|
+
LEFT JOIN conversations c
|
|
164
|
+
ON c.id = q.conversation_id
|
|
165
|
+
WHERE q.status = 'pending'
|
|
166
|
+
AND COALESCE(q.kind, '') != 'continuation'
|
|
167
|
+
AND (q.next_attempt_at IS NULL OR q.next_attempt_at <= ?)
|
|
168
|
+
AND (
|
|
169
|
+
(
|
|
170
|
+
COALESCE(
|
|
171
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
172
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
173
|
+
NULLIF(c.sdk_session_id, '')
|
|
174
|
+
) IS NULL
|
|
175
|
+
-- Defense in depth: session-worker-provider turns are always
|
|
176
|
+
-- born owned, so an unowned one is a routing anomaly — it must
|
|
177
|
+
-- never be claimable by an arbitrary worker of a different
|
|
178
|
+
-- provider (isolation would otherwise rest on owner assignment
|
|
179
|
+
-- alone).
|
|
180
|
+
AND LOWER(COALESCE(NULLIF(rs.provider_type, ''), 'github'))
|
|
181
|
+
NOT IN (${sessionWorkerProviderSqlList()})
|
|
182
|
+
)
|
|
183
|
+
OR COALESCE(
|
|
184
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
185
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
186
|
+
NULLIF(c.sdk_session_id, '')
|
|
187
|
+
) = ?
|
|
188
|
+
)
|
|
189
|
+
ORDER BY
|
|
190
|
+
CASE
|
|
191
|
+
WHEN COALESCE(
|
|
192
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
193
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
194
|
+
NULLIF(c.sdk_session_id, '')
|
|
195
|
+
) = ? THEN 0
|
|
196
|
+
ELSE 1
|
|
197
|
+
END ASC,
|
|
198
|
+
q.retry_count ASC,
|
|
199
|
+
CASE WHEN q.next_attempt_at IS NULL THEN 0 ELSE 1 END ASC,
|
|
200
|
+
COALESCE(q.next_attempt_at, q.timestamp) ASC,
|
|
201
|
+
q.timestamp ASC
|
|
202
|
+
LIMIT 1
|
|
203
|
+
`),
|
|
204
|
+
findPendingForSessionAffinity: db.prepare(`
|
|
205
|
+
SELECT q.*
|
|
206
|
+
FROM queue q
|
|
207
|
+
LEFT JOIN runtime_sessions rs
|
|
208
|
+
ON rs.id = q.runtime_session_id
|
|
209
|
+
LEFT JOIN conversations c
|
|
210
|
+
ON c.id = q.conversation_id
|
|
211
|
+
WHERE q.status = 'pending'
|
|
212
|
+
AND COALESCE(q.kind, '') != 'continuation'
|
|
213
|
+
AND (q.next_attempt_at IS NULL OR q.next_attempt_at <= ?)
|
|
214
|
+
AND COALESCE(
|
|
215
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
216
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
217
|
+
NULLIF(c.sdk_session_id, '')
|
|
218
|
+
) = ?
|
|
219
|
+
ORDER BY
|
|
220
|
+
q.retry_count ASC,
|
|
221
|
+
CASE WHEN q.next_attempt_at IS NULL THEN 0 ELSE 1 END ASC,
|
|
222
|
+
COALESCE(q.next_attempt_at, q.timestamp) ASC,
|
|
223
|
+
q.timestamp ASC
|
|
224
|
+
LIMIT 1
|
|
225
|
+
`),
|
|
226
|
+
countQueueWorkForSessionAffinity: db.prepare(`
|
|
227
|
+
SELECT COUNT(*) AS cnt
|
|
228
|
+
FROM queue q
|
|
229
|
+
LEFT JOIN runtime_sessions rs
|
|
230
|
+
ON rs.id = q.runtime_session_id
|
|
231
|
+
LEFT JOIN conversations c
|
|
232
|
+
ON c.id = q.conversation_id
|
|
233
|
+
WHERE (
|
|
234
|
+
(q.status = 'pending' AND (q.next_attempt_at IS NULL OR q.next_attempt_at <= ?))
|
|
235
|
+
OR q.status = 'processing'
|
|
236
|
+
)
|
|
237
|
+
AND COALESCE(
|
|
238
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
239
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
240
|
+
NULLIF(c.sdk_session_id, '')
|
|
241
|
+
) = ?
|
|
242
|
+
`),
|
|
243
|
+
listPendingWorkerOwnerSessionIds: db.prepare(`
|
|
244
|
+
SELECT COALESCE(
|
|
245
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
246
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
247
|
+
NULLIF(c.sdk_session_id, '')
|
|
248
|
+
) AS sdk_session_id
|
|
249
|
+
FROM queue q
|
|
250
|
+
LEFT JOIN runtime_sessions rs
|
|
251
|
+
ON rs.id = q.runtime_session_id
|
|
252
|
+
LEFT JOIN conversations c
|
|
253
|
+
ON c.id = q.conversation_id
|
|
254
|
+
WHERE q.status = 'pending'
|
|
255
|
+
AND COALESCE(
|
|
256
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
257
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
258
|
+
NULLIF(c.sdk_session_id, '')
|
|
259
|
+
) IS NOT NULL
|
|
260
|
+
GROUP BY COALESCE(
|
|
261
|
+
NULLIF(q.owner_sdk_session_id, ''),
|
|
262
|
+
NULLIF(rs.sdk_session_id, ''),
|
|
263
|
+
NULLIF(c.sdk_session_id, '')
|
|
264
|
+
)
|
|
265
|
+
ORDER BY MIN(q.timestamp) ASC
|
|
266
|
+
LIMIT ?
|
|
267
|
+
`),
|
|
268
|
+
countStatus: db.prepare(`SELECT status, COUNT(*) as cnt FROM queue WHERE status IN ('pending','processing','parked') GROUP BY status`),
|
|
269
|
+
countRuntimeSessions: db.prepare(`SELECT COUNT(*) AS cnt FROM runtime_sessions WHERE status = 'active'`),
|
|
270
|
+
setProcessing: db.prepare(`UPDATE queue SET status = 'processing', processing_at = ? WHERE id = ?`),
|
|
271
|
+
setProcessingWithWorkerLease: db.prepare(`
|
|
272
|
+
UPDATE queue
|
|
273
|
+
SET
|
|
274
|
+
status = 'processing',
|
|
275
|
+
processing_at = ?,
|
|
276
|
+
owner_sdk_session_id = COALESCE(NULLIF(owner_sdk_session_id, ''), ?),
|
|
277
|
+
owner_assigned_at = COALESCE(owner_assigned_at, ?),
|
|
278
|
+
owner_lease_expires_at = ?,
|
|
279
|
+
owner_last_claimed_at = ?
|
|
280
|
+
WHERE id = ?
|
|
281
|
+
`),
|
|
282
|
+
setQueueRuntimeSession: db.prepare(`UPDATE queue SET runtime_session_id = ? WHERE id = ?`),
|
|
283
|
+
setQueueResponseMessageId: db.prepare(`UPDATE queue SET response_message_id = ? WHERE id = ?`),
|
|
284
|
+
setDone: db.prepare(`UPDATE queue SET status = 'done', response = ?, processing_at = NULL, next_attempt_at = NULL, owner_lease_expires_at = NULL WHERE id = ? AND status IN ('processing', 'pending')`),
|
|
285
|
+
setFailed: db.prepare(`UPDATE queue SET status = 'failed', response = ?, processing_at = NULL, next_attempt_at = NULL, owner_lease_expires_at = NULL WHERE id = ?`),
|
|
286
|
+
deleteConvQ: db.prepare(`DELETE FROM queue WHERE conversation_id = ?`),
|
|
287
|
+
findQById: db.prepare(`SELECT * FROM queue WHERE id = ?`),
|
|
288
|
+
pruneQueue: db.prepare(`DELETE FROM queue WHERE status = 'done' AND id NOT IN (SELECT id FROM queue WHERE status = 'done' ORDER BY timestamp DESC LIMIT 200)`),
|
|
289
|
+
// Recovery clears only the lease, never the owner: a recovered row must
|
|
290
|
+
// stay routed to its provider worker so the primer respawns that worker
|
|
291
|
+
// instead of the row becoming claimable by the global relay poll.
|
|
292
|
+
recoverStale: db.prepare(`UPDATE queue SET status = 'pending', processing_at = NULL, next_attempt_at = ?, owner_lease_expires_at = NULL, owner_last_claimed_at = NULL WHERE status = 'processing' AND COALESCE(kind, '') != 'continuation' AND processing_at < ?`),
|
|
293
|
+
// Staleness is inactivity, not elapsed turn time: owner_last_claimed_at is
|
|
294
|
+
// refreshed by every worker heartbeat for the message it is working on, so a
|
|
295
|
+
// long-but-alive turn keeps moving the cutoff. processing_at is only the
|
|
296
|
+
// fallback for rows without an owner (session-worker routing disabled), and
|
|
297
|
+
// it is what the separate absolute ceiling measures.
|
|
298
|
+
//
|
|
299
|
+
// A turn blocked on an unanswered AskUserQuestion is never stale — it is
|
|
300
|
+
// waiting on the human, and relay_questions carries its own expiry.
|
|
301
|
+
listRecoverableProcessing: db.prepare(`
|
|
302
|
+
SELECT id, conversation_id, kind, relay_mode, model, retry_count
|
|
303
|
+
FROM queue
|
|
304
|
+
WHERE status = 'processing'
|
|
305
|
+
AND COALESCE(kind, '') != 'continuation'
|
|
306
|
+
AND (
|
|
307
|
+
COALESCE(owner_last_claimed_at, processing_at, timestamp) < @inactiveBefore
|
|
308
|
+
OR (@ceilingBefore IS NOT NULL AND COALESCE(processing_at, timestamp) < @ceilingBefore)
|
|
309
|
+
)
|
|
310
|
+
AND id NOT IN (SELECT queue_id FROM relay_questions WHERE status = 'pending')
|
|
311
|
+
`),
|
|
312
|
+
recoverProcessingBefore: db.prepare(`
|
|
313
|
+
UPDATE queue
|
|
314
|
+
SET status = 'pending',
|
|
315
|
+
processing_at = NULL,
|
|
316
|
+
next_attempt_at = @requeueAt,
|
|
317
|
+
owner_lease_expires_at = NULL,
|
|
318
|
+
owner_last_claimed_at = NULL,
|
|
319
|
+
retry_count = retry_count + 1
|
|
320
|
+
WHERE status = 'processing'
|
|
321
|
+
AND COALESCE(kind, '') != 'continuation'
|
|
322
|
+
AND (
|
|
323
|
+
COALESCE(owner_last_claimed_at, processing_at, timestamp) < @inactiveBefore
|
|
324
|
+
OR (@ceilingBefore IS NOT NULL AND COALESCE(processing_at, timestamp) < @ceilingBefore)
|
|
325
|
+
)
|
|
326
|
+
AND id NOT IN (SELECT queue_id FROM relay_questions WHERE status = 'pending')
|
|
327
|
+
`),
|
|
328
|
+
// Dead-worker recovery: the owner's process is provably gone, so its
|
|
329
|
+
// in-flight rows go back to pending — owner intact (the primer
|
|
330
|
+
// respawns that worker; the row must never become global prey) and
|
|
331
|
+
// retry_count incremented so a worker that dies on every delivery
|
|
332
|
+
// eventually exhausts the same budget as any other retry loop. The
|
|
333
|
+
// pending-question exemption deliberately does NOT apply here: a dead
|
|
334
|
+
// worker can never collect its answer, so holding the row would leak
|
|
335
|
+
// both the row and the question.
|
|
336
|
+
listProcessingRowsForOwner: db.prepare(`
|
|
337
|
+
SELECT id, conversation_id, kind, retry_count
|
|
338
|
+
FROM queue
|
|
339
|
+
WHERE status = 'processing'
|
|
340
|
+
AND NULLIF(owner_sdk_session_id, '') = ?
|
|
341
|
+
`),
|
|
342
|
+
recoverProcessingRowKeepOwner: db.prepare(`
|
|
343
|
+
UPDATE queue
|
|
344
|
+
SET status = 'pending',
|
|
345
|
+
processing_at = NULL,
|
|
346
|
+
next_attempt_at = ?,
|
|
347
|
+
owner_lease_expires_at = NULL,
|
|
348
|
+
owner_last_claimed_at = NULL,
|
|
349
|
+
retry_count = retry_count + 1
|
|
350
|
+
WHERE id = ? AND status = 'processing'
|
|
351
|
+
`),
|
|
352
|
+
listProcessingOwnerSessionIds: db.prepare(`
|
|
353
|
+
SELECT DISTINCT NULLIF(owner_sdk_session_id, '') AS sdk_session_id
|
|
354
|
+
FROM queue
|
|
355
|
+
WHERE status = 'processing'
|
|
356
|
+
AND NULLIF(owner_sdk_session_id, '') IS NOT NULL
|
|
357
|
+
LIMIT ?
|
|
358
|
+
`),
|
|
359
|
+
// A continuation with no live worker can only be torn down — replaying
|
|
360
|
+
// it as deliverable work would send the CLI's own bookkeeping text back
|
|
361
|
+
// to the CLI as a user prompt.
|
|
362
|
+
listStaleProcessingContinuations: db.prepare(`
|
|
363
|
+
SELECT id, conversation_id
|
|
364
|
+
FROM queue
|
|
365
|
+
WHERE status = 'processing'
|
|
366
|
+
AND kind = 'continuation'
|
|
367
|
+
AND (
|
|
368
|
+
COALESCE(owner_last_claimed_at, processing_at, timestamp) < @inactiveBefore
|
|
369
|
+
OR (@ceilingBefore IS NOT NULL AND COALESCE(processing_at, timestamp) < @ceilingBefore)
|
|
370
|
+
)
|
|
371
|
+
`),
|
|
372
|
+
listQueueForPauseDrop: db.prepare(`SELECT id, conversation_id FROM queue WHERE status IN ('pending', 'processing', 'parked')`),
|
|
373
|
+
// Authoritative "is a turn in flight" signal for the conversation list.
|
|
374
|
+
// Clients otherwise track this from one-shot message_status socket events,
|
|
375
|
+
// which are lost if the socket drops between the final status and delivery.
|
|
376
|
+
listConversationIdsWithActiveQueue: db.prepare(`SELECT DISTINCT conversation_id FROM queue WHERE status IN ('pending', 'processing', 'parked')`),
|
|
377
|
+
deleteQueueById: db.prepare(`DELETE FROM queue WHERE id = ?`),
|
|
378
|
+
getLatestProcessingQueueByConversation: db.prepare(`SELECT id, relay_mode, timestamp, processing_at FROM queue WHERE conversation_id = ? AND status = 'processing' ORDER BY COALESCE(processing_at, timestamp) DESC LIMIT 1`),
|
|
379
|
+
parkPendingQueueForRestart: db.prepare(`
|
|
380
|
+
UPDATE queue
|
|
381
|
+
SET
|
|
382
|
+
status = 'parked',
|
|
383
|
+
next_attempt_at = NULL,
|
|
384
|
+
parked_at = COALESCE(parked_at, @parkedAt),
|
|
385
|
+
parked_target_session_id = @targetSessionId,
|
|
386
|
+
parked_transaction_id = @transactionId,
|
|
387
|
+
parked_reason = @reason
|
|
388
|
+
WHERE status = 'pending'
|
|
389
|
+
`),
|
|
390
|
+
listParkedQueueForRelease: db.prepare(`
|
|
391
|
+
SELECT id, conversation_id
|
|
392
|
+
FROM queue
|
|
393
|
+
WHERE status = 'parked'
|
|
394
|
+
AND (
|
|
395
|
+
parked_transaction_id IS NULL
|
|
396
|
+
OR parked_transaction_id = ?
|
|
397
|
+
OR parked_target_session_id = ?
|
|
398
|
+
)
|
|
399
|
+
ORDER BY COALESCE(parked_at, timestamp) ASC, timestamp ASC
|
|
400
|
+
`),
|
|
401
|
+
listAllParkedQueueForRelease: db.prepare(`
|
|
402
|
+
SELECT id, conversation_id
|
|
403
|
+
FROM queue
|
|
404
|
+
WHERE status = 'parked'
|
|
405
|
+
ORDER BY COALESCE(parked_at, timestamp) ASC, timestamp ASC
|
|
406
|
+
`),
|
|
407
|
+
releaseParkedQueueByIds: db.prepare(`
|
|
408
|
+
UPDATE queue
|
|
409
|
+
SET
|
|
410
|
+
status = 'pending',
|
|
411
|
+
parked_at = NULL,
|
|
412
|
+
parked_target_session_id = NULL,
|
|
413
|
+
parked_transaction_id = NULL,
|
|
414
|
+
parked_reason = NULL
|
|
415
|
+
WHERE id = ? AND status = 'parked'
|
|
416
|
+
`),
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
// uploads
|
|
420
|
+
getUploadFile: db.prepare(`SELECT * FROM uploaded_files WHERE sha256 = ?`),
|
|
421
|
+
insertUploadFile: db.prepare(`INSERT OR IGNORE INTO uploaded_files (sha256, original_name, mime_type, size_bytes, created_at) VALUES (?, ?, ?, ?, ?)`),
|
|
422
|
+
insertUploadRef: db.prepare(`INSERT OR IGNORE INTO upload_refs (file_sha256, conversation_id, message_id, created_at) VALUES (?, ?, ?, ?)`),
|
|
423
|
+
listUploadHashesByConversation: db.prepare(`SELECT DISTINCT file_sha256 FROM upload_refs WHERE conversation_id = ?`),
|
|
424
|
+
deleteUploadRefsByConversation: db.prepare(`DELETE FROM upload_refs WHERE conversation_id = ?`),
|
|
425
|
+
countUploadRefsBySha: db.prepare(`SELECT COUNT(*) AS cnt FROM upload_refs WHERE file_sha256 = ?`),
|
|
426
|
+
deleteUploadFile: db.prepare(`DELETE FROM uploaded_files WHERE sha256 = ?`),
|
|
427
|
+
|
|
428
|
+
// Draft attachment references use a sentinel message id so a composer
|
|
429
|
+
// draft can hold a blob alive before any message exists.
|
|
430
|
+
listDraftUploadHashes: db.prepare(`SELECT file_sha256 FROM upload_refs WHERE conversation_id = ? AND message_id = '__draft__'`),
|
|
431
|
+
deleteDraftUploadRefs: db.prepare(`DELETE FROM upload_refs WHERE conversation_id = ? AND message_id = '__draft__'`),
|
|
432
|
+
deleteDraftUploadRef: db.prepare(`DELETE FROM upload_refs WHERE conversation_id = ? AND message_id = '__draft__' AND file_sha256 = ?`),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
export function createQuestionRepository(db) {
|
|
4
|
+
return {
|
|
5
|
+
// relay questions
|
|
6
|
+
insertQuestion: db.prepare(`INSERT INTO relay_questions (id, queue_id, conversation_id, message_id, relay_mode, prompt, choices, request, request_schema, status, answer, structured_answer, sdk_session_id, owner_worker_id, continuation_id, continuation_question_id, created_at, answered_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, ?, ?, ?, ?, ?, NULL, ?)`),
|
|
7
|
+
getQuestion: db.prepare(`SELECT * FROM relay_questions WHERE id = ?`),
|
|
8
|
+
findPendingQuestionByMessage: db.prepare(`SELECT * FROM relay_questions WHERE message_id = ? AND status = 'pending' ORDER BY created_at DESC LIMIT 1`),
|
|
9
|
+
listPendingQuestionsByMessage: db.prepare(`SELECT * FROM relay_questions WHERE message_id = ? AND status = 'pending' ORDER BY created_at ASC`),
|
|
10
|
+
findRecentlyAnsweredQuestionByMessage: db.prepare(`SELECT * FROM relay_questions WHERE message_id = ? AND status = 'answered' AND answered_at >= ? ORDER BY answered_at DESC LIMIT 1`),
|
|
11
|
+
listQuestions: db.prepare(`SELECT * FROM relay_questions WHERE status = ? AND (? IS NULL OR conversation_id = ?) ORDER BY created_at ASC`),
|
|
12
|
+
timeoutQuestion:db.prepare(`UPDATE relay_questions SET status = 'timed_out' WHERE id = ? AND status = 'pending'`),
|
|
13
|
+
cancelPendingQuestionsByMessage: db.prepare(`UPDATE relay_questions SET status = 'cancelled', answered_at = COALESCE(answered_at, ?) WHERE message_id = ? AND status = 'pending'`),
|
|
14
|
+
deleteConvQuestions: db.prepare(`DELETE FROM relay_questions WHERE conversation_id = ?`),
|
|
15
|
+
// RETURNING (run via .all) so the expiry log names exactly the rows
|
|
16
|
+
// that flipped — a separate pre-SELECT could race an answer landing
|
|
17
|
+
// between the two statements.
|
|
18
|
+
expireQuestions: db.prepare(`UPDATE relay_questions SET status = 'timed_out' WHERE status = 'pending' AND expires_at < ? RETURNING id, created_at`),
|
|
19
|
+
|
|
20
|
+
// relay activity
|
|
21
|
+
insertActivity: db.prepare(`INSERT INTO relay_activity (queue_message_id, response_message_id, conversation_id, relay_mode, text, created_at, subagent_run_id, metadata_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
22
|
+
linkActivityToResponse: db.prepare(`UPDATE relay_activity SET response_message_id = ? WHERE queue_message_id = ? AND response_message_id IS NULL`),
|
|
23
|
+
listActivityByResponse: db.prepare(`SELECT text, subagent_run_id, metadata_json FROM relay_activity WHERE response_message_id = ? ORDER BY id ASC`),
|
|
24
|
+
listActivityByQueueMessage: db.prepare(`SELECT text, subagent_run_id, metadata_json FROM relay_activity WHERE queue_message_id = ? ORDER BY id ASC`),
|
|
25
|
+
deleteConvActivity: db.prepare(`DELETE FROM relay_activity WHERE conversation_id = ?`),
|
|
26
|
+
|
|
27
|
+
// relay stream events — every update carries the full text-so-far
|
|
28
|
+
// (all workers publish cumulative snapshots), so the store keeps ONE
|
|
29
|
+
// row per (queue message, subagent thread) and replaces it in place.
|
|
30
|
+
getLastStreamSeqByQueueMessage: db.prepare(`SELECT COALESCE(MAX(seq), 0) AS max_seq FROM relay_stream_events WHERE queue_message_id = ?`),
|
|
31
|
+
getStreamEventByQueueAndThread: db.prepare(`SELECT id, seq, done FROM relay_stream_events WHERE queue_message_id = ? AND subagent_run_id IS ? LIMIT 1`),
|
|
32
|
+
insertStreamEvent: db.prepare(`INSERT INTO relay_stream_events (queue_message_id, response_message_id, conversation_id, relay_mode, seq, text, done, created_at, subagent_run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
33
|
+
updateStreamEventByQueueAndThread: db.prepare(`
|
|
34
|
+
UPDATE relay_stream_events
|
|
35
|
+
SET response_message_id = COALESCE(response_message_id, ?),
|
|
36
|
+
conversation_id = ?,
|
|
37
|
+
relay_mode = ?,
|
|
38
|
+
seq = ?,
|
|
39
|
+
text = ?,
|
|
40
|
+
done = CASE WHEN done = 1 OR ? = 1 THEN 1 ELSE 0 END,
|
|
41
|
+
created_at = ?
|
|
42
|
+
WHERE queue_message_id = ?
|
|
43
|
+
AND subagent_run_id IS ?
|
|
44
|
+
`),
|
|
45
|
+
linkStreamEventsToResponse: db.prepare(`UPDATE relay_stream_events SET response_message_id = ? WHERE queue_message_id = ? AND response_message_id IS NULL`),
|
|
46
|
+
listStreamEventsByResponse: db.prepare(`SELECT seq, text, done, created_at, subagent_run_id FROM relay_stream_events WHERE response_message_id = ? ORDER BY seq ASC, id ASC`),
|
|
47
|
+
listStreamEventsByQueueMessage: db.prepare(`SELECT seq, text, done, created_at, subagent_run_id FROM relay_stream_events WHERE queue_message_id = ? ORDER BY seq ASC, id ASC`),
|
|
48
|
+
deleteConvStreamEvents: db.prepare(`DELETE FROM relay_stream_events WHERE conversation_id = ?`),
|
|
49
|
+
|
|
50
|
+
// relay thoughts (agent reasoning)
|
|
51
|
+
getLastThoughtSeqByQueueMessage: db.prepare(`SELECT COALESCE(MAX(seq), 0) AS max_seq FROM relay_thought WHERE queue_message_id = ?`),
|
|
52
|
+
getThoughtByQueueAndReasoning: db.prepare(`SELECT seq FROM relay_thought WHERE queue_message_id = ? AND reasoning_id = ? LIMIT 1`),
|
|
53
|
+
insertThought: db.prepare(`INSERT INTO relay_thought (queue_message_id, response_message_id, conversation_id, relay_mode, reasoning_id, seq, text, done, created_at, subagent_run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
54
|
+
updateThoughtByQueueAndReasoning: db.prepare(`
|
|
55
|
+
UPDATE relay_thought
|
|
56
|
+
SET response_message_id = COALESCE(response_message_id, ?),
|
|
57
|
+
conversation_id = ?,
|
|
58
|
+
relay_mode = ?,
|
|
59
|
+
text = ?,
|
|
60
|
+
done = CASE WHEN done = 1 OR ? = 1 THEN 1 ELSE 0 END,
|
|
61
|
+
created_at = ?,
|
|
62
|
+
subagent_run_id = COALESCE(?, subagent_run_id)
|
|
63
|
+
WHERE queue_message_id = ?
|
|
64
|
+
AND reasoning_id = ?
|
|
65
|
+
`),
|
|
66
|
+
linkThoughtsToResponse: db.prepare(`UPDATE relay_thought SET response_message_id = ? WHERE queue_message_id = ? AND response_message_id IS NULL`),
|
|
67
|
+
listThoughtsByResponse: db.prepare(`SELECT reasoning_id, seq, text, done, created_at, subagent_run_id FROM relay_thought WHERE response_message_id = ? ORDER BY seq ASC, id ASC`),
|
|
68
|
+
listThoughtsByQueueMessage: db.prepare(`SELECT reasoning_id, seq, text, done, created_at, subagent_run_id FROM relay_thought WHERE queue_message_id = ? ORDER BY seq ASC, id ASC`),
|
|
69
|
+
deleteConvThoughts: db.prepare(`DELETE FROM relay_thought WHERE conversation_id = ?`),
|
|
70
|
+
|
|
71
|
+
// subagent runs
|
|
72
|
+
insertSubagentRun: db.prepare(`
|
|
73
|
+
INSERT INTO subagent_runs (
|
|
74
|
+
id, queue_message_id, conversation_id, parent_subagent_id, display_name, status, started_at, updated_at, completed_at
|
|
75
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)
|
|
76
|
+
`),
|
|
77
|
+
getSubagentRun: db.prepare(`SELECT * FROM subagent_runs WHERE id = ?`),
|
|
78
|
+
updateSubagentRunStatus: db.prepare(`
|
|
79
|
+
UPDATE subagent_runs
|
|
80
|
+
SET status = ?, updated_at = ?, completed_at = COALESCE(?, completed_at)
|
|
81
|
+
WHERE id = ?
|
|
82
|
+
`),
|
|
83
|
+
listSubagentRunsByQueueMessage: db.prepare(`
|
|
84
|
+
SELECT id, queue_message_id, conversation_id, parent_subagent_id, display_name, status, started_at, updated_at, completed_at
|
|
85
|
+
FROM subagent_runs
|
|
86
|
+
WHERE queue_message_id = ?
|
|
87
|
+
ORDER BY started_at ASC, id ASC
|
|
88
|
+
`),
|
|
89
|
+
// Terminal-state reconciliation: whatever ends a turn (response, fail,
|
|
90
|
+
// abort) also ends its subagent runs — nothing else ever will, and an
|
|
91
|
+
// un-reconciled row renders as a bubble stuck "running" forever.
|
|
92
|
+
listRunningSubagentRunsByQueueMessage: db.prepare(`
|
|
93
|
+
SELECT id, conversation_id, parent_subagent_id, display_name
|
|
94
|
+
FROM subagent_runs
|
|
95
|
+
WHERE queue_message_id = ? AND status = 'running'
|
|
96
|
+
`),
|
|
97
|
+
closeRunningSubagentRunsByQueueMessage: db.prepare(`
|
|
98
|
+
UPDATE subagent_runs
|
|
99
|
+
SET status = ?, updated_at = ?, completed_at = COALESCE(completed_at, ?)
|
|
100
|
+
WHERE queue_message_id = ? AND status = 'running'
|
|
101
|
+
`),
|
|
102
|
+
listSubagentRunsByResponse: db.prepare(`
|
|
103
|
+
SELECT sr.id, sr.queue_message_id, sr.conversation_id, sr.parent_subagent_id, sr.display_name, sr.status, sr.started_at, sr.updated_at, sr.completed_at
|
|
104
|
+
FROM subagent_runs sr
|
|
105
|
+
WHERE sr.queue_message_id IN (
|
|
106
|
+
SELECT id FROM queue WHERE response_message_id = ?
|
|
107
|
+
UNION
|
|
108
|
+
SELECT DISTINCT queue_message_id FROM relay_activity WHERE response_message_id = ? AND queue_message_id IS NOT NULL
|
|
109
|
+
UNION
|
|
110
|
+
SELECT DISTINCT queue_message_id FROM relay_thought WHERE response_message_id = ? AND queue_message_id IS NOT NULL
|
|
111
|
+
)
|
|
112
|
+
ORDER BY sr.started_at ASC, sr.id ASC
|
|
113
|
+
`),
|
|
114
|
+
deleteConvSubagentRuns: db.prepare(`DELETE FROM subagent_runs WHERE conversation_id = ?`),
|
|
115
|
+
|
|
116
|
+
// workflow runs — final digests of settled background workflows,
|
|
117
|
+
// attached to the assistant message that reports the completion.
|
|
118
|
+
// Inserted inside the /api/response finalize transaction, so rows key
|
|
119
|
+
// directly on the response message id (unlike subagent_runs, which are
|
|
120
|
+
// written before the response id exists and need queue-id resolution).
|
|
121
|
+
insertWorkflowRun: db.prepare(`
|
|
122
|
+
INSERT INTO workflow_runs (
|
|
123
|
+
id, response_message_id, conversation_id, run_index, digest_json, created_at
|
|
124
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
125
|
+
`),
|
|
126
|
+
listWorkflowRunsByResponse: db.prepare(`
|
|
127
|
+
SELECT id, response_message_id, conversation_id, run_index, digest_json, created_at
|
|
128
|
+
FROM workflow_runs
|
|
129
|
+
WHERE response_message_id = ?
|
|
130
|
+
ORDER BY run_index ASC, id ASC
|
|
131
|
+
`),
|
|
132
|
+
deleteConvWorkflowRuns: db.prepare(`DELETE FROM workflow_runs WHERE conversation_id = ?`),
|
|
133
|
+
|
|
134
|
+
// preview cards — snapshots of previews published mid-turn, written
|
|
135
|
+
// with the queue id and linked to the response at finalize (the same
|
|
136
|
+
// two-step relay_activity uses, because the response id does not exist
|
|
137
|
+
// while the turn is still running).
|
|
138
|
+
insertPreviewCard: db.prepare(`
|
|
139
|
+
INSERT INTO preview_cards (
|
|
140
|
+
id, queue_message_id, response_message_id, conversation_id, preview_json, created_at
|
|
141
|
+
) VALUES (?, ?, NULL, ?, ?, ?)
|
|
142
|
+
`),
|
|
143
|
+
linkPreviewCardsToResponse: db.prepare(`UPDATE preview_cards SET response_message_id = ? WHERE queue_message_id = ? AND response_message_id IS NULL`),
|
|
144
|
+
listPreviewCardsByResponse: db.prepare(`
|
|
145
|
+
SELECT id, preview_json, created_at
|
|
146
|
+
FROM preview_cards
|
|
147
|
+
WHERE response_message_id = ?
|
|
148
|
+
ORDER BY created_at ASC, id ASC
|
|
149
|
+
`),
|
|
150
|
+
deleteConvPreviewCards: db.prepare(`DELETE FROM preview_cards WHERE conversation_id = ?`),
|
|
151
|
+
|
|
152
|
+
// relay boards
|
|
153
|
+
insertBoard: db.prepare(`INSERT INTO relay_boards (id, queue_id, conversation_id, message_id, board_type, relay_mode, title, body, actions_json, recommended_action, context_json, status, selected_action, acted_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, ?, ?)`),
|
|
154
|
+
getBoard: db.prepare(`SELECT * FROM relay_boards WHERE id = ?`),
|
|
155
|
+
findBoardByMessageType: db.prepare(`SELECT * FROM relay_boards WHERE message_id = ? AND board_type = ? ORDER BY created_at DESC LIMIT 1`),
|
|
156
|
+
listBoards: db.prepare(`SELECT * FROM relay_boards WHERE status = ? AND (? IS NULL OR conversation_id = ?) ORDER BY created_at ASC`),
|
|
157
|
+
markBoardAction: db.prepare(`UPDATE relay_boards SET status = 'acted', selected_action = ?, acted_at = ?, updated_at = ? WHERE id = ? AND status = 'pending'`),
|
|
158
|
+
dismissBoard: db.prepare(`UPDATE relay_boards SET status = 'dismissed', selected_action = ?, acted_at = ?, updated_at = ? WHERE id = ? AND status = 'pending'`),
|
|
159
|
+
deleteConvBoards: db.prepare(`DELETE FROM relay_boards WHERE conversation_id = ?`),
|
|
160
|
+
};
|
|
161
|
+
}
|