@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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
|
|
6
|
+
import * as schema from "../schema.js";
|
|
7
|
+
import { migrateCreateConversationToolSurfaces } from "./378-create-conversation-tool-surfaces.js";
|
|
8
|
+
|
|
9
|
+
function createTestDb() {
|
|
10
|
+
const sqlite = new Database(":memory:");
|
|
11
|
+
sqlite.run("PRAGMA foreign_keys = ON");
|
|
12
|
+
// Only the parent table the FK points at.
|
|
13
|
+
sqlite.run(/*sql*/ `CREATE TABLE conversations (id TEXT PRIMARY KEY)`);
|
|
14
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function tableDdl(sqlite: Database): string | undefined {
|
|
18
|
+
const row = sqlite
|
|
19
|
+
.query(
|
|
20
|
+
`SELECT sql FROM sqlite_master WHERE type='table' AND name='conversation_tool_surfaces'`,
|
|
21
|
+
)
|
|
22
|
+
.get() as { sql: string } | null;
|
|
23
|
+
return row?.sql;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function insertSurface(sqlite: Database, conversationId: string): void {
|
|
27
|
+
sqlite
|
|
28
|
+
.query(
|
|
29
|
+
/*sql*/ `INSERT INTO conversation_tool_surfaces (conversation_id, tools_json, tools_hash, updated_at) VALUES (?, ?, ?, ?)`,
|
|
30
|
+
)
|
|
31
|
+
.run(conversationId, "[]", "hash", 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function surfaceCount(sqlite: Database): number {
|
|
35
|
+
const row = sqlite
|
|
36
|
+
.query(`SELECT COUNT(*) AS n FROM conversation_tool_surfaces`)
|
|
37
|
+
.get() as { n: number };
|
|
38
|
+
return row.n;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("migration 378: conversation_tool_surfaces", () => {
|
|
42
|
+
test("creates the table keyed by conversation with a cascading FK", () => {
|
|
43
|
+
const { sqlite, db } = createTestDb();
|
|
44
|
+
expect(tableDdl(sqlite)).toBeUndefined();
|
|
45
|
+
|
|
46
|
+
migrateCreateConversationToolSurfaces(db);
|
|
47
|
+
|
|
48
|
+
const ddl = tableDdl(sqlite);
|
|
49
|
+
expect(ddl).toContain("conversation_id TEXT PRIMARY KEY");
|
|
50
|
+
expect(ddl).toContain("REFERENCES conversations(id) ON DELETE CASCADE");
|
|
51
|
+
expect(ddl).toContain("tools_json TEXT NOT NULL");
|
|
52
|
+
expect(ddl).toContain("tools_hash TEXT NOT NULL");
|
|
53
|
+
expect(ddl).toContain("updated_at INTEGER NOT NULL");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("deleting a conversation cascades to its surface row", () => {
|
|
57
|
+
const { sqlite, db } = createTestDb();
|
|
58
|
+
migrateCreateConversationToolSurfaces(db);
|
|
59
|
+
sqlite.query(`INSERT INTO conversations (id) VALUES (?)`).run("conv-1");
|
|
60
|
+
insertSurface(sqlite, "conv-1");
|
|
61
|
+
expect(surfaceCount(sqlite)).toBe(1);
|
|
62
|
+
|
|
63
|
+
sqlite.query(`DELETE FROM conversations WHERE id = ?`).run("conv-1");
|
|
64
|
+
|
|
65
|
+
expect(surfaceCount(sqlite)).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("is idempotent: a second run keeps the table and its rows", () => {
|
|
69
|
+
const { sqlite, db } = createTestDb();
|
|
70
|
+
migrateCreateConversationToolSurfaces(db);
|
|
71
|
+
sqlite.query(`INSERT INTO conversations (id) VALUES (?)`).run("conv-1");
|
|
72
|
+
insertSurface(sqlite, "conv-1");
|
|
73
|
+
|
|
74
|
+
expect(() => migrateCreateConversationToolSurfaces(db)).not.toThrow();
|
|
75
|
+
|
|
76
|
+
expect(surfaceCount(sqlite)).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
const TABLE = "conversation_tool_surfaces";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create `conversation_tool_surfaces`: one row per conversation holding the
|
|
7
|
+
* tool definitions its most recent live turn sent to the provider, keyed by
|
|
8
|
+
* conversation with a content hash so an unchanged surface is never rewritten.
|
|
9
|
+
*
|
|
10
|
+
* Its own table rather than a column on `conversations` because the payload
|
|
11
|
+
* is a JSON array of every tool definition (tens of kilobytes) that only fork
|
|
12
|
+
* wakes read, while `conversations` rows are read whole on every hot path.
|
|
13
|
+
* The row cascades with its conversation, so a deleted conversation leaves
|
|
14
|
+
* no surface behind.
|
|
15
|
+
*
|
|
16
|
+
* Idempotent via IF NOT EXISTS.
|
|
17
|
+
*/
|
|
18
|
+
export function migrateCreateConversationToolSurfaces(
|
|
19
|
+
database: DrizzleDb,
|
|
20
|
+
): void {
|
|
21
|
+
database.run(
|
|
22
|
+
`CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
23
|
+
conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
|
|
24
|
+
tools_json TEXT NOT NULL,
|
|
25
|
+
tools_hash TEXT NOT NULL,
|
|
26
|
+
updated_at INTEGER NOT NULL
|
|
27
|
+
)`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
|
|
6
|
+
import * as schema from "../schema.js";
|
|
7
|
+
import { migrateOAuthProvidersResponseOkField } from "./379-oauth-providers-response-ok-field.js";
|
|
8
|
+
|
|
9
|
+
function createTestDb(options: { withTable?: boolean } = {}) {
|
|
10
|
+
const sqlite = new Database(":memory:");
|
|
11
|
+
if (options.withTable !== false) {
|
|
12
|
+
// Pre-379 shape: only the columns the migration and tests touch.
|
|
13
|
+
sqlite.exec(/*sql*/ `
|
|
14
|
+
CREATE TABLE oauth_providers (
|
|
15
|
+
provider_key TEXT PRIMARY KEY,
|
|
16
|
+
identity_ok_field TEXT,
|
|
17
|
+
created_at INTEGER NOT NULL,
|
|
18
|
+
updated_at INTEGER NOT NULL
|
|
19
|
+
);
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function columnNames(sqlite: Database): string[] {
|
|
26
|
+
return (
|
|
27
|
+
sqlite.query("PRAGMA table_info(oauth_providers)").all() as Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
}>
|
|
30
|
+
).map((c) => c.name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("migration 379: oauth_providers.response_ok_field", () => {
|
|
34
|
+
test("adds the nullable column beside the identity one", () => {
|
|
35
|
+
const { sqlite, db } = createTestDb();
|
|
36
|
+
expect(columnNames(sqlite)).not.toContain("response_ok_field");
|
|
37
|
+
|
|
38
|
+
migrateOAuthProvidersResponseOkField(db);
|
|
39
|
+
|
|
40
|
+
const column = (
|
|
41
|
+
sqlite.query("PRAGMA table_info(oauth_providers)").all() as Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
notnull: number;
|
|
44
|
+
}>
|
|
45
|
+
).find((c) => c.name === "response_ok_field");
|
|
46
|
+
expect(column).toBeDefined();
|
|
47
|
+
expect(column?.notnull).toBe(0);
|
|
48
|
+
expect(columnNames(sqlite)).toContain("identity_ok_field");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("rows written before the column existed read back null", () => {
|
|
52
|
+
// A provider that declared nothing is judged by status alone, which is
|
|
53
|
+
// what it was before the column existed.
|
|
54
|
+
const { sqlite, db } = createTestDb();
|
|
55
|
+
sqlite.exec(/*sql*/ `
|
|
56
|
+
INSERT INTO oauth_providers (provider_key, identity_ok_field, created_at, updated_at)
|
|
57
|
+
VALUES ('slack', 'ok', 1000, 1000)
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
migrateOAuthProvidersResponseOkField(db);
|
|
61
|
+
|
|
62
|
+
const row = sqlite
|
|
63
|
+
.query(
|
|
64
|
+
"SELECT response_ok_field FROM oauth_providers WHERE provider_key = 'slack'",
|
|
65
|
+
)
|
|
66
|
+
.get() as { response_ok_field: unknown };
|
|
67
|
+
expect(row.response_ok_field).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("is idempotent: re-run is a no-op", () => {
|
|
71
|
+
const { sqlite, db } = createTestDb();
|
|
72
|
+
|
|
73
|
+
migrateOAuthProvidersResponseOkField(db);
|
|
74
|
+
expect(() => migrateOAuthProvidersResponseOkField(db)).not.toThrow();
|
|
75
|
+
|
|
76
|
+
expect(
|
|
77
|
+
columnNames(sqlite).filter((n) => n === "response_ok_field"),
|
|
78
|
+
).toHaveLength(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("a missing table is a failure, never a silent success", () => {
|
|
82
|
+
// The runner checkpoints a throwing step as failed and retries it, and
|
|
83
|
+
// the registry defers this step behind the table's own. Swallowing the
|
|
84
|
+
// error here would checkpoint the step as done against no table, and the
|
|
85
|
+
// column would never be added.
|
|
86
|
+
// The driver wraps SQLite's "no such table" in its own message, so the
|
|
87
|
+
// assertion is that the step throws at all.
|
|
88
|
+
const { db } = createTestDb({ withTable: false });
|
|
89
|
+
|
|
90
|
+
expect(() => migrateOAuthProvidersResponseOkField(db)).toThrow();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = "oauth_providers";
|
|
5
|
+
const COLUMN_NAME = "response_ok_field";
|
|
6
|
+
const COLUMN_DEFINITION = "response_ok_field TEXT";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Add `response_ok_field` to `oauth_providers` (migration 149): the dot path
|
|
10
|
+
* of the boolean a provider's API puts in every response body to report
|
|
11
|
+
* success (Slack: `ok`). Its own column, distinct from `identity_ok_field`,
|
|
12
|
+
* because the two declare different facts: the identity field governs the
|
|
13
|
+
* one call the provider row names, this one governs any request made with
|
|
14
|
+
* the credential.
|
|
15
|
+
*
|
|
16
|
+
* Nullable with no backfill: seeding upserts the value for the built-in rows
|
|
17
|
+
* on the next start, and a custom provider that declares nothing is judged by
|
|
18
|
+
* status alone, which is what it was before the column existed.
|
|
19
|
+
*
|
|
20
|
+
* Idempotent: guarded with `tableHasColumn` so a crash between the `ALTER
|
|
21
|
+
* TABLE` and the checkpoint write doesn't cause a duplicate-column error on
|
|
22
|
+
* the next boot. A missing table is not swallowed: the registry declares the
|
|
23
|
+
* dependency on the table's own step, so this one is deferred rather than
|
|
24
|
+
* checkpointed as done against no table.
|
|
25
|
+
*/
|
|
26
|
+
export function migrateOAuthProvidersResponseOkField(
|
|
27
|
+
database: DrizzleDb,
|
|
28
|
+
): void {
|
|
29
|
+
if (!tableHasColumn(database, TABLE_NAME, COLUMN_NAME)) {
|
|
30
|
+
database.run(`ALTER TABLE ${TABLE_NAME} ADD COLUMN ${COLUMN_DEFINITION}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
|
|
6
|
+
import * as schema from "../schema.js";
|
|
7
|
+
import { migrateCreateConversationToolSurfaces } from "./378-create-conversation-tool-surfaces.js";
|
|
8
|
+
import { migrateConversationToolSurfacesDelegateIndependentTasks } from "./380-conversation-tool-surfaces-delegate-independent-tasks.js";
|
|
9
|
+
|
|
10
|
+
function createTestDb(options: { withTable?: boolean } = {}) {
|
|
11
|
+
const sqlite = new Database(":memory:");
|
|
12
|
+
sqlite.run("PRAGMA foreign_keys = ON");
|
|
13
|
+
// Only the parent table the FK points at.
|
|
14
|
+
sqlite.run(/*sql*/ `CREATE TABLE conversations (id TEXT PRIMARY KEY)`);
|
|
15
|
+
const db = drizzle(sqlite, { schema });
|
|
16
|
+
if (options.withTable !== false) {
|
|
17
|
+
// Pre-380 shape: the table as migration 378 creates it.
|
|
18
|
+
migrateCreateConversationToolSurfaces(db);
|
|
19
|
+
}
|
|
20
|
+
return { sqlite, db };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function columns(sqlite: Database): Array<{ name: string; notnull: number }> {
|
|
24
|
+
return sqlite
|
|
25
|
+
.query("PRAGMA table_info(conversation_tool_surfaces)")
|
|
26
|
+
.all() as Array<{ name: string; notnull: number }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("migration 380: conversation_tool_surfaces.delegate_independent_tasks", () => {
|
|
30
|
+
test("adds the nullable column", () => {
|
|
31
|
+
const { sqlite, db } = createTestDb();
|
|
32
|
+
expect(columns(sqlite).map((c) => c.name)).not.toContain(
|
|
33
|
+
"delegate_independent_tasks",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
migrateConversationToolSurfacesDelegateIndependentTasks(db);
|
|
37
|
+
|
|
38
|
+
const column = columns(sqlite).find(
|
|
39
|
+
(c) => c.name === "delegate_independent_tasks",
|
|
40
|
+
);
|
|
41
|
+
expect(column).toBeDefined();
|
|
42
|
+
expect(column?.notnull).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("rows written before the column existed read back null", () => {
|
|
46
|
+
// A fork replaying such a row derives the section for itself, which is
|
|
47
|
+
// what it did before the column existed.
|
|
48
|
+
const { sqlite, db } = createTestDb();
|
|
49
|
+
sqlite.query(`INSERT INTO conversations (id) VALUES (?)`).run("conv-1");
|
|
50
|
+
sqlite
|
|
51
|
+
.query(
|
|
52
|
+
/*sql*/ `INSERT INTO conversation_tool_surfaces (conversation_id, tools_json, tools_hash, updated_at) VALUES (?, ?, ?, ?)`,
|
|
53
|
+
)
|
|
54
|
+
.run("conv-1", "[]", "hash", 1);
|
|
55
|
+
|
|
56
|
+
migrateConversationToolSurfacesDelegateIndependentTasks(db);
|
|
57
|
+
|
|
58
|
+
const row = sqlite
|
|
59
|
+
.query(
|
|
60
|
+
"SELECT delegate_independent_tasks FROM conversation_tool_surfaces WHERE conversation_id = 'conv-1'",
|
|
61
|
+
)
|
|
62
|
+
.get() as { delegate_independent_tasks: unknown };
|
|
63
|
+
expect(row.delegate_independent_tasks).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("is idempotent: re-run is a no-op", () => {
|
|
67
|
+
const { sqlite, db } = createTestDb();
|
|
68
|
+
|
|
69
|
+
migrateConversationToolSurfacesDelegateIndependentTasks(db);
|
|
70
|
+
expect(() =>
|
|
71
|
+
migrateConversationToolSurfacesDelegateIndependentTasks(db),
|
|
72
|
+
).not.toThrow();
|
|
73
|
+
|
|
74
|
+
expect(
|
|
75
|
+
columns(sqlite).filter((c) => c.name === "delegate_independent_tasks"),
|
|
76
|
+
).toHaveLength(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("a missing table is a failure, never a silent success", () => {
|
|
80
|
+
// The runner checkpoints a throwing step as failed and retries it, and
|
|
81
|
+
// the registry defers this step behind the table's own. Swallowing the
|
|
82
|
+
// error here would checkpoint the step as done against no table, and the
|
|
83
|
+
// column would never be added.
|
|
84
|
+
const { db } = createTestDb({ withTable: false });
|
|
85
|
+
|
|
86
|
+
expect(() =>
|
|
87
|
+
migrateConversationToolSurfacesDelegateIndependentTasks(db),
|
|
88
|
+
).toThrow();
|
|
89
|
+
});
|
|
90
|
+
});
|
package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = "conversation_tool_surfaces";
|
|
5
|
+
const COLUMN_NAME = "delegate_independent_tasks";
|
|
6
|
+
const COLUMN_DEFINITION = "delegate_independent_tasks INTEGER";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Add `delegate_independent_tasks` to `conversation_tool_surfaces`: whether
|
|
10
|
+
* the system prompt of the turn that recorded the surface rendered the
|
|
11
|
+
* parallel-delegation section (`01-parallel-tasks`). The prompt derives that
|
|
12
|
+
* section from the same resolved tool surface, so a fork wake replaying the
|
|
13
|
+
* surface needs the rendered state too: the section sits in the system
|
|
14
|
+
* prompt, the second tier of the provider cache prefix, and a fork that
|
|
15
|
+
* derives it from its own restricted scope renders it off where the source
|
|
16
|
+
* rendered it on, missing the source's cached prefix from that byte on.
|
|
17
|
+
*
|
|
18
|
+
* Nullable with no backfill: a row written before the column existed reads
|
|
19
|
+
* null and the fork derives the section for itself, exactly as it did before
|
|
20
|
+
* the column; the next live turn records the state.
|
|
21
|
+
*
|
|
22
|
+
* Idempotent: guarded with `tableHasColumn` so a crash between the `ALTER
|
|
23
|
+
* TABLE` and the checkpoint write doesn't cause a duplicate-column error on
|
|
24
|
+
* the next boot. A missing table is not swallowed: the registry declares the
|
|
25
|
+
* dependency on the table's own step, so this one is deferred rather than
|
|
26
|
+
* checkpointed as done against no table.
|
|
27
|
+
*/
|
|
28
|
+
export function migrateConversationToolSurfacesDelegateIndependentTasks(
|
|
29
|
+
database: DrizzleDb,
|
|
30
|
+
): void {
|
|
31
|
+
if (!tableHasColumn(database, TABLE_NAME, COLUMN_NAME)) {
|
|
32
|
+
database.run(`ALTER TABLE ${TABLE_NAME} ADD COLUMN ${COLUMN_DEFINITION}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
import { conversations } from "./conversations.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The tool definitions a conversation's most recent live turn sent to the
|
|
7
|
+
* provider, serialized exactly as resolved, plus whether that turn's system
|
|
8
|
+
* prompt rendered the parallel-delegation section (which the prompt derives
|
|
9
|
+
* from the same resolved surface and the turn's channel). Written at the
|
|
10
|
+
* agent loop's send boundary when either changes; read by fork wakes that
|
|
11
|
+
* replay both so their provider prompt-cache prefix matches the source's live
|
|
12
|
+
* turns. Cascades with its conversation.
|
|
13
|
+
*/
|
|
14
|
+
export const conversationToolSurfaces = sqliteTable(
|
|
15
|
+
"conversation_tool_surfaces",
|
|
16
|
+
{
|
|
17
|
+
conversationId: text("conversation_id")
|
|
18
|
+
.primaryKey()
|
|
19
|
+
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
20
|
+
toolsJson: text("tools_json").notNull(),
|
|
21
|
+
toolsHash: text("tools_hash").notNull(),
|
|
22
|
+
/**
|
|
23
|
+
* Whether the turn's system prompt rendered the `01-parallel-tasks`
|
|
24
|
+
* section. Null when unknown: the turn ran on a system-prompt override,
|
|
25
|
+
* or the row predates the column.
|
|
26
|
+
*/
|
|
27
|
+
delegateIndependentTasks: integer("delegate_independent_tasks", {
|
|
28
|
+
mode: "boolean",
|
|
29
|
+
}),
|
|
30
|
+
updatedAt: integer("updated_at").notNull(),
|
|
31
|
+
},
|
|
32
|
+
);
|
|
@@ -5,6 +5,7 @@ export * from "./calls.js";
|
|
|
5
5
|
export * from "./contacts.js";
|
|
6
6
|
export * from "./conversation-groups.js";
|
|
7
7
|
export * from "./conversation-starters.js";
|
|
8
|
+
export * from "./conversation-tool-surfaces.js";
|
|
8
9
|
export * from "./conversations.js";
|
|
9
10
|
export * from "./documents.js";
|
|
10
11
|
export * from "./guardian.js";
|
|
@@ -50,6 +50,7 @@ export const oauthProviders = sqliteTable("oauth_providers", {
|
|
|
50
50
|
identityResponsePaths: text("identity_response_paths"),
|
|
51
51
|
identityFormat: text("identity_format"),
|
|
52
52
|
identityOkField: text("identity_ok_field"),
|
|
53
|
+
responseOkField: text("response_ok_field"),
|
|
53
54
|
featureFlag: text("feature_flag"),
|
|
54
55
|
createdAt: integer("created_at").notNull(),
|
|
55
56
|
updatedAt: integer("updated_at").notNull(),
|
package/src/persistence/steps.ts
CHANGED
|
@@ -485,6 +485,9 @@ import { migrateChannelInboundMessageIdIndex } from "./migrations/374-channel-in
|
|
|
485
485
|
import { migrateCreateChannelOutboundPosts } from "./migrations/375-create-channel-outbound-posts.js";
|
|
486
486
|
import { migrateNotificationDeliveriesCanonicalMessageId } from "./migrations/376-notification-deliveries-canonical-message-id.js";
|
|
487
487
|
import { migrateAddSubagentBudgetStopReason } from "./migrations/377-add-subagent-budget-stop-reason.js";
|
|
488
|
+
import { migrateCreateConversationToolSurfaces } from "./migrations/378-create-conversation-tool-surfaces.js";
|
|
489
|
+
import { migrateOAuthProvidersResponseOkField } from "./migrations/379-oauth-providers-response-ok-field.js";
|
|
490
|
+
import { migrateConversationToolSurfacesDelegateIndependentTasks } from "./migrations/380-conversation-tool-surfaces-delegate-independent-tasks.js";
|
|
488
491
|
import type { MigrationStep } from "./migrations/run-migrations.js";
|
|
489
492
|
|
|
490
493
|
export const migrationSteps: MigrationStep[] = [
|
|
@@ -1606,9 +1609,27 @@ export const migrationSteps: MigrationStep[] = [
|
|
|
1606
1609
|
{
|
|
1607
1610
|
name: "migrateAddSubagentBudgetStopReason",
|
|
1608
1611
|
run: migrateAddSubagentBudgetStopReason,
|
|
1609
|
-
// The column guard
|
|
1610
|
-
// must be checkpointed first or a repair flow
|
|
1611
|
-
// this
|
|
1612
|
+
// The column guard reads the table's columns and the ALTER throws on a
|
|
1613
|
+
// missing table, so the table must be checkpointed first or a repair flow
|
|
1614
|
+
// that failed to create it would fail this step needlessly.
|
|
1612
1615
|
dependsOn: ["migrateCreateSubagentsTable"],
|
|
1613
1616
|
},
|
|
1617
|
+
migrateCreateConversationToolSurfaces,
|
|
1618
|
+
{
|
|
1619
|
+
name: "migrateOAuthProvidersResponseOkField",
|
|
1620
|
+
run: migrateOAuthProvidersResponseOkField,
|
|
1621
|
+
// The column guard reads the table's columns, and an `ALTER` on a missing
|
|
1622
|
+
// table throws, so the table must be checkpointed first or a repair flow
|
|
1623
|
+
// that failed to create it would either fail this step needlessly or, if
|
|
1624
|
+
// the error were swallowed, checkpoint it as done against no table.
|
|
1625
|
+
dependsOn: ["createOAuthTables"],
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
name: "migrateConversationToolSurfacesDelegateIndependentTasks",
|
|
1629
|
+
run: migrateConversationToolSurfacesDelegateIndependentTasks,
|
|
1630
|
+
// Same column-guard shape as the step above: the guard reads the table's
|
|
1631
|
+
// columns and the `ALTER` throws on a missing table, so the table's own
|
|
1632
|
+
// step must be checkpointed first.
|
|
1633
|
+
dependsOn: ["migrateCreateConversationToolSurfaces"],
|
|
1634
|
+
},
|
|
1614
1635
|
];
|
|
@@ -284,13 +284,20 @@ export async function runConversationTurn(
|
|
|
284
284
|
const { resolveChannelCapabilities } =
|
|
285
285
|
await import("../daemon/conversation-runtime-assembly.js");
|
|
286
286
|
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
// below) without requiring a client to approve prompts.
|
|
287
|
+
// Channel-addressed turns are inbound: the gateway classifies the sender
|
|
288
|
+
// and the turn runs only if that actor clears the channel admission floor.
|
|
289
|
+
// Internal plugin jobs (no channel address) use guardian trust because they
|
|
290
|
+
// are assistant-owned work, not an external sender.
|
|
292
291
|
const { INTERNAL_GUARDIAN_TRUST_CONTEXT } =
|
|
293
292
|
await import("../daemon/trust-context.js");
|
|
293
|
+
const { resolvePluginChannelTurnTrust } =
|
|
294
|
+
await import("./plugin-channel-turn-trust.js");
|
|
295
|
+
const { prepareChannelInboundContent } =
|
|
296
|
+
await import("../runtime/routes/inbound-stages/inbound-content-prep.js");
|
|
297
|
+
|
|
298
|
+
const trustContext = options.channel
|
|
299
|
+
? await resolvePluginChannelTurnTrust(options.channel)
|
|
300
|
+
: INTERNAL_GUARDIAN_TRUST_CONTEXT;
|
|
294
301
|
|
|
295
302
|
// A channel address resolves through the same binding an inbound message
|
|
296
303
|
// uses, so a turn addressed by chat lands in that chat's conversation
|
|
@@ -311,7 +318,7 @@ export async function runConversationTurn(
|
|
|
311
318
|
? !channelConversation.created
|
|
312
319
|
: getConversation(conversationId) != null;
|
|
313
320
|
const conversation = await getOrCreateConversation(conversationId, {
|
|
314
|
-
trustContext
|
|
321
|
+
trustContext,
|
|
315
322
|
...(options.conversationType
|
|
316
323
|
? { conversationType: options.conversationType }
|
|
317
324
|
: {}),
|
|
@@ -336,10 +343,23 @@ export async function runConversationTurn(
|
|
|
336
343
|
|
|
337
344
|
// Convert ContentBlock[] input to the text + attachments shape the
|
|
338
345
|
// conversation's processMessage path expects.
|
|
339
|
-
const { text, attachments } = extractContentAndAttachments(
|
|
346
|
+
const { text: rawText, attachments } = extractContentAndAttachments(
|
|
340
347
|
options.content,
|
|
341
348
|
resolveMediaSourceData,
|
|
342
349
|
);
|
|
350
|
+
const inboundContent = options.channel
|
|
351
|
+
? prepareChannelInboundContent({
|
|
352
|
+
trimmedContent: rawText,
|
|
353
|
+
trustClass: trustContext.trustClass,
|
|
354
|
+
sourceChannel: options.channel.sourceChannel,
|
|
355
|
+
requesterIdentifier:
|
|
356
|
+
options.channel.username ??
|
|
357
|
+
options.channel.externalUserId ??
|
|
358
|
+
undefined,
|
|
359
|
+
})
|
|
360
|
+
: { content: rawText, displayContent: undefined };
|
|
361
|
+
const text = inboundContent.content;
|
|
362
|
+
const displayContent = inboundContent.displayContent;
|
|
343
363
|
|
|
344
364
|
// The channel this turn speaks on. Runtime assembly reads the per-turn
|
|
345
365
|
// context first and falls back to the conversation's `originChannel`, then
|
|
@@ -409,6 +429,8 @@ export async function runConversationTurn(
|
|
|
409
429
|
requestId,
|
|
410
430
|
isInteractive: false,
|
|
411
431
|
metadata,
|
|
432
|
+
trustContext,
|
|
433
|
+
...(displayContent ? { displayContent } : {}),
|
|
412
434
|
});
|
|
413
435
|
if (enqueueResult.rejected) {
|
|
414
436
|
throw new Error(
|
|
@@ -432,6 +454,8 @@ export async function runConversationTurn(
|
|
|
432
454
|
onEvent,
|
|
433
455
|
isInteractive: false,
|
|
434
456
|
metadata,
|
|
457
|
+
trustContext,
|
|
458
|
+
...(displayContent ? { displayContent } : {}),
|
|
435
459
|
...(options.callSite ? { callSite: options.callSite } : {}),
|
|
436
460
|
});
|
|
437
461
|
|
package/src/plugin-api/index.ts
CHANGED
|
@@ -286,6 +286,11 @@ export {
|
|
|
286
286
|
// writes files under the workspace (e.g. its own `plugins/<name>/data/`
|
|
287
287
|
// directory) resolves them against this instead of hardcoding a base path.
|
|
288
288
|
export { getWorkspaceDir } from "../util/platform.js";
|
|
289
|
+
// `String.prototype.slice` that never cuts a UTF-16 surrogate pair in half.
|
|
290
|
+
// Any text a plugin truncates by character budget and hands to a model must
|
|
291
|
+
// go through this: an orphaned half is invalid UTF-16 that strict provider
|
|
292
|
+
// parsers reject.
|
|
293
|
+
export { safeStringSlice } from "../util/unicode.js";
|
|
289
294
|
// Declarative help for the top-level `assistant` CLI commands that have adopted
|
|
290
295
|
// the static-help split. Plugins (e.g. the memory capability indexer) read this
|
|
291
296
|
// to embed CLI command capabilities without importing the CLI action graph.
|
|
@@ -359,7 +364,8 @@ export {
|
|
|
359
364
|
stringifyMessageContent,
|
|
360
365
|
} from "../persistence/message-content.js";
|
|
361
366
|
// Conversation history — reads and writes on the host conversation store
|
|
362
|
-
// (rows, message history, processing state,
|
|
367
|
+
// (rows, message history, processing state, the recorded wire tool surface,
|
|
368
|
+
// disk-view paths) plus the lexical
|
|
363
369
|
// message-search surface. Every operation takes explicit parameters; nothing
|
|
364
370
|
// is resolved from config. Async because the facade loads the DB store graph
|
|
365
371
|
// lazily on first call.
|
|
@@ -371,7 +377,9 @@ export {
|
|
|
371
377
|
getConversation,
|
|
372
378
|
getConversationDirPath,
|
|
373
379
|
getConversationProcessingStartedAt,
|
|
380
|
+
getConversationToolSurface,
|
|
374
381
|
getMessages,
|
|
382
|
+
getRecordedConversationToolSurface,
|
|
375
383
|
hasLexicalTokens,
|
|
376
384
|
isConversationProcessing,
|
|
377
385
|
listConversations,
|
|
@@ -380,6 +388,7 @@ export {
|
|
|
380
388
|
syncMessageToDisk,
|
|
381
389
|
updateMessageMetadata,
|
|
382
390
|
} from "../persistence/conversation-plugin-facade.js";
|
|
391
|
+
export type { ConversationToolSurface } from "../persistence/conversation-tool-surface.js";
|
|
383
392
|
// System cards: a transcript notice authored by the daemon rather than the
|
|
384
393
|
// assistant persona, for telling the user something a turn did to their input
|
|
385
394
|
// that the model's reply cannot explain (e.g. an attachment that could not be
|
|
@@ -438,6 +447,7 @@ export type {
|
|
|
438
447
|
RunConversationTurnResult,
|
|
439
448
|
} from "./conversation-turn.js";
|
|
440
449
|
export { runConversationTurn } from "./conversation-turn.js";
|
|
450
|
+
export { PluginTurnNotAdmittedError } from "./plugin-channel-turn-trust.js";
|
|
441
451
|
// Live voice — drive a single client's real-time voice session (STT → agent
|
|
442
452
|
// turn → TTS, with server-VAD turn-taking, pauses, and barge-in) over a
|
|
443
453
|
// transport the plugin owns. The plugin brings only a `send` callback (e.g.
|