@vellumai/assistant 0.12.2 → 0.12.3-staging.1
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/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { basename, dirname, extname, join } from "node:path";
|
|
19
19
|
|
|
20
|
-
import { eq } from "drizzle-orm";
|
|
20
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
21
21
|
import { v4 as uuid } from "uuid";
|
|
22
22
|
|
|
23
23
|
import {
|
|
@@ -31,7 +31,7 @@ import { getWorkspaceDir } from "../util/platform.js";
|
|
|
31
31
|
import { getConversationAttachmentsDirPath } from "./conversation-directories.js";
|
|
32
32
|
import { getDb } from "./db-connection.js";
|
|
33
33
|
import { rawAll, rawGet, rawRun } from "./raw-query.js";
|
|
34
|
-
import { attachments, messageAttachments } from "./schema.js";
|
|
34
|
+
import { attachments, messageAttachments, messages } from "./schema.js";
|
|
35
35
|
|
|
36
36
|
export interface StoredAttachment {
|
|
37
37
|
id: string;
|
|
@@ -206,16 +206,23 @@ function insertMessageAttachmentLink(
|
|
|
206
206
|
attachmentId: string,
|
|
207
207
|
position: number,
|
|
208
208
|
): void {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
209
|
+
rawRun(
|
|
210
|
+
"attachments:insertMessageAttachmentLink",
|
|
211
|
+
`INSERT INTO message_attachments
|
|
212
|
+
(id, message_id, attachment_id, position, created_at)
|
|
213
|
+
SELECT ?, ?, ?, ?, ?
|
|
214
|
+
WHERE NOT EXISTS (
|
|
215
|
+
SELECT 1 FROM message_attachments
|
|
216
|
+
WHERE message_id = ? AND attachment_id = ?
|
|
217
|
+
)`,
|
|
218
|
+
uuid(),
|
|
219
|
+
messageId,
|
|
220
|
+
attachmentId,
|
|
221
|
+
position,
|
|
222
|
+
Date.now(),
|
|
223
|
+
messageId,
|
|
224
|
+
attachmentId,
|
|
225
|
+
);
|
|
219
226
|
}
|
|
220
227
|
|
|
221
228
|
function persistAttachmentFilePath(
|
|
@@ -1256,6 +1263,20 @@ export function linkAttachmentToMessage(
|
|
|
1256
1263
|
throw new Error(`Message not found: ${messageId}`);
|
|
1257
1264
|
}
|
|
1258
1265
|
|
|
1266
|
+
const existing = getDb()
|
|
1267
|
+
.select({ attachmentId: messageAttachments.attachmentId })
|
|
1268
|
+
.from(messageAttachments)
|
|
1269
|
+
.where(
|
|
1270
|
+
and(
|
|
1271
|
+
eq(messageAttachments.messageId, messageId),
|
|
1272
|
+
eq(messageAttachments.attachmentId, attachmentId),
|
|
1273
|
+
),
|
|
1274
|
+
)
|
|
1275
|
+
.get();
|
|
1276
|
+
if (existing) {
|
|
1277
|
+
return existing.attachmentId;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1259
1280
|
const scopedAttachmentId = scopeAttachmentToConversation(
|
|
1260
1281
|
attachmentId,
|
|
1261
1282
|
ctx.conversationId,
|
|
@@ -1301,41 +1322,60 @@ export function getAttachmentsForMessage(
|
|
|
1301
1322
|
export function getAttachmentMetadataForMessage(
|
|
1302
1323
|
messageId: string,
|
|
1303
1324
|
): StoredAttachment[] {
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
.where(eq(messageAttachments.messageId, messageId))
|
|
1309
|
-
.orderBy(messageAttachments.position)
|
|
1310
|
-
.all();
|
|
1325
|
+
return getAttachmentMetadataForMessages([messageId]).map(
|
|
1326
|
+
(row) => row.attachment,
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1311
1329
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1330
|
+
export interface MessageAttachmentMetadata {
|
|
1331
|
+
messageId: string;
|
|
1332
|
+
messageMetadata: string | null;
|
|
1333
|
+
attachment: StoredAttachment;
|
|
1334
|
+
}
|
|
1315
1335
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
mimeType: attachments.mimeType,
|
|
1326
|
-
sizeBytes: attachments.sizeBytes,
|
|
1327
|
-
kind: attachments.kind,
|
|
1328
|
-
thumbnailBase64: attachments.thumbnailBase64,
|
|
1329
|
-
createdAt: attachments.createdAt,
|
|
1330
|
-
})
|
|
1331
|
-
.from(attachments)
|
|
1332
|
-
.where(eq(attachments.id, link.attachmentId))
|
|
1333
|
-
.get();
|
|
1334
|
-
if (row) {
|
|
1335
|
-
results.push(row);
|
|
1336
|
-
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Return linked attachment metadata together with the metadata of each source
|
|
1338
|
+
* message. Rows follow the requested message order, then link position.
|
|
1339
|
+
*/
|
|
1340
|
+
export function getAttachmentMetadataForMessages(
|
|
1341
|
+
messageIds: string[],
|
|
1342
|
+
): MessageAttachmentMetadata[] {
|
|
1343
|
+
if (messageIds.length === 0) {
|
|
1344
|
+
return [];
|
|
1337
1345
|
}
|
|
1338
|
-
|
|
1346
|
+
const db = getDb();
|
|
1347
|
+
const rows = db
|
|
1348
|
+
.select({
|
|
1349
|
+
messageId: messageAttachments.messageId,
|
|
1350
|
+
messageMetadata: messages.metadata,
|
|
1351
|
+
position: messageAttachments.position,
|
|
1352
|
+
id: attachments.id,
|
|
1353
|
+
originalFilename: attachments.originalFilename,
|
|
1354
|
+
mimeType: attachments.mimeType,
|
|
1355
|
+
sizeBytes: attachments.sizeBytes,
|
|
1356
|
+
kind: attachments.kind,
|
|
1357
|
+
thumbnailBase64: attachments.thumbnailBase64,
|
|
1358
|
+
createdAt: attachments.createdAt,
|
|
1359
|
+
})
|
|
1360
|
+
.from(messageAttachments)
|
|
1361
|
+
.innerJoin(messages, eq(messages.id, messageAttachments.messageId))
|
|
1362
|
+
.innerJoin(attachments, eq(attachments.id, messageAttachments.attachmentId))
|
|
1363
|
+
.where(inArray(messageAttachments.messageId, messageIds))
|
|
1364
|
+
.all();
|
|
1365
|
+
const messageOrder = new Map(messageIds.map((id, index) => [id, index]));
|
|
1366
|
+
rows.sort((a, b) => {
|
|
1367
|
+
const messageDelta =
|
|
1368
|
+
(messageOrder.get(a.messageId) ?? 0) -
|
|
1369
|
+
(messageOrder.get(b.messageId) ?? 0);
|
|
1370
|
+
return messageDelta !== 0 ? messageDelta : a.position - b.position;
|
|
1371
|
+
});
|
|
1372
|
+
return rows.map(
|
|
1373
|
+
({ messageId, messageMetadata, position: _position, ...a }) => ({
|
|
1374
|
+
messageId,
|
|
1375
|
+
messageMetadata,
|
|
1376
|
+
attachment: a,
|
|
1377
|
+
}),
|
|
1378
|
+
);
|
|
1339
1379
|
}
|
|
1340
1380
|
|
|
1341
1381
|
/**
|
|
@@ -94,6 +94,8 @@ import {
|
|
|
94
94
|
import { deleteConversationRowsInBatches } from "./conversation-row-batch-delete.js";
|
|
95
95
|
import {
|
|
96
96
|
BACKGROUND_CONVERSATION_TYPES,
|
|
97
|
+
COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY,
|
|
98
|
+
computerUseScreenshotAttachmentIdsFromMetadata,
|
|
97
99
|
type ConversationCreateType,
|
|
98
100
|
type ConversationOrigin,
|
|
99
101
|
isHiddenMessageMetadata,
|
|
@@ -413,6 +415,9 @@ export const messageMetadataSchema = z
|
|
|
413
415
|
* channel/interface fields cannot stand in for it.
|
|
414
416
|
*/
|
|
415
417
|
voiceSessionTurn: z.boolean().optional(),
|
|
418
|
+
[COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY]: z
|
|
419
|
+
.array(z.string())
|
|
420
|
+
.optional(),
|
|
416
421
|
/**
|
|
417
422
|
* Discriminates daemon-authored rows from ordinary turns.
|
|
418
423
|
* `"system_card"` marks pre-composed status cards (the /compact, /clean,
|
|
@@ -1849,7 +1854,25 @@ function populateForkContentsInProcess(args: PopulateForkContentsArgs): void {
|
|
|
1849
1854
|
});
|
|
1850
1855
|
}
|
|
1851
1856
|
|
|
1852
|
-
|
|
1857
|
+
remapForkWorkspaceAttachmentRefs(
|
|
1858
|
+
messagesToCopy,
|
|
1859
|
+
forkedMessageIds,
|
|
1860
|
+
attachmentIdMap,
|
|
1861
|
+
);
|
|
1862
|
+
widenForkAttachmentIdTags(
|
|
1863
|
+
messagesToCopy,
|
|
1864
|
+
forkedMessageIds,
|
|
1865
|
+
attachmentIdMap,
|
|
1866
|
+
SIGHT_FRAME_ATTACHMENT_IDS_KEY,
|
|
1867
|
+
sightFrameAttachmentIdsFromMetadata,
|
|
1868
|
+
);
|
|
1869
|
+
widenForkAttachmentIdTags(
|
|
1870
|
+
messagesToCopy,
|
|
1871
|
+
forkedMessageIds,
|
|
1872
|
+
attachmentIdMap,
|
|
1873
|
+
COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY,
|
|
1874
|
+
computerUseScreenshotAttachmentIdsFromMetadata,
|
|
1875
|
+
);
|
|
1853
1876
|
|
|
1854
1877
|
// Set lastMessageAt to the max createdAt of copied messages so the
|
|
1855
1878
|
// forked conversation sorts correctly by message recency.
|
|
@@ -1895,31 +1918,99 @@ function populateForkContentsInProcess(args: PopulateForkContentsArgs): void {
|
|
|
1895
1918
|
}
|
|
1896
1919
|
|
|
1897
1920
|
/**
|
|
1898
|
-
*
|
|
1899
|
-
*
|
|
1921
|
+
* Remap copied workspace references to the fork-scoped attachment ids created
|
|
1922
|
+
* by the relink loop.
|
|
1900
1923
|
*
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
1903
|
-
*
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1924
|
+
* The same source attachment can be linked to several copied messages. The
|
|
1925
|
+
* shared map keeps every copied reference and message link on one cloned row,
|
|
1926
|
+
* while references to attachments outside the copied window stay unchanged.
|
|
1927
|
+
*/
|
|
1928
|
+
function remapForkWorkspaceAttachmentRefs(
|
|
1929
|
+
messagesToCopy: MessageRow[],
|
|
1930
|
+
forkedMessageIds: Map<string, string>,
|
|
1931
|
+
attachmentIdMap: Map<string, string>,
|
|
1932
|
+
): void {
|
|
1933
|
+
if (attachmentIdMap.size === 0) {
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
const db = getDb();
|
|
1938
|
+
for (const message of messagesToCopy) {
|
|
1939
|
+
const forkedMessageId = forkedMessageIds.get(message.id);
|
|
1940
|
+
if (!forkedMessageId) {
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
const remappedContent = remapWorkspaceAttachmentRefs(
|
|
1945
|
+
message.content,
|
|
1946
|
+
attachmentIdMap,
|
|
1947
|
+
);
|
|
1948
|
+
if (remappedContent === message.content) {
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
db.update(messages)
|
|
1953
|
+
.set({ content: JSON.stringify(remappedContent) })
|
|
1954
|
+
.where(eq(messages.id, forkedMessageId))
|
|
1955
|
+
.run();
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
function remapWorkspaceAttachmentRefs(
|
|
1960
|
+
value: unknown,
|
|
1961
|
+
attachmentIdMap: ReadonlyMap<string, string>,
|
|
1962
|
+
): unknown {
|
|
1963
|
+
if (Array.isArray(value)) {
|
|
1964
|
+
let changed = false;
|
|
1965
|
+
const remapped = value.map((entry) => {
|
|
1966
|
+
const next = remapWorkspaceAttachmentRefs(entry, attachmentIdMap);
|
|
1967
|
+
changed ||= next !== entry;
|
|
1968
|
+
return next;
|
|
1969
|
+
});
|
|
1970
|
+
return changed ? remapped : value;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
if (value == null || typeof value !== "object") {
|
|
1974
|
+
return value;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
const record = value as Record<string, unknown>;
|
|
1978
|
+
let changed = false;
|
|
1979
|
+
const remapped: Record<string, unknown> = {};
|
|
1980
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
1981
|
+
let next = remapWorkspaceAttachmentRefs(entry, attachmentIdMap);
|
|
1982
|
+
if (
|
|
1983
|
+
key === "attachmentId" &&
|
|
1984
|
+
record.type === "workspace_ref" &&
|
|
1985
|
+
typeof entry === "string"
|
|
1986
|
+
) {
|
|
1987
|
+
next = attachmentIdMap.get(entry) ?? entry;
|
|
1988
|
+
}
|
|
1989
|
+
changed ||= next !== entry;
|
|
1990
|
+
remapped[key] = next;
|
|
1991
|
+
}
|
|
1992
|
+
return changed ? remapped : value;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
/**
|
|
1996
|
+
* Extend a copied row's attachment-id metadata to name cloned ids alongside
|
|
1997
|
+
* the source ids it was written with.
|
|
1909
1998
|
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
* simply never matches.
|
|
1999
|
+
* Copied message content uses the fork-scoped ids, while metadata begins as a
|
|
2000
|
+
* copy of the source row. Naming both ids preserves compatibility with readers
|
|
2001
|
+
* of either vocabulary. Extra ids are inert because an id no block or linked
|
|
2002
|
+
* attachment carries simply never matches.
|
|
1914
2003
|
*
|
|
1915
2004
|
* Runs after the attachment loop because that loop is what produces the id map.
|
|
1916
2005
|
* Only rows that actually carry a tag are rewritten, so an ordinary fork does
|
|
1917
2006
|
* no extra writes.
|
|
1918
2007
|
*/
|
|
1919
|
-
function
|
|
2008
|
+
function widenForkAttachmentIdTags(
|
|
1920
2009
|
messagesToCopy: MessageRow[],
|
|
1921
2010
|
forkedMessageIds: Map<string, string>,
|
|
1922
2011
|
attachmentIdMap: Map<string, string>,
|
|
2012
|
+
metadataKey: string,
|
|
2013
|
+
readIds: (metadata: Record<string, unknown> | null | undefined) => string[],
|
|
1923
2014
|
): void {
|
|
1924
2015
|
if (attachmentIdMap.size === 0) {
|
|
1925
2016
|
return;
|
|
@@ -1931,7 +2022,7 @@ function widenForkSightFrameTags(
|
|
|
1931
2022
|
continue;
|
|
1932
2023
|
}
|
|
1933
2024
|
const sourceMetadata = parseMessageMetadata(message.metadata);
|
|
1934
|
-
const sourceIds =
|
|
2025
|
+
const sourceIds = readIds(sourceMetadata);
|
|
1935
2026
|
if (sourceIds.length === 0) {
|
|
1936
2027
|
continue;
|
|
1937
2028
|
}
|
|
@@ -1955,7 +2046,7 @@ function widenForkSightFrameTags(
|
|
|
1955
2046
|
.set({
|
|
1956
2047
|
metadata: JSON.stringify({
|
|
1957
2048
|
...(forkedMetadata ?? {}),
|
|
1958
|
-
[
|
|
2049
|
+
[metadataKey]: [...widened],
|
|
1959
2050
|
}),
|
|
1960
2051
|
})
|
|
1961
2052
|
.where(eq(messages.id, forkedMessageId))
|
|
@@ -2806,8 +2897,8 @@ export interface ConversationAttachmentListing {
|
|
|
2806
2897
|
* Driven from `messages` so the lineage predicate rides
|
|
2807
2898
|
* `idx_messages_conversation_created_at`. An attachment linked to more than
|
|
2808
2899
|
* one row is listed once, on the newest row that carries it. Tool-result rows
|
|
2809
|
-
* are left out:
|
|
2810
|
-
*
|
|
2900
|
+
* are left out: reply-linked output belongs in Files, while tool-result-only
|
|
2901
|
+
* media stays available through tool history.
|
|
2811
2902
|
*
|
|
2812
2903
|
* The lineage-wide select still reads every linked row: an exact `total` and
|
|
2813
2904
|
* the metadata-derived flags are only known after the role and visibility
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolDefinition } from "../providers/types.js";
|
|
1
2
|
import type {
|
|
2
3
|
AddMessageOptions,
|
|
3
4
|
ConversationRow,
|
|
@@ -5,6 +6,7 @@ import type {
|
|
|
5
6
|
MessageRow,
|
|
6
7
|
} from "./conversation-crud.js";
|
|
7
8
|
import type { ArchiveStatusFilter } from "./conversation-queries.js";
|
|
9
|
+
import type { ConversationToolSurface } from "./conversation-tool-surface.js";
|
|
8
10
|
import type { ConversationType } from "./conversation-types.js";
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -52,6 +54,34 @@ export async function getMessages(
|
|
|
52
54
|
return fn(conversationId);
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
/**
|
|
58
|
+
* What the conversation's most recent live turn sent to the provider that a
|
|
59
|
+
* replaying fork reproduces verbatim (its tool definitions and the
|
|
60
|
+
* delegation-section state of its system prompt), or `null` when none has
|
|
61
|
+
* been recorded.
|
|
62
|
+
*/
|
|
63
|
+
export async function getRecordedConversationToolSurface(
|
|
64
|
+
conversationId: string,
|
|
65
|
+
): Promise<ConversationToolSurface | null> {
|
|
66
|
+
const { getConversationToolSurface: fn } =
|
|
67
|
+
await import("./conversation-tool-surface.js");
|
|
68
|
+
return fn(conversationId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The tool definitions the conversation's most recent live turn sent to the
|
|
73
|
+
* provider, or `null` when none has been recorded. A projection of
|
|
74
|
+
* {@link getRecordedConversationToolSurface}: a fork replaying the source's
|
|
75
|
+
* request needs the whole record, whose delegation-section state the system
|
|
76
|
+
* prompt tier of the provider cache prefix depends on.
|
|
77
|
+
*/
|
|
78
|
+
export async function getConversationToolSurface(
|
|
79
|
+
conversationId: string,
|
|
80
|
+
): Promise<ToolDefinition[] | null> {
|
|
81
|
+
const surface = await getRecordedConversationToolSurface(conversationId);
|
|
82
|
+
return surface?.tools ?? null;
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
/** Whether the conversation currently has a turn in flight. */
|
|
56
86
|
export async function isConversationProcessing(id: string): Promise<boolean> {
|
|
57
87
|
const { isConversationProcessing: fn } =
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { eq, sql } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import type { ToolDefinition } from "../providers/types.js";
|
|
6
|
+
import { getDb } from "./db-connection.js";
|
|
7
|
+
import { conversationToolSurfaces } from "./schema/index.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Per-conversation record of what the most recent live turn sent to the
|
|
11
|
+
* provider that a fork wake cannot re-derive (`conversation_tool_surfaces`).
|
|
12
|
+
*
|
|
13
|
+
* The provider prompt cache is a byte-exact prefix match over
|
|
14
|
+
* `tools -> system -> messages`, so a background wake that forks a
|
|
15
|
+
* conversation can only reuse the source's cached prefix by sending the SAME
|
|
16
|
+
* tools array under the SAME system prompt. Re-deriving either on the fork
|
|
17
|
+
* cannot guarantee that: the fork may run in another process (a different
|
|
18
|
+
* tool registry), with no connected clients (different host-tool gates),
|
|
19
|
+
* under a presence the persisted message stamps do not encode, or under a
|
|
20
|
+
* wake scope that cannot spawn subagents where the source could (the system
|
|
21
|
+
* prompt's delegation section). Recording the resolved values and replaying
|
|
22
|
+
* them verbatim can.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** What a live turn sent that a replaying fork reproduces verbatim. */
|
|
26
|
+
export interface ConversationToolSurface {
|
|
27
|
+
/** The tool definitions the turn sent, exactly as resolved. */
|
|
28
|
+
tools: ToolDefinition[];
|
|
29
|
+
/**
|
|
30
|
+
* Whether the turn's system prompt rendered the parallel-delegation
|
|
31
|
+
* section (`01-parallel-tasks`), which the prompt derives from the same
|
|
32
|
+
* resolved tool surface plus the turn's channel. `null` when unknown: the
|
|
33
|
+
* turn ran on a system-prompt override, or the row predates the column.
|
|
34
|
+
*/
|
|
35
|
+
delegateIndependentTasks: boolean | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Content hash of the serialized surface. */
|
|
39
|
+
export function hashConversationToolSurface(
|
|
40
|
+
surface: ConversationToolSurface,
|
|
41
|
+
): string {
|
|
42
|
+
return hashSurface(
|
|
43
|
+
JSON.stringify(surface.tools),
|
|
44
|
+
surface.delegateIndependentTasks,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hashSurface(
|
|
49
|
+
toolsJson: string,
|
|
50
|
+
delegateIndependentTasks: boolean | null,
|
|
51
|
+
): string {
|
|
52
|
+
const marker =
|
|
53
|
+
delegateIndependentTasks === null
|
|
54
|
+
? ""
|
|
55
|
+
: delegateIndependentTasks
|
|
56
|
+
? "1"
|
|
57
|
+
: "0";
|
|
58
|
+
return createHash("sha256")
|
|
59
|
+
.update(toolsJson)
|
|
60
|
+
.update("\n")
|
|
61
|
+
.update(marker)
|
|
62
|
+
.digest("hex")
|
|
63
|
+
.slice(0, 32);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Persist `surface` as the conversation's current wire surface and return
|
|
68
|
+
* its hash. `knownHash` is the hash the caller last recorded for this
|
|
69
|
+
* conversation (undefined when it has recorded nothing this process
|
|
70
|
+
* lifetime): a matching hash skips the statement outright. Otherwise the
|
|
71
|
+
* upsert only rewrites a row whose stored hash differs, so a conversation
|
|
72
|
+
* reloaded from disk never rewrites an unchanged surface either.
|
|
73
|
+
*/
|
|
74
|
+
export function recordConversationToolSurface(
|
|
75
|
+
conversationId: string,
|
|
76
|
+
surface: ConversationToolSurface,
|
|
77
|
+
knownHash?: string,
|
|
78
|
+
): string {
|
|
79
|
+
const toolsJson = JSON.stringify(surface.tools);
|
|
80
|
+
const { delegateIndependentTasks } = surface;
|
|
81
|
+
const toolsHash = hashSurface(toolsJson, delegateIndependentTasks);
|
|
82
|
+
if (knownHash === toolsHash) {
|
|
83
|
+
return toolsHash;
|
|
84
|
+
}
|
|
85
|
+
const updatedAt = Date.now();
|
|
86
|
+
getDb()
|
|
87
|
+
.insert(conversationToolSurfaces)
|
|
88
|
+
.values({
|
|
89
|
+
conversationId,
|
|
90
|
+
toolsJson,
|
|
91
|
+
toolsHash,
|
|
92
|
+
delegateIndependentTasks,
|
|
93
|
+
updatedAt,
|
|
94
|
+
})
|
|
95
|
+
.onConflictDoUpdate({
|
|
96
|
+
target: conversationToolSurfaces.conversationId,
|
|
97
|
+
set: { toolsJson, toolsHash, delegateIndependentTasks, updatedAt },
|
|
98
|
+
setWhere: sql`${conversationToolSurfaces.toolsHash} <> excluded.tools_hash`,
|
|
99
|
+
})
|
|
100
|
+
.run();
|
|
101
|
+
return toolsHash;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The surface the conversation's most recent live turn sent, or `null` when
|
|
106
|
+
* no turn has recorded one (or the stored JSON is unreadable).
|
|
107
|
+
*/
|
|
108
|
+
export function getConversationToolSurface(
|
|
109
|
+
conversationId: string,
|
|
110
|
+
): ConversationToolSurface | null {
|
|
111
|
+
const row = getDb()
|
|
112
|
+
.select({
|
|
113
|
+
toolsJson: conversationToolSurfaces.toolsJson,
|
|
114
|
+
delegateIndependentTasks:
|
|
115
|
+
conversationToolSurfaces.delegateIndependentTasks,
|
|
116
|
+
})
|
|
117
|
+
.from(conversationToolSurfaces)
|
|
118
|
+
.where(eq(conversationToolSurfaces.conversationId, conversationId))
|
|
119
|
+
.get();
|
|
120
|
+
if (!row) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const parsed: unknown = JSON.parse(row.toolsJson);
|
|
125
|
+
if (!Array.isArray(parsed)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
tools: parsed as ToolDefinition[],
|
|
130
|
+
delegateIndependentTasks: row.delegateIndependentTasks ?? null,
|
|
131
|
+
};
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY,
|
|
5
|
+
computerUseScreenshotAttachmentIdsFromMetadata,
|
|
6
|
+
} from "./conversation-types.js";
|
|
7
|
+
|
|
8
|
+
describe("computerUseScreenshotAttachmentIdsFromMetadata", () => {
|
|
9
|
+
test("returns valid nonempty attachment ids", () => {
|
|
10
|
+
expect(
|
|
11
|
+
computerUseScreenshotAttachmentIdsFromMetadata({
|
|
12
|
+
[COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY]: [
|
|
13
|
+
"attachment-1",
|
|
14
|
+
"",
|
|
15
|
+
42,
|
|
16
|
+
"attachment-2",
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
).toEqual(["attachment-1", "attachment-2"]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("treats absent and malformed metadata as legacy", () => {
|
|
23
|
+
expect(computerUseScreenshotAttachmentIdsFromMetadata(undefined)).toEqual(
|
|
24
|
+
[],
|
|
25
|
+
);
|
|
26
|
+
expect(
|
|
27
|
+
computerUseScreenshotAttachmentIdsFromMetadata({
|
|
28
|
+
[COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY]: "attachment-1",
|
|
29
|
+
}),
|
|
30
|
+
).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -82,7 +82,10 @@ export function isBackgroundConversationType(
|
|
|
82
82
|
* columns into one label.
|
|
83
83
|
*/
|
|
84
84
|
export type ConversationKind =
|
|
85
|
-
|
|
85
|
+
| "user"
|
|
86
|
+
| "background"
|
|
87
|
+
| "background_memory_consolidation"
|
|
88
|
+
| "scheduled";
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
91
|
* Single classifier shared by the LLM-context routes and the notification
|
|
@@ -270,6 +273,10 @@ export function isVoiceSessionUserMessage(
|
|
|
270
273
|
*/
|
|
271
274
|
export const SIGHT_FRAME_ATTACHMENT_IDS_KEY = "sightFrameAttachmentIds";
|
|
272
275
|
|
|
276
|
+
/** Metadata key naming computer-use screenshots automatically linked to a reply. */
|
|
277
|
+
export const COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY =
|
|
278
|
+
"computerUseScreenshotAttachmentIds";
|
|
279
|
+
|
|
273
280
|
/**
|
|
274
281
|
* The row's attachments that arrived as ambient camera frames, by attachment
|
|
275
282
|
* id. A user attachment carries no metadata of its own, so the marking lives on
|
|
@@ -291,6 +298,23 @@ export function sightFrameAttachmentIdsFromMetadata(
|
|
|
291
298
|
);
|
|
292
299
|
}
|
|
293
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Attachment ids whose placement on this row was automatic computer-use
|
|
303
|
+
* screenshot promotion. Malformed and legacy metadata conservatively yields
|
|
304
|
+
* no marked attachments.
|
|
305
|
+
*/
|
|
306
|
+
export function computerUseScreenshotAttachmentIdsFromMetadata(
|
|
307
|
+
metadata: Record<string, unknown> | null | undefined,
|
|
308
|
+
): string[] {
|
|
309
|
+
const ids = metadata?.[COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY];
|
|
310
|
+
if (!Array.isArray(ids)) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
return ids.filter(
|
|
314
|
+
(id): id is string => typeof id === "string" && id.length > 0,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
294
318
|
/**
|
|
295
319
|
* True when a stored `messages.metadata` column belongs to a standalone
|
|
296
320
|
* ambient camera keep: a row the call's own gate sampled, which no one spoke.
|
|
@@ -360,9 +384,7 @@ export function isDesktopOriginatedUserMessage(
|
|
|
360
384
|
return false;
|
|
361
385
|
}
|
|
362
386
|
const clientOs = parseClientOs((client as Record<string, unknown>).os);
|
|
363
|
-
return
|
|
364
|
-
clientOs === "macos" || clientOs === "windows" || clientOs === "linux"
|
|
365
|
-
);
|
|
387
|
+
return clientOs === "macos" || clientOs === "windows" || clientOs === "linux";
|
|
366
388
|
}
|
|
367
389
|
|
|
368
390
|
/**
|