@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,497 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function normalizeId(value) {
|
|
4
|
+
return String(value || '').trim();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeMessageRole(value) {
|
|
8
|
+
const role = String(value || '').trim().toLowerCase();
|
|
9
|
+
if (role === 'assistant' || role === 'user') return role;
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeMessageTimestamp(value, fallbackIso) {
|
|
14
|
+
const text = String(value || '').trim();
|
|
15
|
+
if (!text) return fallbackIso;
|
|
16
|
+
const parsed = Date.parse(text);
|
|
17
|
+
if (!Number.isFinite(parsed)) return fallbackIso;
|
|
18
|
+
return new Date(parsed).toISOString();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureArray(value) {
|
|
22
|
+
return Array.isArray(value) ? value : [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseAttachments(value) {
|
|
26
|
+
if (Array.isArray(value)) return value.filter((attachment) => attachment && typeof attachment === 'object');
|
|
27
|
+
if (typeof value !== 'string' || !value.trim()) return [];
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(value);
|
|
30
|
+
return Array.isArray(parsed) ? parsed.filter((attachment) => attachment && typeof attachment === 'object') : [];
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
37
|
+
|
|
38
|
+
function attachmentSha256(attachment) {
|
|
39
|
+
const sha256 = String(attachment?.sha256 || '').trim().toLowerCase();
|
|
40
|
+
return SHA256_PATTERN.test(sha256) ? sha256 : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function attachmentSizeBytes(attachment) {
|
|
44
|
+
const size = Number(attachment?.size ?? attachment?.size_bytes ?? attachment?.sizeBytes);
|
|
45
|
+
return Number.isFinite(size) && size > 0 ? Math.trunc(size) : 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function attachmentSignature(attachments, detail = { sha: true, size: true }) {
|
|
49
|
+
const parts = ensureArray(attachments)
|
|
50
|
+
.map((attachment) => {
|
|
51
|
+
const sha256 = detail.sha ? attachmentSha256(attachment) : '';
|
|
52
|
+
if (sha256) return `sha:${sha256}`;
|
|
53
|
+
const name = String(attachment?.name || attachment?.displayName || '').trim().toLowerCase();
|
|
54
|
+
const type = String(attachment?.type || attachment?.mimeType || '').trim().toLowerCase();
|
|
55
|
+
if (!name && !type) return '';
|
|
56
|
+
const size = detail.size ? attachmentSizeBytes(attachment) : 0;
|
|
57
|
+
return `${name}\u0000${type}${size ? `\u0000${size}` : ''}`;
|
|
58
|
+
})
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.sort();
|
|
61
|
+
return parts.length ? parts.join('\u0001') : '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// SDK hint attachments never carry a sha256 in the fallback matching path
|
|
65
|
+
// (sha-bearing attachments are persisted directly) and their reported sizes
|
|
66
|
+
// are unreliable, so matching only uses the fields the hints actually have.
|
|
67
|
+
function signatureDetailForHints(hintAttachments) {
|
|
68
|
+
const hints = ensureArray(hintAttachments).filter((attachment) => attachment && typeof attachment === 'object');
|
|
69
|
+
return {
|
|
70
|
+
sha: hints.length > 0 && hints.every((attachment) => !!attachmentSha256(attachment)),
|
|
71
|
+
size: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function attachmentTimestampDistance(left, right) {
|
|
76
|
+
const leftMs = Date.parse(String(left || ''));
|
|
77
|
+
const rightMs = Date.parse(String(right || ''));
|
|
78
|
+
if (!Number.isFinite(leftMs) || !Number.isFinite(rightMs)) return Number.POSITIVE_INFINITY;
|
|
79
|
+
return Math.abs(leftMs - rightMs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function candidateSignatureForHints(candidateAttachments, hintAttachments) {
|
|
83
|
+
const hints = ensureArray(hintAttachments);
|
|
84
|
+
const imageOnlyHints = hints.length > 0
|
|
85
|
+
&& hints.every((attachment) => String(attachment?.type || attachment?.mimeType || '').trim().toLowerCase().startsWith('image/'));
|
|
86
|
+
const comparable = imageOnlyHints
|
|
87
|
+
? ensureArray(candidateAttachments).filter((attachment) => (
|
|
88
|
+
String(attachment?.type || attachment?.mimeType || '').trim().toLowerCase().startsWith('image/')
|
|
89
|
+
))
|
|
90
|
+
: candidateAttachments;
|
|
91
|
+
return attachmentSignature(comparable, signatureDetailForHints(hintAttachments));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeActivityEntry(value) {
|
|
95
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
96
|
+
const text = String(value.text || '').trim();
|
|
97
|
+
if (!text) return null;
|
|
98
|
+
const subagentRunId = value.subagentRunId ? String(value.subagentRunId).trim() : null;
|
|
99
|
+
const metadata = (value.metadata && typeof value.metadata === 'object' && !Array.isArray(value.metadata))
|
|
100
|
+
? value.metadata
|
|
101
|
+
: null;
|
|
102
|
+
return { text, subagentRunId, metadata };
|
|
103
|
+
}
|
|
104
|
+
const text = String(value || '').trim();
|
|
105
|
+
if (!text) return null;
|
|
106
|
+
return { text, subagentRunId: null, metadata: null };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeThoughtEntry(value, index) {
|
|
110
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
111
|
+
const text = String(value.text || '').trim();
|
|
112
|
+
if (!text) return null;
|
|
113
|
+
return {
|
|
114
|
+
reasoningId: String(value.reasoningId || '').trim() || `history-thought-${index + 1}`,
|
|
115
|
+
text,
|
|
116
|
+
done: value.done !== false,
|
|
117
|
+
subagentRunId: value.subagentRunId ? String(value.subagentRunId).trim() : null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createSessionHistoryRefreshService({
|
|
122
|
+
db,
|
|
123
|
+
stmts,
|
|
124
|
+
parseSessionEventsToMessages = null,
|
|
125
|
+
inFlightStateForConversation = null,
|
|
126
|
+
isDeletedSdkSession = null,
|
|
127
|
+
} = {}) {
|
|
128
|
+
const messageColumns = new Set(
|
|
129
|
+
db.prepare(`PRAGMA table_info(messages)`).all().map((column) => String(column?.name || '').trim()),
|
|
130
|
+
);
|
|
131
|
+
const messagesSupportShareVisibility = messageColumns.has('hidden_from_shares')
|
|
132
|
+
&& messageColumns.has('share_hidden_at');
|
|
133
|
+
const listExistingMessageAttachments = messageColumns.has('attachments')
|
|
134
|
+
? db.prepare(`
|
|
135
|
+
SELECT id, attachments, timestamp
|
|
136
|
+
FROM messages
|
|
137
|
+
WHERE conversation_id = ? AND attachments IS NOT NULL
|
|
138
|
+
`)
|
|
139
|
+
: null;
|
|
140
|
+
const listHiddenMessageVisibility = messagesSupportShareVisibility
|
|
141
|
+
? db.prepare(`
|
|
142
|
+
SELECT id, role, text, timestamp, share_hidden_at
|
|
143
|
+
FROM messages
|
|
144
|
+
WHERE conversation_id = ? AND hidden_from_shares = 1
|
|
145
|
+
`)
|
|
146
|
+
: null;
|
|
147
|
+
const restoreHiddenMessageVisibility = messagesSupportShareVisibility
|
|
148
|
+
? db.prepare(`
|
|
149
|
+
UPDATE messages
|
|
150
|
+
SET hidden_from_shares = 1, share_hidden_at = ?
|
|
151
|
+
WHERE conversation_id = ? AND id = ?
|
|
152
|
+
`)
|
|
153
|
+
: null;
|
|
154
|
+
const countBusyQueueByConversation = db.prepare(`
|
|
155
|
+
SELECT COUNT(*) AS cnt
|
|
156
|
+
FROM queue
|
|
157
|
+
WHERE conversation_id = ?
|
|
158
|
+
AND status IN ('pending', 'processing', 'parked')
|
|
159
|
+
`);
|
|
160
|
+
const countMessagesByConversation = db.prepare(`
|
|
161
|
+
SELECT COUNT(*) AS cnt
|
|
162
|
+
FROM messages
|
|
163
|
+
WHERE conversation_id = ?
|
|
164
|
+
`);
|
|
165
|
+
const tableNames = new Set(
|
|
166
|
+
db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table'`).all()
|
|
167
|
+
.map((row) => String(row?.name || '').trim()),
|
|
168
|
+
);
|
|
169
|
+
const queueColumns = tableNames.has('queue')
|
|
170
|
+
? new Set(db.prepare(`PRAGMA table_info(queue)`).all().map((column) => String(column?.name || '').trim()))
|
|
171
|
+
: new Set();
|
|
172
|
+
const listQueuedAttachments = queueColumns.has('attachments') && queueColumns.has('timestamp')
|
|
173
|
+
? db.prepare(`
|
|
174
|
+
SELECT id, attachments, timestamp
|
|
175
|
+
FROM queue
|
|
176
|
+
WHERE conversation_id = ? AND attachments IS NOT NULL
|
|
177
|
+
`)
|
|
178
|
+
: null;
|
|
179
|
+
const listReferencedAttachments = tableNames.has('upload_refs') && tableNames.has('uploaded_files')
|
|
180
|
+
? db.prepare(`
|
|
181
|
+
SELECT
|
|
182
|
+
ref.message_id,
|
|
183
|
+
MIN(ref.created_at) AS timestamp,
|
|
184
|
+
file.sha256,
|
|
185
|
+
file.original_name,
|
|
186
|
+
file.mime_type,
|
|
187
|
+
file.size_bytes
|
|
188
|
+
FROM upload_refs ref
|
|
189
|
+
JOIN uploaded_files file ON file.sha256 = ref.file_sha256
|
|
190
|
+
WHERE ref.conversation_id = ?
|
|
191
|
+
GROUP BY ref.message_id, file.sha256, file.original_name, file.mime_type, file.size_bytes
|
|
192
|
+
ORDER BY ref.message_id, MIN(ref.created_at), file.original_name
|
|
193
|
+
`)
|
|
194
|
+
: null;
|
|
195
|
+
const insertUploadRef = tableNames.has('upload_refs')
|
|
196
|
+
? db.prepare(`
|
|
197
|
+
INSERT OR IGNORE INTO upload_refs (file_sha256, conversation_id, message_id, created_at)
|
|
198
|
+
VALUES (?, ?, ?, ?)
|
|
199
|
+
`)
|
|
200
|
+
: null;
|
|
201
|
+
|
|
202
|
+
const deleteConversationMessages = typeof stmts?.deleteConvMsg?.run === 'function'
|
|
203
|
+
? stmts.deleteConvMsg
|
|
204
|
+
: db.prepare(`DELETE FROM messages WHERE conversation_id = ?`);
|
|
205
|
+
const deleteConversationActivity = typeof stmts?.deleteConvActivity?.run === 'function'
|
|
206
|
+
? stmts.deleteConvActivity
|
|
207
|
+
: db.prepare(`DELETE FROM relay_activity WHERE conversation_id = ?`);
|
|
208
|
+
const deleteConversationThoughts = typeof stmts?.deleteConvThoughts?.run === 'function'
|
|
209
|
+
? stmts.deleteConvThoughts
|
|
210
|
+
: db.prepare(`DELETE FROM relay_thought WHERE conversation_id = ?`);
|
|
211
|
+
const deleteConversationStreamEvents = typeof stmts?.deleteConvStreamEvents?.run === 'function'
|
|
212
|
+
? stmts.deleteConvStreamEvents
|
|
213
|
+
: db.prepare(`DELETE FROM relay_stream_events WHERE conversation_id = ?`);
|
|
214
|
+
const deleteConversationSubagentRuns = typeof stmts?.deleteConvSubagentRuns?.run === 'function'
|
|
215
|
+
? stmts.deleteConvSubagentRuns
|
|
216
|
+
: db.prepare(`DELETE FROM subagent_runs WHERE conversation_id = ?`);
|
|
217
|
+
// workflow_runs rows key on assistant message ids; a history rebuild
|
|
218
|
+
// replaces those ids, so the rows must go with the messages they annotated
|
|
219
|
+
// (same lifecycle as subagent_runs). The fallback guards dbs opened before
|
|
220
|
+
// the table existed.
|
|
221
|
+
const deleteConversationWorkflowRuns = typeof stmts?.deleteConvWorkflowRuns?.run === 'function'
|
|
222
|
+
? stmts.deleteConvWorkflowRuns
|
|
223
|
+
: db.prepare(`DELETE FROM workflow_runs WHERE conversation_id = ?`);
|
|
224
|
+
|
|
225
|
+
function collectAttachmentCandidates(conversationId) {
|
|
226
|
+
const candidates = new Map();
|
|
227
|
+
const addCandidate = (sourceMessageId, attachments, timestamp, priority) => {
|
|
228
|
+
const parsed = parseAttachments(attachments);
|
|
229
|
+
const signature = attachmentSignature(parsed);
|
|
230
|
+
if (!signature) return;
|
|
231
|
+
const key = normalizeId(sourceMessageId) || `${signature}:${timestamp || ''}`;
|
|
232
|
+
const existing = candidates.get(key);
|
|
233
|
+
if (existing && existing.priority <= priority) return;
|
|
234
|
+
candidates.set(key, {
|
|
235
|
+
sourceMessageId: key,
|
|
236
|
+
attachments: parsed,
|
|
237
|
+
signature,
|
|
238
|
+
timestamp: String(timestamp || '').trim(),
|
|
239
|
+
priority,
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
for (const row of listExistingMessageAttachments?.all(conversationId) || []) {
|
|
244
|
+
addCandidate(row.id, row.attachments, row.timestamp, 0);
|
|
245
|
+
}
|
|
246
|
+
for (const row of listQueuedAttachments?.all(conversationId) || []) {
|
|
247
|
+
addCandidate(row.id, row.attachments, row.timestamp, 1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const referencedByMessage = new Map();
|
|
251
|
+
for (const row of listReferencedAttachments?.all(conversationId) || []) {
|
|
252
|
+
const messageId = normalizeId(row.message_id);
|
|
253
|
+
if (!messageId) continue;
|
|
254
|
+
const group = referencedByMessage.get(messageId) || {
|
|
255
|
+
timestamp: row.timestamp,
|
|
256
|
+
attachments: [],
|
|
257
|
+
};
|
|
258
|
+
group.attachments.push({
|
|
259
|
+
name: String(row.original_name || '').trim(),
|
|
260
|
+
type: String(row.mime_type || '').trim().toLowerCase(),
|
|
261
|
+
size: Number(row.size_bytes || 0),
|
|
262
|
+
sha256: String(row.sha256 || '').trim().toLowerCase(),
|
|
263
|
+
});
|
|
264
|
+
referencedByMessage.set(messageId, group);
|
|
265
|
+
}
|
|
266
|
+
for (const [messageId, group] of referencedByMessage) {
|
|
267
|
+
addCandidate(messageId, group.attachments, group.timestamp, 2);
|
|
268
|
+
}
|
|
269
|
+
return Array.from(candidates.values());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function resolveRebuiltAttachments(message, candidates, consumedCandidates) {
|
|
273
|
+
const messageAttachments = parseAttachments(message?.attachments);
|
|
274
|
+
const persistedMessageAttachments = messageAttachments.filter((attachment) => (
|
|
275
|
+
String(attachment?.sha256 || '').trim()
|
|
276
|
+
|| String(attachment?.contentUrl || '').trim()
|
|
277
|
+
|| String(attachment?.dataUrl || '').trim()
|
|
278
|
+
|| attachment?.generatedImage
|
|
279
|
+
));
|
|
280
|
+
if (persistedMessageAttachments.length) return persistedMessageAttachments;
|
|
281
|
+
|
|
282
|
+
const messageId = normalizeId(message?.id);
|
|
283
|
+
const sameIdCandidate = candidates.find((candidate) => candidate.sourceMessageId === messageId);
|
|
284
|
+
if (sameIdCandidate) {
|
|
285
|
+
consumedCandidates.add(sameIdCandidate.sourceMessageId);
|
|
286
|
+
return sameIdCandidate.attachments;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const signature = attachmentSignature(messageAttachments, signatureDetailForHints(messageAttachments));
|
|
290
|
+
if (!signature) return [];
|
|
291
|
+
const matching = candidates
|
|
292
|
+
.filter((candidate) => (
|
|
293
|
+
!consumedCandidates.has(candidate.sourceMessageId)
|
|
294
|
+
&& candidateSignatureForHints(candidate.attachments, messageAttachments) === signature
|
|
295
|
+
))
|
|
296
|
+
.sort((left, right) => (
|
|
297
|
+
attachmentTimestampDistance(left.timestamp, message?.timestamp)
|
|
298
|
+
- attachmentTimestampDistance(right.timestamp, message?.timestamp)
|
|
299
|
+
|| left.priority - right.priority
|
|
300
|
+
));
|
|
301
|
+
const selected = matching[0];
|
|
302
|
+
if (!selected) return [];
|
|
303
|
+
consumedCandidates.add(selected.sourceMessageId);
|
|
304
|
+
return selected.attachments;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function insertRebuiltMessages(conversationId, messages = [], attachmentCandidates = []) {
|
|
308
|
+
const nowIso = new Date().toISOString();
|
|
309
|
+
const consumedAttachmentCandidates = new Set();
|
|
310
|
+
for (const message of ensureArray(messages)) {
|
|
311
|
+
const role = normalizeMessageRole(message?.role);
|
|
312
|
+
if (!role) continue;
|
|
313
|
+
const messageId = normalizeId(message?.id);
|
|
314
|
+
if (!messageId) continue;
|
|
315
|
+
const timestamp = normalizeMessageTimestamp(message?.timestamp, nowIso);
|
|
316
|
+
const attachments = resolveRebuiltAttachments(message, attachmentCandidates, consumedAttachmentCandidates);
|
|
317
|
+
stmts.insertMsg.run(
|
|
318
|
+
messageId,
|
|
319
|
+
conversationId,
|
|
320
|
+
role,
|
|
321
|
+
String(message?.text || ''),
|
|
322
|
+
role === 'assistant' ? (String(message?.model || '').trim() || null) : null,
|
|
323
|
+
String(message?.mode || '').trim() || null,
|
|
324
|
+
attachments.length ? JSON.stringify(attachments) : null,
|
|
325
|
+
timestamp,
|
|
326
|
+
null,
|
|
327
|
+
role === 'assistant' ? (String(message?.model || '').trim() || null) : null,
|
|
328
|
+
null,
|
|
329
|
+
);
|
|
330
|
+
for (const attachment of attachments) {
|
|
331
|
+
const sha256 = String(attachment?.sha256 || '').trim().toLowerCase();
|
|
332
|
+
if (!/^[a-f0-9]{64}$/.test(sha256)) continue;
|
|
333
|
+
insertUploadRef?.run(sha256, conversationId, messageId, timestamp);
|
|
334
|
+
}
|
|
335
|
+
if (role !== 'assistant') continue;
|
|
336
|
+
const activities = ensureArray(message?.activities)
|
|
337
|
+
.map((value) => normalizeActivityEntry(value))
|
|
338
|
+
.filter(Boolean);
|
|
339
|
+
for (const activity of activities) {
|
|
340
|
+
stmts.insertActivity.run(
|
|
341
|
+
String(message?.sourceMessageId || messageId),
|
|
342
|
+
messageId,
|
|
343
|
+
conversationId,
|
|
344
|
+
String(message?.mode || 'agent').trim() || 'agent',
|
|
345
|
+
activity.text,
|
|
346
|
+
timestamp,
|
|
347
|
+
activity.subagentRunId,
|
|
348
|
+
activity.metadata ? JSON.stringify(activity.metadata) : null,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const thoughts = ensureArray(message?.thoughts)
|
|
352
|
+
.map((value, index) => normalizeThoughtEntry(value, index))
|
|
353
|
+
.filter(Boolean);
|
|
354
|
+
for (let index = 0; index < thoughts.length; index += 1) {
|
|
355
|
+
const thought = thoughts[index];
|
|
356
|
+
if (typeof stmts.insertThought?.run !== 'function') continue;
|
|
357
|
+
stmts.insertThought.run(
|
|
358
|
+
String(message?.sourceMessageId || messageId),
|
|
359
|
+
messageId,
|
|
360
|
+
conversationId,
|
|
361
|
+
String(message?.mode || 'agent').trim() || 'agent',
|
|
362
|
+
thought.reasoningId,
|
|
363
|
+
index + 1,
|
|
364
|
+
thought.text,
|
|
365
|
+
thought.done ? 1 : 0,
|
|
366
|
+
timestamp,
|
|
367
|
+
thought.subagentRunId,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function normalizeHiddenMatchText(value) {
|
|
374
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function resolveHiddenMessageId(hiddenMessage, rebuiltMessages, consumedIds) {
|
|
378
|
+
const hiddenId = normalizeId(hiddenMessage?.id);
|
|
379
|
+
const sameIdMatch = rebuiltMessages.find((message) => normalizeId(message?.id) === hiddenId);
|
|
380
|
+
if (sameIdMatch) {
|
|
381
|
+
consumedIds.add(hiddenId);
|
|
382
|
+
return hiddenId;
|
|
383
|
+
}
|
|
384
|
+
const role = normalizeMessageRole(hiddenMessage?.role);
|
|
385
|
+
const text = normalizeHiddenMatchText(hiddenMessage?.text);
|
|
386
|
+
if (!role || !text) return '';
|
|
387
|
+
const matching = rebuiltMessages
|
|
388
|
+
.filter((message) => {
|
|
389
|
+
const messageId = normalizeId(message?.id);
|
|
390
|
+
return messageId
|
|
391
|
+
&& !consumedIds.has(messageId)
|
|
392
|
+
&& normalizeMessageRole(message?.role) === role
|
|
393
|
+
&& normalizeHiddenMatchText(message?.text) === text;
|
|
394
|
+
})
|
|
395
|
+
.sort((left, right) => (
|
|
396
|
+
attachmentTimestampDistance(left?.timestamp, hiddenMessage?.timestamp)
|
|
397
|
+
- attachmentTimestampDistance(right?.timestamp, hiddenMessage?.timestamp)
|
|
398
|
+
));
|
|
399
|
+
const selected = matching[0];
|
|
400
|
+
if (!selected) return '';
|
|
401
|
+
const selectedId = normalizeId(selected.id);
|
|
402
|
+
consumedIds.add(selectedId);
|
|
403
|
+
return selectedId;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const clearRetrievableHistoryTx = db.transaction((conversationId) => {
|
|
407
|
+
deleteConversationMessages.run(conversationId);
|
|
408
|
+
deleteConversationActivity.run(conversationId);
|
|
409
|
+
deleteConversationThoughts.run(conversationId);
|
|
410
|
+
deleteConversationStreamEvents.run(conversationId);
|
|
411
|
+
deleteConversationSubagentRuns.run(conversationId);
|
|
412
|
+
deleteConversationWorkflowRuns.run(conversationId);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const persistRebuiltHistoryTx = db.transaction((conversationId, messages = []) => {
|
|
416
|
+
insertRebuiltMessages(conversationId, messages);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const replaceRetrievableHistoryTx = db.transaction((conversationId, messages = []) => {
|
|
420
|
+
const hiddenMessages = listHiddenMessageVisibility?.all(conversationId) || [];
|
|
421
|
+
const attachmentCandidates = collectAttachmentCandidates(conversationId);
|
|
422
|
+
deleteConversationMessages.run(conversationId);
|
|
423
|
+
deleteConversationActivity.run(conversationId);
|
|
424
|
+
deleteConversationThoughts.run(conversationId);
|
|
425
|
+
deleteConversationStreamEvents.run(conversationId);
|
|
426
|
+
deleteConversationSubagentRuns.run(conversationId);
|
|
427
|
+
deleteConversationWorkflowRuns.run(conversationId);
|
|
428
|
+
insertRebuiltMessages(conversationId, messages, attachmentCandidates);
|
|
429
|
+
const rebuiltMessages = ensureArray(messages);
|
|
430
|
+
const consumedHiddenIds = new Set();
|
|
431
|
+
for (const hiddenMessage of hiddenMessages) {
|
|
432
|
+
const resolvedId = resolveHiddenMessageId(hiddenMessage, rebuiltMessages, consumedHiddenIds);
|
|
433
|
+
if (!resolvedId) continue;
|
|
434
|
+
restoreHiddenMessageVisibility.run(
|
|
435
|
+
hiddenMessage.share_hidden_at || new Date().toISOString(),
|
|
436
|
+
conversationId,
|
|
437
|
+
resolvedId,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
function evaluateRefreshIdleState(conversationId) {
|
|
443
|
+
const sid = normalizeId(conversationId);
|
|
444
|
+
if (!sid) return { idle: false, reason: 'missing-conversation-id' };
|
|
445
|
+
const queueBusyCount = Number(countBusyQueueByConversation.get(sid)?.cnt || 0);
|
|
446
|
+
if (queueBusyCount > 0) return { idle: false, reason: 'queue-busy' };
|
|
447
|
+
if (typeof inFlightStateForConversation === 'function') {
|
|
448
|
+
const inFlight = inFlightStateForConversation(sid);
|
|
449
|
+
const status = String(inFlight?.status || '').trim().toLowerCase();
|
|
450
|
+
if (status === 'processing' || status === 'pending' || status === 'parked') {
|
|
451
|
+
return { idle: false, reason: 'turn-processing' };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return { idle: true };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function mapSdkEventsToMessages(events = []) {
|
|
458
|
+
if (typeof parseSessionEventsToMessages !== 'function') return [];
|
|
459
|
+
return parseSessionEventsToMessages(ensureArray(events));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function countRetrievableMessages(conversationId) {
|
|
463
|
+
const sid = normalizeId(conversationId);
|
|
464
|
+
if (!sid) return 0;
|
|
465
|
+
return Number(countMessagesByConversation.get(sid)?.cnt || 0);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function clearRetrievableHistory(conversationId) {
|
|
469
|
+
const sid = normalizeId(conversationId);
|
|
470
|
+
if (!sid) return false;
|
|
471
|
+
clearRetrievableHistoryTx(sid);
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function persistRebuiltHistory(conversationId, messages = []) {
|
|
476
|
+
const sid = normalizeId(conversationId);
|
|
477
|
+
if (!sid) return { insertedCount: 0 };
|
|
478
|
+
persistRebuiltHistoryTx(sid, messages);
|
|
479
|
+
return { insertedCount: ensureArray(messages).length };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function replaceRetrievableHistory(conversationId, messages = []) {
|
|
483
|
+
const sid = normalizeId(conversationId);
|
|
484
|
+
if (!sid) return { insertedCount: 0 };
|
|
485
|
+
replaceRetrievableHistoryTx(sid, messages);
|
|
486
|
+
return { insertedCount: ensureArray(messages).length };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
evaluateRefreshIdleState,
|
|
491
|
+
mapSdkEventsToMessages,
|
|
492
|
+
countRetrievableMessages,
|
|
493
|
+
clearRetrievableHistory,
|
|
494
|
+
persistRebuiltHistory,
|
|
495
|
+
replaceRetrievableHistory,
|
|
496
|
+
};
|
|
497
|
+
}
|