@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
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
* route captured calls back to the originating conversation's probe arrays.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
21
|
+
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
|
|
22
22
|
|
|
23
23
|
import type { DiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
|
|
24
|
+
import { desktopAutomationLease } from "../../desktop/desktop-automation-lease.js";
|
|
24
25
|
|
|
25
26
|
// ── Per-conversation capture registry ────────────────────────────────
|
|
26
27
|
//
|
|
@@ -501,6 +502,13 @@ function makeWakeConversation(options: {
|
|
|
501
502
|
`tools:${snapshotAllowedTools()?.join(",") ?? "all"}`,
|
|
502
503
|
);
|
|
503
504
|
},
|
|
505
|
+
// Mirrors Conversation.setPreactivatedSkillIds; the wake's tool-scope
|
|
506
|
+
// restore calls it, and a double without it throws inside the restore
|
|
507
|
+
// closure, leaving every field restored after it untouched.
|
|
508
|
+
preactivatedSkillIds: undefined as string[] | undefined,
|
|
509
|
+
setPreactivatedSkillIds(ids: string[] | undefined) {
|
|
510
|
+
this.preactivatedSkillIds = ids;
|
|
511
|
+
},
|
|
504
512
|
get wakePersonaOverride() {
|
|
505
513
|
return wakePersonaOverride;
|
|
506
514
|
},
|
|
@@ -1357,6 +1365,50 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
1357
1365
|
expect(conversation.toolContextPin).toBeUndefined();
|
|
1358
1366
|
});
|
|
1359
1367
|
|
|
1368
|
+
test("applies wireToolDefinitions and the delegation state alongside the allowlist and restores both after the wake", async () => {
|
|
1369
|
+
const replay = [
|
|
1370
|
+
{ name: "remember", description: "Save", input_schema: {} },
|
|
1371
|
+
{ name: "bell_jingle", description: "Ring", input_schema: {} },
|
|
1372
|
+
];
|
|
1373
|
+
let replayDuringRun: unknown;
|
|
1374
|
+
let delegationDuringRun: unknown;
|
|
1375
|
+
const conversation = makeWakeConversation({
|
|
1376
|
+
runImpl: async (input) => {
|
|
1377
|
+
replayDuringRun = conversation.wireToolReplay;
|
|
1378
|
+
delegationDuringRun = conversation.delegateIndependentTasksReplay;
|
|
1379
|
+
return runResult([
|
|
1380
|
+
...input,
|
|
1381
|
+
{ role: "assistant", content: [{ type: "text", text: "Saved." }] },
|
|
1382
|
+
]);
|
|
1383
|
+
},
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
const result = await wakeAgentForOpportunity(
|
|
1387
|
+
{
|
|
1388
|
+
conversationId: conversation.conversationId,
|
|
1389
|
+
hint: "review for memories",
|
|
1390
|
+
source: "memory-retrospective",
|
|
1391
|
+
allowedTools: ["remember"],
|
|
1392
|
+
toolGateMode: "execution",
|
|
1393
|
+
wireToolDefinitions: replay,
|
|
1394
|
+
delegateIndependentTasks: true,
|
|
1395
|
+
},
|
|
1396
|
+
{ resolveTarget: async () => conversation },
|
|
1397
|
+
);
|
|
1398
|
+
|
|
1399
|
+
expect(result).toEqual({ invoked: true, producedToolCalls: false });
|
|
1400
|
+
// The replay array is live on the conversation for the duration of the
|
|
1401
|
+
// run (the tool resolver returns it as the wire array), as is the
|
|
1402
|
+
// delegation-section state the source's prompt rendered (the prompt
|
|
1403
|
+
// build reads it)...
|
|
1404
|
+
expect(replayDuringRun).toEqual(replay);
|
|
1405
|
+
expect(delegationDuringRun).toBe(true);
|
|
1406
|
+
// ...and both are cleared alongside the allowlist + gate mode after the
|
|
1407
|
+
// wake.
|
|
1408
|
+
expect(conversation.wireToolReplay).toBeUndefined();
|
|
1409
|
+
expect(conversation.delegateIndependentTasksReplay).toBeUndefined();
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1360
1412
|
test("defaults to the wire gate mode when toolGateMode is absent", async () => {
|
|
1361
1413
|
let gateModeDuringRun: string | undefined;
|
|
1362
1414
|
const conversation = makeWakeConversation({
|
|
@@ -1517,6 +1569,47 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
1517
1569
|
expect(conversation.pushedMessages[2]).toEqual(followupAssistant);
|
|
1518
1570
|
});
|
|
1519
1571
|
|
|
1572
|
+
for (const fails of [false, true]) {
|
|
1573
|
+
test(`releases desktop control before a ${fails ? "failed" : "completed"} wake drains the queue`, async () => {
|
|
1574
|
+
const conversation = makeWakeConversation({
|
|
1575
|
+
scriptedAssistant: {
|
|
1576
|
+
role: "assistant",
|
|
1577
|
+
content: [{ type: "text", text: "reply" }],
|
|
1578
|
+
},
|
|
1579
|
+
...(fails
|
|
1580
|
+
? {
|
|
1581
|
+
runImpl: async () => {
|
|
1582
|
+
throw new Error("wake failed");
|
|
1583
|
+
},
|
|
1584
|
+
}
|
|
1585
|
+
: {}),
|
|
1586
|
+
});
|
|
1587
|
+
const release = spyOn(desktopAutomationLease, "releaseForConversation");
|
|
1588
|
+
const setProcessing = conversation.setProcessing.bind(conversation);
|
|
1589
|
+
conversation.setProcessing = (processing) => {
|
|
1590
|
+
if (!processing) {
|
|
1591
|
+
expect(release).toHaveBeenCalledWith(conversation.conversationId);
|
|
1592
|
+
}
|
|
1593
|
+
setProcessing(processing);
|
|
1594
|
+
};
|
|
1595
|
+
try {
|
|
1596
|
+
const result = await wakeAgentForOpportunity(
|
|
1597
|
+
{
|
|
1598
|
+
conversationId: conversation.conversationId,
|
|
1599
|
+
hint: "x",
|
|
1600
|
+
source: "unit-test",
|
|
1601
|
+
},
|
|
1602
|
+
{ resolveTarget: async () => conversation },
|
|
1603
|
+
);
|
|
1604
|
+
expect(result.invoked).toBe(!fails);
|
|
1605
|
+
expect(release).toHaveBeenCalledTimes(1);
|
|
1606
|
+
expect(conversation.drainQueueCalls).toBe(1);
|
|
1607
|
+
} finally {
|
|
1608
|
+
release.mockRestore();
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1520
1613
|
test("marks processing true during the run and false afterwards", async () => {
|
|
1521
1614
|
const conversation = makeWakeConversation({
|
|
1522
1615
|
baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
@@ -107,6 +107,7 @@ import {
|
|
|
107
107
|
persistWakeTriggerMessage,
|
|
108
108
|
scopeWakeAllowedTools,
|
|
109
109
|
} from "../daemon/wake-conversation-ops.js";
|
|
110
|
+
import { desktopAutomationLease } from "../desktop/desktop-automation-lease.js";
|
|
110
111
|
import {
|
|
111
112
|
recordCompactionEndBestEffort,
|
|
112
113
|
recordCompactionStartBestEffort,
|
|
@@ -118,7 +119,7 @@ import {
|
|
|
118
119
|
setAgentLoopExitReasonOnLatestLog,
|
|
119
120
|
} from "../persistence/llm-request-log-store.js";
|
|
120
121
|
import type { SystemPromptPersonaOverride } from "../prompts/system-prompt.js";
|
|
121
|
-
import type { Message } from "../providers/types.js";
|
|
122
|
+
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
122
123
|
import {
|
|
123
124
|
type UntrustedContentSource,
|
|
124
125
|
wrapUntrustedContent,
|
|
@@ -350,6 +351,27 @@ export interface WakeOptions {
|
|
|
350
351
|
* skill-management authoring tools callable directly.
|
|
351
352
|
*/
|
|
352
353
|
preactivateSkillIds?: readonly string[];
|
|
354
|
+
/**
|
|
355
|
+
* Tool definitions to send verbatim as the wake's wire tool array, in place
|
|
356
|
+
* of the ones the conversation would resolve for itself. Used by fork-based
|
|
357
|
+
* memory retrospectives to replay the SOURCE conversation's recorded surface
|
|
358
|
+
* (`getRecordedConversationToolSurface`) so the provider prompt-cache prefix matches
|
|
359
|
+
* the source's live turns byte for byte. Definitions only: what may execute
|
|
360
|
+
* is still decided by `allowedTools` and the turn's own active set. Applied
|
|
361
|
+
* and restored alongside `allowedTools`; ignored when `allowedTools` is
|
|
362
|
+
* absent.
|
|
363
|
+
*/
|
|
364
|
+
wireToolDefinitions?: readonly ToolDefinition[];
|
|
365
|
+
/**
|
|
366
|
+
* Whether the source's recorded surface rendered the system prompt's
|
|
367
|
+
* parallel-delegation section, replayed into the wake's prompt so that
|
|
368
|
+
* tier of the provider cache prefix matches the source's too (the wake's
|
|
369
|
+
* own scope cannot spawn, so deriving it would render the section off where
|
|
370
|
+
* an interactive source rendered it on). Prompt only: a spawn attempt is
|
|
371
|
+
* still rejected at execution. Applied and restored alongside
|
|
372
|
+
* `allowedTools`; ignored when `allowedTools` is absent.
|
|
373
|
+
*/
|
|
374
|
+
delegateIndependentTasks?: boolean;
|
|
353
375
|
/**
|
|
354
376
|
* Explicit persona/channel slugs for the wake's system-prompt build,
|
|
355
377
|
* applied to the conversation for the duration of the run and restored
|
|
@@ -1410,6 +1432,7 @@ export async function wakeAgentForOpportunity(
|
|
|
1410
1432
|
* is one function rather than a rebuild bolted onto either half.
|
|
1411
1433
|
*/
|
|
1412
1434
|
const restoreWakeTurnScope = (): void => {
|
|
1435
|
+
desktopAutomationLease.releaseForConversation(conversationId);
|
|
1413
1436
|
restoreWakeAllowedTools();
|
|
1414
1437
|
clearWakePersonaOverride();
|
|
1415
1438
|
syncWakeLoopSystemPrompt();
|
|
@@ -1422,9 +1445,13 @@ export async function wakeAgentForOpportunity(
|
|
|
1422
1445
|
restoreWakeToolScope = scopeWakeAllowedTools(
|
|
1423
1446
|
conversation,
|
|
1424
1447
|
new Set(opts.allowedTools),
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1448
|
+
{
|
|
1449
|
+
gateMode: opts.toolGateMode,
|
|
1450
|
+
toolContextPin: opts.toolContextPin,
|
|
1451
|
+
preactivateSkillIds: opts.preactivateSkillIds,
|
|
1452
|
+
wireToolDefinitions: opts.wireToolDefinitions,
|
|
1453
|
+
delegateIndependentTasks: opts.delegateIndependentTasks,
|
|
1454
|
+
},
|
|
1428
1455
|
);
|
|
1429
1456
|
return true;
|
|
1430
1457
|
} catch (err) {
|
|
@@ -16,21 +16,6 @@ import {
|
|
|
16
16
|
isApprovalAction,
|
|
17
17
|
} from "./channel-approval-types.js";
|
|
18
18
|
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Constants
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Legacy actions that map to canonical ones during client rollout.
|
|
25
|
-
* All temporal/persistent approval variants collapse to approve_once.
|
|
26
|
-
* Keep until all clients are updated and no in-flight buttons remain.
|
|
27
|
-
*/
|
|
28
|
-
const LEGACY_ACTION_MAP: Record<string, string> = {
|
|
29
|
-
approve_10m: "approve_once",
|
|
30
|
-
approve_conversation: "approve_once",
|
|
31
|
-
approve_always: "approve_once",
|
|
32
|
-
};
|
|
33
|
-
|
|
34
19
|
// ---------------------------------------------------------------------------
|
|
35
20
|
// Types
|
|
36
21
|
// ---------------------------------------------------------------------------
|
|
@@ -98,8 +83,8 @@ export async function processGuardianDecision(
|
|
|
98
83
|
): Promise<ProcessGuardianDecisionResult> {
|
|
99
84
|
const { requestId, conversationId, channel, actorContext } = params;
|
|
100
85
|
|
|
101
|
-
// 1.
|
|
102
|
-
const action =
|
|
86
|
+
// 1. Validate the action
|
|
87
|
+
const action = params.action;
|
|
103
88
|
if (!isApprovalAction(action)) {
|
|
104
89
|
return {
|
|
105
90
|
ok: false,
|
|
@@ -153,12 +153,6 @@ export interface GuardianReplyResult {
|
|
|
153
153
|
// Callback data parser — format: "apr:<requestId>:<action>"
|
|
154
154
|
// ---------------------------------------------------------------------------
|
|
155
155
|
|
|
156
|
-
const LEGACY_CALLBACK_MAP: Record<string, string> = {
|
|
157
|
-
approve_10m: "approve_once",
|
|
158
|
-
approve_conversation: "approve_once",
|
|
159
|
-
approve_always: "approve_once",
|
|
160
|
-
};
|
|
161
|
-
|
|
162
156
|
interface ParsedCallback {
|
|
163
157
|
requestId: string;
|
|
164
158
|
action: ApprovalAction;
|
|
@@ -170,8 +164,7 @@ function parseCallbackAction(data: string): ParsedCallback | null {
|
|
|
170
164
|
return null;
|
|
171
165
|
}
|
|
172
166
|
const requestId = parts[1];
|
|
173
|
-
const
|
|
174
|
-
const action = LEGACY_CALLBACK_MAP[rawAction] ?? rawAction;
|
|
167
|
+
const action = parts.slice(2).join(":");
|
|
175
168
|
if (!requestId || !isApprovalAction(action)) {
|
|
176
169
|
return null;
|
|
177
170
|
}
|
|
@@ -254,9 +254,9 @@ type AllWebSocketData =
|
|
|
254
254
|
|
|
255
255
|
function virtualDesktopEnabled(): boolean {
|
|
256
256
|
try {
|
|
257
|
-
return isVirtualDesktopEnabled(
|
|
257
|
+
return isVirtualDesktopEnabled();
|
|
258
258
|
} catch (err) {
|
|
259
|
-
log.warn({ err }, "Failed to
|
|
259
|
+
log.warn({ err }, "Failed to check desktop stream gate");
|
|
260
260
|
return false;
|
|
261
261
|
}
|
|
262
262
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isConfigArchivePath,
|
|
16
16
|
isCredentialMetadataArchivePath,
|
|
17
17
|
isLegacyPersonaArchivePath,
|
|
18
|
+
isLocalDevRuntimeVersion,
|
|
18
19
|
isWorkspaceNamespacedArchivePath,
|
|
19
20
|
LEGACY_USER_MD_ARCHIVE_PATH,
|
|
20
21
|
partitionWorkspacePreserveSkipDirs,
|
|
@@ -257,4 +258,92 @@ describe("evaluateRuntimeCompatibility", () => {
|
|
|
257
258
|
),
|
|
258
259
|
).toEqual({ ok: true });
|
|
259
260
|
});
|
|
261
|
+
|
|
262
|
+
// A `vel up` minikube assistant image is stamped
|
|
263
|
+
// `<pkg.version>-local.<ts>.<sha>` from the checkout's package.json,
|
|
264
|
+
// which only moves on release cuts, so the minimum bound cannot be
|
|
265
|
+
// decided from it; the explicit maximum ceiling still applies.
|
|
266
|
+
test("local dev runtime build skips the minimum bound", () => {
|
|
267
|
+
expect(
|
|
268
|
+
evaluateRuntimeCompatibility(
|
|
269
|
+
{ min_runtime_version: "0.12.2-staging.4", max_runtime_version: null },
|
|
270
|
+
"0.12.1-local.20260916144827.2f409056e6",
|
|
271
|
+
),
|
|
272
|
+
).toEqual({ ok: true });
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("local dev runtime build within an explicit range passes", () => {
|
|
276
|
+
expect(
|
|
277
|
+
evaluateRuntimeCompatibility(
|
|
278
|
+
{ min_runtime_version: "0.12.2", max_runtime_version: "0.12.5" },
|
|
279
|
+
"0.12.1-local.20260916144827.2f409056e6",
|
|
280
|
+
),
|
|
281
|
+
).toEqual({ ok: true });
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("local dev runtime build above max still fails", () => {
|
|
285
|
+
const compat = {
|
|
286
|
+
min_runtime_version: "0.7.0",
|
|
287
|
+
max_runtime_version: "0.7.5",
|
|
288
|
+
};
|
|
289
|
+
const runtime = "0.12.1-local.20260916144827.2f409056e6";
|
|
290
|
+
expect(evaluateRuntimeCompatibility(compat, runtime)).toEqual({
|
|
291
|
+
ok: false,
|
|
292
|
+
reason: "version_incompatible",
|
|
293
|
+
bundle_compat: compat,
|
|
294
|
+
runtime_version: runtime,
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("local dev runtime build with unparsable max fails open", () => {
|
|
299
|
+
expect(
|
|
300
|
+
evaluateRuntimeCompatibility(
|
|
301
|
+
{ min_runtime_version: "0.7.0", max_runtime_version: "garbage" },
|
|
302
|
+
"0.12.1-local.20260916144827.2f409056e6",
|
|
303
|
+
),
|
|
304
|
+
).toEqual({ ok: true });
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("other prerelease builds are still gated", () => {
|
|
308
|
+
const compat = {
|
|
309
|
+
min_runtime_version: "0.12.2-staging.4",
|
|
310
|
+
max_runtime_version: null,
|
|
311
|
+
};
|
|
312
|
+
for (const runtime of [
|
|
313
|
+
"0.12.1-dev.202609151913.a151da3",
|
|
314
|
+
"0.12.1-staging.9",
|
|
315
|
+
"0.12.1",
|
|
316
|
+
]) {
|
|
317
|
+
expect(evaluateRuntimeCompatibility(compat, runtime)).toEqual({
|
|
318
|
+
ok: false,
|
|
319
|
+
reason: "version_incompatible",
|
|
320
|
+
bundle_compat: compat,
|
|
321
|
+
runtime_version: runtime,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("isLocalDevRuntimeVersion", () => {
|
|
328
|
+
test("matches the local hatch / minikube version stamp", () => {
|
|
329
|
+
expect(
|
|
330
|
+
isLocalDevRuntimeVersion("0.12.1-local.20260916144827.2f409056e6"),
|
|
331
|
+
).toBe(true);
|
|
332
|
+
expect(isLocalDevRuntimeVersion("0.0.0-local.1789584535000")).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("rejects release, dev and staging builds and lookalikes", () => {
|
|
336
|
+
for (const version of [
|
|
337
|
+
"0.12.1",
|
|
338
|
+
"0.12.1-dev.202609151913.a151da3",
|
|
339
|
+
"0.12.2-staging.4",
|
|
340
|
+
"0.0.0-dev",
|
|
341
|
+
"0.0.0-legacy",
|
|
342
|
+
"0.12.1-localhost.1",
|
|
343
|
+
"local.0.12.1",
|
|
344
|
+
"",
|
|
345
|
+
]) {
|
|
346
|
+
expect(isLocalDevRuntimeVersion(version)).toBe(false);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
260
349
|
});
|
|
@@ -172,6 +172,20 @@ export function formatRuntimeCompatibilityMessage(
|
|
|
172
172
|
return `Cannot import: bundle requires runtime ${range}, but this runtime is ${runtimeVersion}. Update your runtime before importing.`;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Whether `version` names a runtime built from a source checkout: the
|
|
177
|
+
* `<pkg.version>-local.<timestamp>.<sha>` `APP_VERSION` that the platform
|
|
178
|
+
* repo's `vel up` stamps on the assistant images it builds for its local
|
|
179
|
+
* minikube cluster. The triple is the checkout's package.json version,
|
|
180
|
+
* which only moves on release cuts, so it says nothing about whether the
|
|
181
|
+
* checkout is newer or older than a release. A CLI hatch from a checkout
|
|
182
|
+
* without an explicit `APP_VERSION` runs as the plain package version and
|
|
183
|
+
* is not covered.
|
|
184
|
+
*/
|
|
185
|
+
export function isLocalDevRuntimeVersion(version: string): boolean {
|
|
186
|
+
return /^\d+\.\d+\.\d+-local\./.test(version);
|
|
187
|
+
}
|
|
188
|
+
|
|
175
189
|
export function evaluateRuntimeCompatibility(
|
|
176
190
|
compat: RuntimeCompatibility,
|
|
177
191
|
runtimeVersion: string,
|
|
@@ -179,17 +193,25 @@ export function evaluateRuntimeCompatibility(
|
|
|
179
193
|
if (compat.min_runtime_version === LEGACY_RUNTIME_VERSION_SENTINEL) {
|
|
180
194
|
return { ok: true };
|
|
181
195
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
196
|
+
// A local dev build skips the minimum bound, like an unparsable version
|
|
197
|
+
// does below: its triple is not comparable to release versions (see
|
|
198
|
+
// isLocalDevRuntimeVersion), so "runtime older than the bundle" cannot be
|
|
199
|
+
// decided from it. The maximum bound still applies: it is an explicit
|
|
200
|
+
// ceiling the bundle's producer recorded, and a local triple above it is
|
|
201
|
+
// as good a signal as any release's.
|
|
202
|
+
if (!isLocalDevRuntimeVersion(runtimeVersion)) {
|
|
203
|
+
const minCmp = compareSemver(runtimeVersion, compat.min_runtime_version);
|
|
204
|
+
if (minCmp === null) {
|
|
205
|
+
return { ok: true };
|
|
206
|
+
}
|
|
207
|
+
if (minCmp < 0) {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
reason: "version_incompatible",
|
|
211
|
+
bundle_compat: compat,
|
|
212
|
+
runtime_version: runtimeVersion,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
193
215
|
}
|
|
194
216
|
if (compat.max_runtime_version !== null) {
|
|
195
217
|
const maxCmp = compareSemver(runtimeVersion, compat.max_runtime_version);
|
|
@@ -123,8 +123,7 @@ function seedVellumConnection(): void {
|
|
|
123
123
|
|
|
124
124
|
function persistedProfiles(): Record<string, unknown> {
|
|
125
125
|
const llm = loadRawConfig().llm as
|
|
126
|
-
|
|
127
|
-
| undefined;
|
|
126
|
+
{ profiles?: Record<string, unknown> } | undefined;
|
|
128
127
|
return llm?.profiles ?? {};
|
|
129
128
|
}
|
|
130
129
|
|
|
@@ -923,6 +922,29 @@ describe("PUT inference/active-profile validation", () => {
|
|
|
923
922
|
).rejects.toThrow(/disabled/);
|
|
924
923
|
});
|
|
925
924
|
|
|
925
|
+
test("rejects a profile whose catalog model does not produce chat text", async () => {
|
|
926
|
+
setConfig("llm", {
|
|
927
|
+
profiles: {
|
|
928
|
+
jev: {
|
|
929
|
+
source: "user",
|
|
930
|
+
provider: "jev",
|
|
931
|
+
model: "jev-latest",
|
|
932
|
+
status: "active",
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
});
|
|
936
|
+
const promise = call("inference_profiles_set_active", {
|
|
937
|
+
body: { name: "jev" },
|
|
938
|
+
});
|
|
939
|
+
await expect(promise).rejects.toBeInstanceOf(BadRequestError);
|
|
940
|
+
await expect(promise).rejects.toThrow(
|
|
941
|
+
/structured answers rather than chat text/,
|
|
942
|
+
);
|
|
943
|
+
expect(
|
|
944
|
+
(loadRawConfig().llm as { activeProfile?: string }).activeProfile,
|
|
945
|
+
).toBeUndefined();
|
|
946
|
+
});
|
|
947
|
+
|
|
926
948
|
test("rejects a profile that cannot serve requests — no escape hatch", async () => {
|
|
927
949
|
setConfig("llm", {
|
|
928
950
|
profiles: {
|
|
@@ -29,13 +29,6 @@ export function requiredDecisionKeywords(
|
|
|
29
29
|
// Callback data parser — format: "apr:<requestId>:<action>"
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
|
-
/** Map legacy callback actions to canonical ones for in-flight buttons. */
|
|
33
|
-
const LEGACY_CALLBACK_MAP: Record<string, string> = {
|
|
34
|
-
approve_10m: "approve_once",
|
|
35
|
-
approve_conversation: "approve_once",
|
|
36
|
-
approve_always: "approve_once",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
32
|
export function parseCallbackData(
|
|
40
33
|
data: string,
|
|
41
34
|
sourceChannel?: string,
|
|
@@ -45,8 +38,7 @@ export function parseCallbackData(
|
|
|
45
38
|
return null;
|
|
46
39
|
}
|
|
47
40
|
const requestId = parts[1];
|
|
48
|
-
const
|
|
49
|
-
const action = LEGACY_CALLBACK_MAP[rawAction] ?? rawAction;
|
|
41
|
+
const action = parts.slice(2).join(":");
|
|
50
42
|
if (!requestId || !isApprovalAction(action)) {
|
|
51
43
|
return null;
|
|
52
44
|
}
|
|
@@ -113,7 +113,7 @@ import { recordOnboardingEvent } from "../../onboarding/onboarding-events-store.
|
|
|
113
113
|
import {
|
|
114
114
|
classifyKind,
|
|
115
115
|
getAttachmentById,
|
|
116
|
-
|
|
116
|
+
getAttachmentMetadataForMessages,
|
|
117
117
|
getAttachmentsByIds,
|
|
118
118
|
resolveAttachmentsForPersist,
|
|
119
119
|
} from "../../persistence/attachments-store.js";
|
|
@@ -132,6 +132,7 @@ import {
|
|
|
132
132
|
isSuppressedQueuedMessage,
|
|
133
133
|
isSystemCardMetadata,
|
|
134
134
|
type MessageRow,
|
|
135
|
+
parseMessageMetadata,
|
|
135
136
|
recordConversationPersistedSeq,
|
|
136
137
|
setConversationInferenceProfile,
|
|
137
138
|
} from "../../persistence/conversation-crud.js";
|
|
@@ -141,6 +142,7 @@ import {
|
|
|
141
142
|
} from "../../persistence/conversation-key-store.js";
|
|
142
143
|
import { searchConversations } from "../../persistence/conversation-queries.js";
|
|
143
144
|
import {
|
|
145
|
+
computerUseScreenshotAttachmentIdsFromMetadata,
|
|
144
146
|
isNoResponseMetadata,
|
|
145
147
|
messageMetadataIsAmbientSightKeep,
|
|
146
148
|
} from "../../persistence/conversation-types.js";
|
|
@@ -1180,9 +1182,29 @@ export async function handleListMessages({
|
|
|
1180
1182
|
let msgAttachments: RuntimeAttachmentMetadata[] = [];
|
|
1181
1183
|
if (m.id) {
|
|
1182
1184
|
const idsToQuery = [m.id, ...mergedMessageIds];
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1185
|
+
const linkedRows = getAttachmentMetadataForMessages(idsToQuery);
|
|
1186
|
+
const computerUseScreenshotIdsByMessage = new Map<
|
|
1187
|
+
string,
|
|
1188
|
+
Set<string>
|
|
1189
|
+
>();
|
|
1190
|
+
const linked = linkedRows.map((row) => {
|
|
1191
|
+
let screenshotIds = computerUseScreenshotIdsByMessage.get(
|
|
1192
|
+
row.messageId,
|
|
1193
|
+
);
|
|
1194
|
+
if (!screenshotIds) {
|
|
1195
|
+
screenshotIds = new Set(
|
|
1196
|
+
computerUseScreenshotAttachmentIdsFromMetadata(
|
|
1197
|
+
parseMessageMetadata(row.messageMetadata),
|
|
1198
|
+
),
|
|
1199
|
+
);
|
|
1200
|
+
computerUseScreenshotIdsByMessage.set(row.messageId, screenshotIds);
|
|
1201
|
+
}
|
|
1202
|
+
return {
|
|
1203
|
+
...row.attachment,
|
|
1204
|
+
computerUseScreenshot:
|
|
1205
|
+
screenshotIds.has(row.attachment.id) || undefined,
|
|
1206
|
+
};
|
|
1207
|
+
});
|
|
1186
1208
|
if (linked.length > 0) {
|
|
1187
1209
|
msgAttachments = await Promise.all(
|
|
1188
1210
|
linked.map(async (a) => {
|
|
@@ -1221,6 +1243,9 @@ export async function handleListMessages({
|
|
|
1221
1243
|
? { thumbnailData: a.thumbnailBase64 }
|
|
1222
1244
|
: {}),
|
|
1223
1245
|
fileBacked: true,
|
|
1246
|
+
...(a.computerUseScreenshot
|
|
1247
|
+
? { computerUseScreenshot: true }
|
|
1248
|
+
: {}),
|
|
1224
1249
|
};
|
|
1225
1250
|
}),
|
|
1226
1251
|
);
|
|
@@ -79,8 +79,8 @@ describe("desktop setup route feature gate", () => {
|
|
|
79
79
|
setOverridesForTesting({ "assistant-desktop": true });
|
|
80
80
|
expect(route.handler({})).toEqual(
|
|
81
81
|
route.method === "GET"
|
|
82
|
-
? { state: "required" }
|
|
83
|
-
: { state: "installing", stage: "packages" },
|
|
82
|
+
? { state: "required", automationActive: false }
|
|
83
|
+
: { state: "installing", stage: "packages", automationActive: false },
|
|
84
84
|
);
|
|
85
85
|
expect(route.method === "GET" ? status : start).toHaveBeenCalledTimes(1);
|
|
86
86
|
expect(route.method === "GET" ? start : status).not.toHaveBeenCalled();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { desktopAutomationLease } from "../../desktop/desktop-automation-lease.js";
|
|
4
4
|
import { desktopDependencyInstaller } from "../../desktop/desktop-dependencies.js";
|
|
5
5
|
import { isVirtualDesktopEnabled } from "../../desktop/virtual-desktop-feature.js";
|
|
6
6
|
import { GATEWAY_PRINCIPALS } from "../auth/route-policy.js";
|
|
@@ -9,6 +9,7 @@ import type { RouteDefinition } from "./types.js";
|
|
|
9
9
|
|
|
10
10
|
const statusSchema = z.object({
|
|
11
11
|
state: z.enum(["required", "installing", "ready", "failed", "unsupported"]),
|
|
12
|
+
automationActive: z.boolean().optional(),
|
|
12
13
|
stage: z.enum(["packages", "chrome", "checking"]).optional(),
|
|
13
14
|
});
|
|
14
15
|
|
|
@@ -19,14 +20,16 @@ export const ROUTES: RouteDefinition[] = ["GET", "POST"].map((method) => ({
|
|
|
19
20
|
method,
|
|
20
21
|
policy: { requiredScopes: [], allowedPrincipalTypes: GATEWAY_PRINCIPALS },
|
|
21
22
|
handler: () => {
|
|
22
|
-
if (!isVirtualDesktopEnabled(
|
|
23
|
+
if (!isVirtualDesktopEnabled()) {
|
|
23
24
|
throw new NotFoundError(
|
|
24
25
|
"Virtual desktop is available only on enabled platform-hosted assistants",
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const status =
|
|
29
|
+
method === "GET"
|
|
30
|
+
? desktopDependencyInstaller.getStatus()
|
|
31
|
+
: desktopDependencyInstaller.start();
|
|
32
|
+
return { ...status, automationActive: desktopAutomationLease.isActive };
|
|
30
33
|
},
|
|
31
34
|
summary:
|
|
32
35
|
method === "GET"
|
|
@@ -299,6 +299,30 @@ export async function handleApprovalInterception(
|
|
|
299
299
|
|
|
300
300
|
return { handled: true, type: "stale_ignored" };
|
|
301
301
|
}
|
|
302
|
+
|
|
303
|
+
// An approval callback that names no approval action is a button press
|
|
304
|
+
// the daemon does not serve (an id outside the decision vocabulary, or an
|
|
305
|
+
// answer token aimed at a card this rail does not resolve). It is never
|
|
306
|
+
// text: the channel normalizers copy callback data into `content`, so
|
|
307
|
+
// letting it fall through would hand the raw callback to the
|
|
308
|
+
// conversational and natural-language parsers, which can read a token
|
|
309
|
+
// like `approve_always` as an approval. Consume it as a stale button.
|
|
310
|
+
if (callbackData.startsWith("apr:")) {
|
|
311
|
+
log.warn(
|
|
312
|
+
{ conversationId, callbackData },
|
|
313
|
+
"Approval callback carries no approval action, ignoring stale button press",
|
|
314
|
+
);
|
|
315
|
+
if (approvalMessageId) {
|
|
316
|
+
editStaleApprovalMessage({
|
|
317
|
+
replyCallbackUrl,
|
|
318
|
+
chatId: conversationExternalId,
|
|
319
|
+
messageId: approvalMessageId,
|
|
320
|
+
assistantId,
|
|
321
|
+
conversationId,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return { handled: true, type: "stale_ignored" };
|
|
325
|
+
}
|
|
302
326
|
}
|
|
303
327
|
|
|
304
328
|
// ── Conversational approval engine for plain-text messages ──
|
|
@@ -1531,8 +1531,7 @@ export async function handleChannelInbound({
|
|
|
1531
1531
|
sourceMetadata.account.length > 0
|
|
1532
1532
|
? sourceMetadata.account
|
|
1533
1533
|
: undefined;
|
|
1534
|
-
const
|
|
1535
|
-
sourceChannel === "slack" && sourceMetadata?.slackBotMentioned === true;
|
|
1534
|
+
const botMentioned = sourceMetadata?.botMentioned === true;
|
|
1536
1535
|
|
|
1537
1536
|
// ── DM cold-start backfill ──
|
|
1538
1537
|
// First time a Slack DM without thread_ts lands in a conversation that
|
|
@@ -1625,7 +1624,7 @@ export async function handleChannelInbound({
|
|
|
1625
1624
|
approvalCopyGenerator,
|
|
1626
1625
|
chatType: sourceChatType,
|
|
1627
1626
|
clientTimezone: inboundClientTimezone,
|
|
1628
|
-
|
|
1627
|
+
botMentioned,
|
|
1629
1628
|
slackInbound,
|
|
1630
1629
|
channelInbound,
|
|
1631
1630
|
});
|
|
@@ -1169,7 +1169,7 @@ describe("channel activity timing", () => {
|
|
|
1169
1169
|
externalChatId: channelId,
|
|
1170
1170
|
trustCtx,
|
|
1171
1171
|
metadataHints: [],
|
|
1172
|
-
|
|
1172
|
+
botMentioned: true,
|
|
1173
1173
|
replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&threadTs=${threadTs}`,
|
|
1174
1174
|
});
|
|
1175
1175
|
|