@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292116.41f7caa
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/ARCHITECTURE.md +26 -3
- package/docs/automatic-done.md +32 -0
- package/docs/desktop-browser-cli.md +3 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
- package/node_modules/@vellumai/environments/src/shell.ts +22 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/openapi.yaml +270 -10
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +76 -0
- package/src/__tests__/anthropic-provider.test.ts +2 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
- package/src/__tests__/config-get-vision-flag.test.ts +1 -1
- package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
- package/src/__tests__/conversation-evictor.test.ts +54 -0
- package/src/__tests__/conversation-pairing.test.ts +112 -0
- package/src/__tests__/document-append-idempotency.test.ts +13 -0
- package/src/__tests__/document-create-dedupe.test.ts +13 -0
- package/src/__tests__/document-find-replace.test.ts +13 -0
- package/src/__tests__/document-revision-history.test.ts +390 -0
- package/src/__tests__/document-revision.test.ts +425 -0
- package/src/__tests__/document-tool-security.test.ts +13 -0
- package/src/__tests__/document-update-default-surface.test.ts +13 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
- package/src/__tests__/gateway-flag-listener.test.ts +59 -6
- package/src/__tests__/headless-browser-navigate.test.ts +20 -30
- package/src/__tests__/host-cu-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -2
- package/src/__tests__/identity-routes.test.ts +4 -3
- package/src/__tests__/list-messages-client-message-id.test.ts +43 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -4
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/notification-platform-adapter.test.ts +83 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/openai-provider.test.ts +38 -11
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/public-ingress-urls.test.ts +26 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
- package/src/__tests__/skill-execute-input.test.ts +11 -0
- package/src/__tests__/tool-approval-handler.test.ts +4 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -0
- package/src/__tests__/usage-attribution.test.ts +31 -0
- package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
- package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
- package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
- package/src/acp/agent-process.ts +6 -1
- package/src/acp/client-handler.ts +6 -1
- package/src/agent/loop-unparseable-tool-retry.test.ts +333 -0
- package/src/agent/loop.ts +161 -17
- package/src/api/events/document-editor-show.ts +5 -0
- package/src/api/events/document-editor-update.ts +10 -0
- package/src/api/events/tool-result.ts +1 -0
- package/src/calls/__tests__/call-controller.test.ts +273 -1
- package/src/calls/__tests__/media-stream-server-integration.test.ts +40 -0
- package/src/calls/__tests__/media-stream-stt-session.test.ts +33 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
- package/src/calls/call-constants.ts +10 -0
- package/src/calls/call-controller.ts +359 -25
- package/src/calls/channel-admission-reader.ts +15 -9
- package/src/calls/media-stream-server.ts +18 -0
- package/src/calls/media-stream-stt-session.ts +30 -9
- package/src/calls/twilio-provider.ts +14 -31
- package/src/calls/voice-session-bridge.ts +18 -1
- package/src/cli/commands/browser.ts +1 -2
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
- package/src/cli/lib/bundled-marketplace.json +54 -2
- package/src/cli/lib/inspect-plugin.ts +15 -15
- package/src/cli/lib/list-installed-plugins.ts +29 -28
- package/src/cli/lib/plugin-artifact.ts +74 -63
- package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
- package/src/cli/lib/plugin-details.ts +27 -42
- package/src/cli/lib/plugin-marketplace.ts +6 -8
- package/src/cli/lib/plugin-surfaces.ts +8 -7
- package/src/cli.ts +1 -3
- package/src/config/__tests__/chat-settings.test.ts +121 -0
- package/src/config/assistant-feature-flags.ts +28 -23
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
- package/src/config/chat-settings.ts +100 -0
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/feature-flag-cache.ts +7 -25
- package/src/config/feature-flag-registry.json +9 -9
- package/src/config/loader.ts +5 -1
- package/src/config/profile-text-generation.ts +25 -21
- package/src/config/schemas/conversations.ts +14 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/notifications.ts +8 -1
- package/src/contacts/gateway-channel-read.ts +1 -1
- package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
- package/src/conversations/__tests__/auto-archive.test.ts +304 -0
- package/src/conversations/auto-archive-activity.ts +61 -0
- package/src/conversations/auto-archive.ts +201 -0
- package/src/daemon/computer-use-observation.test.ts +81 -0
- package/src/daemon/computer-use-observation.ts +23 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -2
- package/src/daemon/conversation-agent-loop.ts +14 -6
- package/src/daemon/conversation-evictor.ts +50 -16
- package/src/daemon/conversation-messaging.ts +4 -0
- package/src/daemon/conversation-process.ts +15 -0
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/doordash-steps.ts +1 -3
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
- package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
- package/src/daemon/handlers/config-a2a.ts +75 -2
- package/src/daemon/handlers/shared.ts +13 -2
- package/src/daemon/host-cu-proxy.ts +34 -3
- package/src/daemon/lifecycle.ts +15 -2
- package/src/daemon/process-message.ts +5 -0
- package/src/daemon/shutdown-handlers.ts +3 -0
- package/src/daemon/turn-finalization.ts +4 -0
- package/src/desktop/desktop-browser-endpoint.ts +11 -2
- package/src/desktop/desktop-chrome-policy.test.ts +1 -0
- package/src/desktop/desktop-chrome-policy.ts +7 -1
- package/src/desktop/desktop-computer-use.test.ts +14 -4
- package/src/desktop/desktop-session-manager.ts +5 -3
- package/src/documents/document-revisions-store.ts +153 -0
- package/src/documents/document-store.ts +471 -133
- package/src/followups/followup-store.ts +0 -4
- package/src/i18n/__tests__/i18n.test.ts +25 -0
- package/src/i18n/assistant-text.ts +18 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/messages.ts +11 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
- package/src/inbound/oauth-callback-url.ts +30 -1
- package/src/inbound/public-ingress-urls.ts +12 -2
- package/src/ipc/gateway-client.test.ts +40 -0
- package/src/ipc/gateway-client.ts +12 -29
- package/src/ipc/gateway-flag-listener.ts +7 -10
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
- package/src/live-voice/__tests__/protocol.test.ts +77 -0
- package/src/live-voice/__tests__/share-targets.test.ts +163 -0
- package/src/live-voice/live-voice-session.ts +23 -6
- package/src/live-voice/protocol.ts +22 -0
- package/src/live-voice/share-targets.ts +225 -0
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
- package/src/mcp/client.ts +1 -19
- package/src/mcp/mcp-oauth-provider.ts +66 -49
- package/src/media/openrouter-image-service.ts +39 -22
- package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
- package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
- package/src/monitoring/control.ts +2 -1
- package/src/monitoring/oom-inheritance-sweep.ts +96 -0
- package/src/monitoring/oom-kill-reporter.ts +469 -0
- package/src/monitoring/resource-sampler.ts +30 -0
- package/src/notifications/README.md +53 -4
- package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
- package/src/notifications/__tests__/broadcaster.test.ts +558 -6
- package/src/notifications/__tests__/completion-policy.test.ts +37 -0
- package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
- package/src/notifications/adapters/platform.ts +14 -0
- package/src/notifications/assistant-initiated-thread.ts +101 -0
- package/src/notifications/broadcaster.ts +94 -16
- package/src/notifications/chat-reply-policy.ts +11 -0
- package/src/notifications/completion-policy.ts +12 -2
- package/src/notifications/conversation-pairing.ts +15 -70
- package/src/notifications/emit-signal.ts +34 -3
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connect-orchestrator.ts +1 -2
- package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
- package/src/persistence/conversation-auto-archive.ts +170 -0
- package/src/persistence/conversation-crud.ts +2 -1
- package/src/persistence/conversation-queries.ts +15 -8
- package/src/persistence/embeddings/embed.test.ts +96 -0
- package/src/persistence/embeddings/embed.ts +20 -2
- package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
- package/src/persistence/embeddings/embedding-gemini.ts +39 -1
- package/src/persistence/embeddings/embedding-local.ts +6 -0
- package/src/persistence/embeddings/qdrant-manager.ts +5 -0
- package/src/persistence/job-utils.ts +10 -0
- package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
- package/src/persistence/migrations/384-add-document-revision.ts +21 -0
- package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
- package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
- package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
- package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
- package/src/persistence/schema/conversations.ts +1 -0
- package/src/persistence/schema/documents.ts +20 -0
- package/src/persistence/steps.ts +6 -0
- package/src/platform/managed-speech.ts +42 -46
- package/src/plugin-api/vision-support.test.ts +21 -21
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +25 -0
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/fireworks/client.test.ts +168 -0
- package/src/providers/fireworks/client.ts +24 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/ionet/client.test.ts +143 -0
- package/src/providers/ionet/client.ts +37 -0
- package/src/providers/model-catalog.ts +141 -84
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +33 -2
- package/src/providers/retry.ts +56 -4
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +10 -2
- package/src/providers/unparseable-tool-args.ts +29 -5
- package/src/providers/usage-tracking.ts +5 -3
- package/src/runtime/assistant-event-hub.ts +23 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
- package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +2 -0
- package/src/runtime/channel-retry-sweep.ts +1 -2
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
- package/src/runtime/migrations/vbundle-builder.ts +19 -3
- package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
- package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
- package/src/runtime/migrations/vbundle-validator.ts +54 -1
- package/src/runtime/pending-interactions.ts +20 -2
- package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
- package/src/runtime/routes/contact-routes.ts +2 -2
- package/src/runtime/routes/conversation-list-routes.ts +10 -2
- package/src/runtime/routes/conversation-query-routes.ts +48 -10
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/documents-routes.ts +185 -47
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -0
- package/src/runtime/routes/host-bash-routes.ts +1 -0
- package/src/runtime/routes/host-browser-routes.ts +15 -0
- package/src/runtime/routes/host-cu-routes.ts +4 -1
- package/src/runtime/routes/host-file-routes.ts +1 -0
- package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
- package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
- package/src/runtime/routes/host-transfer-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/integrations/a2a.ts +4 -1
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/notification-routes.ts +1 -2
- package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
- package/src/runtime/routes/user-route-dispatcher.ts +5 -1
- package/src/runtime/verification-templates.ts +1 -2
- package/src/sequence/analytics.ts +0 -4
- package/src/sequence/engine.ts +1 -11
- package/src/sequence/guardrails.ts +1 -6
- package/src/sequence/importer.ts +0 -8
- package/src/sequence/store.ts +0 -10
- package/src/signals/user-message.ts +0 -6
- package/src/skills/catalog-install.ts +52 -23
- package/src/tools/browser/browser-execution.ts +20 -23
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
- package/src/tools/document/document-tool.ts +13 -11
- package/src/tools/executor.ts +1 -2
- package/src/tools/host-terminal/host-shell.ts +4 -2
- package/src/tools/network/__tests__/web-search.test.ts +105 -0
- package/src/tools/network/web-fetch.ts +2 -4
- package/src/tools/network/web-search.ts +239 -12
- package/src/tools/shared/filesystem/format-diff.ts +1 -2
- package/src/tools/shared/shell-output.test.ts +42 -0
- package/src/tools/shared/shell-output.ts +27 -4
- package/src/tools/skills/execute.ts +10 -7
- package/src/tools/terminal/shell.ts +3 -3
- package/src/tools/tool-approval-handler.ts +9 -1
- package/src/tools/ui-surface/visual-validation.ts +1 -2
- package/src/usage/attribution.ts +18 -1
- package/src/util/__tests__/oom-priority.test.ts +55 -0
- package/src/util/__tests__/worker-memory.test.ts +16 -0
- package/src/util/debounce.ts +0 -4
- package/src/util/oom-priority.ts +79 -0
- package/src/util/platform.ts +0 -2
- package/src/util/plugin-manifest.ts +5 -7
- package/src/util/pricing.ts +25 -0
- package/src/util/worker-memory.ts +9 -3
- package/src/util/worker-process.ts +8 -0
- package/src/workspace/byok-default-profile-ensure.ts +11 -0
- package/src/workspace/hatched-date.ts +11 -4
- package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
- package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -6,71 +6,40 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
getDocumentRevision,
|
|
11
|
+
listDocumentRevisions,
|
|
12
|
+
} from "../../documents/document-revisions-store.js";
|
|
9
13
|
import {
|
|
10
14
|
addDocumentConversation,
|
|
11
15
|
createDocument,
|
|
12
16
|
DEFAULT_DOCUMENT_TITLE,
|
|
13
17
|
getDocumentById,
|
|
14
18
|
getDocumentsForConversation,
|
|
19
|
+
listAllDocuments,
|
|
20
|
+
restoreDocumentRevision,
|
|
15
21
|
saveDocument,
|
|
16
22
|
} from "../../documents/document-store.js";
|
|
17
23
|
import { getConversation } from "../../persistence/conversation-crud.js";
|
|
18
|
-
import { rawAll } from "../../persistence/raw-query.js";
|
|
19
24
|
import { getLogger } from "../../util/logger.js";
|
|
25
|
+
import { broadcastMessage } from "../assistant-event-hub.js";
|
|
20
26
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
21
27
|
import {
|
|
22
28
|
getOriginClientId,
|
|
23
29
|
publishDocumentsChanged,
|
|
24
30
|
} from "../sync/resource-sync-events.js";
|
|
25
31
|
import { renderMarkdownToPDF } from "./document-pdf-renderer.js";
|
|
26
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
BadRequestError,
|
|
34
|
+
ConflictError,
|
|
35
|
+
InternalError,
|
|
36
|
+
NotFoundError,
|
|
37
|
+
} from "./errors.js";
|
|
27
38
|
import type { RouteDefinition } from "./types.js";
|
|
28
39
|
import { RouteResponse } from "./types.js";
|
|
29
40
|
|
|
30
41
|
const log = getLogger("documents-routes");
|
|
31
42
|
|
|
32
|
-
interface DocumentListRow {
|
|
33
|
-
surface_id: string;
|
|
34
|
-
conversation_id: string;
|
|
35
|
-
title: string;
|
|
36
|
-
word_count: number;
|
|
37
|
-
created_at: number;
|
|
38
|
-
updated_at: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function listAllDocuments(): Array<{
|
|
42
|
-
surfaceId: string;
|
|
43
|
-
conversationId: string;
|
|
44
|
-
title: string;
|
|
45
|
-
wordCount: number;
|
|
46
|
-
createdAt: number;
|
|
47
|
-
updatedAt: number;
|
|
48
|
-
}> {
|
|
49
|
-
try {
|
|
50
|
-
const results = rawAll<DocumentListRow>(
|
|
51
|
-
"documents:listAllDocuments",
|
|
52
|
-
/*sql*/ `
|
|
53
|
-
SELECT surface_id, conversation_id, title, word_count, created_at, updated_at
|
|
54
|
-
FROM documents
|
|
55
|
-
ORDER BY updated_at DESC
|
|
56
|
-
`,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
log.info({ count: results.length }, "Listed documents");
|
|
60
|
-
return results.map((row) => ({
|
|
61
|
-
surfaceId: row.surface_id,
|
|
62
|
-
conversationId: row.conversation_id,
|
|
63
|
-
title: row.title,
|
|
64
|
-
wordCount: row.word_count,
|
|
65
|
-
createdAt: row.created_at,
|
|
66
|
-
updatedAt: row.updated_at,
|
|
67
|
-
}));
|
|
68
|
-
} catch (error) {
|
|
69
|
-
log.error({ err: error }, "List error");
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
43
|
/** The document payload shape returned by `GET documents/{id}`. */
|
|
75
44
|
const documentPayloadSchema = z.object({
|
|
76
45
|
success: z.boolean(),
|
|
@@ -81,8 +50,32 @@ const documentPayloadSchema = z.object({
|
|
|
81
50
|
wordCount: z.number(),
|
|
82
51
|
createdAt: z.number(),
|
|
83
52
|
updatedAt: z.number(),
|
|
53
|
+
revision: z
|
|
54
|
+
.number()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe(
|
|
57
|
+
"Bumped by one on every write. Absent from assistants that predate document revisions.",
|
|
58
|
+
),
|
|
84
59
|
});
|
|
85
60
|
|
|
61
|
+
const revisionSummarySchema = z.object({
|
|
62
|
+
revision: z.number().describe("The document revision this snapshot holds"),
|
|
63
|
+
author: z
|
|
64
|
+
.enum(["user", "assistant"])
|
|
65
|
+
.describe("Who made the edit that replaced this state"),
|
|
66
|
+
createdAt: z.number().describe("When the snapshot was taken"),
|
|
67
|
+
wordCount: z.number(),
|
|
68
|
+
title: z.string(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function parseRevisionParam(raw: string | undefined): number {
|
|
72
|
+
const revision = Number(raw);
|
|
73
|
+
if (!raw || !Number.isInteger(revision) || revision < 0) {
|
|
74
|
+
throw new BadRequestError("revision must be a non-negative integer");
|
|
75
|
+
}
|
|
76
|
+
return revision;
|
|
77
|
+
}
|
|
78
|
+
|
|
86
79
|
// ---------------------------------------------------------------------------
|
|
87
80
|
// Route definitions
|
|
88
81
|
// ---------------------------------------------------------------------------
|
|
@@ -115,6 +108,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
115
108
|
wordCount: z.number(),
|
|
116
109
|
createdAt: z.number(),
|
|
117
110
|
updatedAt: z.number(),
|
|
111
|
+
revision: z
|
|
112
|
+
.number()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe(
|
|
115
|
+
"Bumped by one on every write. Absent from assistants that predate document revisions.",
|
|
116
|
+
),
|
|
118
117
|
}),
|
|
119
118
|
),
|
|
120
119
|
}),
|
|
@@ -157,7 +156,8 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
157
156
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
158
157
|
},
|
|
159
158
|
summary: "Save a document",
|
|
160
|
-
description:
|
|
159
|
+
description:
|
|
160
|
+
"Create or upsert a document (by surfaceId). With `baseRevision`, an existing document is only overwritten while its revision still equals `baseRevision`; otherwise the save is rejected with 409 CONFLICT and `error.details` carries the current `revision`, `title`, and `content`.",
|
|
161
161
|
tags: ["documents"],
|
|
162
162
|
requestBody: z.object({
|
|
163
163
|
surfaceId: z.string().describe("Surface ID (unique key)"),
|
|
@@ -165,19 +165,40 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
165
165
|
title: z.string().describe("Document title"),
|
|
166
166
|
content: z.string().describe("Document content"),
|
|
167
167
|
wordCount: z.number().describe("Word count"),
|
|
168
|
+
baseRevision: z
|
|
169
|
+
.number()
|
|
170
|
+
.int()
|
|
171
|
+
.nonnegative()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe(
|
|
174
|
+
"The revision this save was edited from. When set and the stored revision differs, nothing is written and the request fails with 409. Omit for an unconditional write.",
|
|
175
|
+
),
|
|
168
176
|
}),
|
|
169
177
|
responseBody: z.object({
|
|
170
178
|
success: z.literal(true),
|
|
171
179
|
surfaceId: z.string(),
|
|
180
|
+
revision: z
|
|
181
|
+
.number()
|
|
182
|
+
.optional()
|
|
183
|
+
.describe(
|
|
184
|
+
"The document's revision after the save. Absent from assistants that predate document revisions.",
|
|
185
|
+
),
|
|
172
186
|
}),
|
|
173
187
|
handler: ({ body, headers }) => {
|
|
174
|
-
const {
|
|
175
|
-
|
|
188
|
+
const {
|
|
189
|
+
surfaceId,
|
|
190
|
+
conversationId,
|
|
191
|
+
title,
|
|
192
|
+
content,
|
|
193
|
+
wordCount,
|
|
194
|
+
baseRevision,
|
|
195
|
+
} = (body ?? {}) as {
|
|
176
196
|
surfaceId?: string;
|
|
177
197
|
conversationId?: string;
|
|
178
198
|
title?: string;
|
|
179
199
|
content?: string;
|
|
180
200
|
wordCount?: number;
|
|
201
|
+
baseRevision?: number | null;
|
|
181
202
|
};
|
|
182
203
|
|
|
183
204
|
if (!surfaceId || typeof surfaceId !== "string") {
|
|
@@ -195,6 +216,14 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
195
216
|
if (typeof wordCount !== "number") {
|
|
196
217
|
throw new BadRequestError("wordCount is required");
|
|
197
218
|
}
|
|
219
|
+
if (
|
|
220
|
+
baseRevision != null &&
|
|
221
|
+
(!Number.isInteger(baseRevision) || baseRevision < 0)
|
|
222
|
+
) {
|
|
223
|
+
throw new BadRequestError(
|
|
224
|
+
"baseRevision must be a non-negative integer",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
198
227
|
|
|
199
228
|
const result = saveDocument({
|
|
200
229
|
surfaceId,
|
|
@@ -202,9 +231,13 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
202
231
|
title,
|
|
203
232
|
content,
|
|
204
233
|
wordCount,
|
|
234
|
+
baseRevision: baseRevision ?? undefined,
|
|
205
235
|
});
|
|
206
236
|
|
|
207
237
|
if (!result.success) {
|
|
238
|
+
if (result.conflict) {
|
|
239
|
+
throw new ConflictError(result.error, result.conflict);
|
|
240
|
+
}
|
|
208
241
|
throw new InternalError(result.error);
|
|
209
242
|
}
|
|
210
243
|
// Every save moves `updated_at`, which both document lists order by and
|
|
@@ -305,6 +338,111 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
305
338
|
},
|
|
306
339
|
},
|
|
307
340
|
|
|
341
|
+
{
|
|
342
|
+
operationId: "listDocumentRevisions",
|
|
343
|
+
endpoint: "documents/:id/revisions",
|
|
344
|
+
method: "GET",
|
|
345
|
+
policy: {
|
|
346
|
+
requiredScopes: ["settings.read"],
|
|
347
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
348
|
+
},
|
|
349
|
+
summary: "List a document's revisions",
|
|
350
|
+
description:
|
|
351
|
+
"Return the document's history snapshots, newest first, without their content.",
|
|
352
|
+
tags: ["documents"],
|
|
353
|
+
responseBody: z.object({ revisions: z.array(revisionSummarySchema) }),
|
|
354
|
+
handler: ({ pathParams }) => {
|
|
355
|
+
if (!getDocumentById(pathParams!.id)) {
|
|
356
|
+
throw new NotFoundError("Document not found");
|
|
357
|
+
}
|
|
358
|
+
return { revisions: listDocumentRevisions(pathParams!.id) };
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
{
|
|
363
|
+
operationId: "getDocumentRevision",
|
|
364
|
+
endpoint: "documents/:id/revisions/:revision",
|
|
365
|
+
method: "GET",
|
|
366
|
+
policy: {
|
|
367
|
+
requiredScopes: ["settings.read"],
|
|
368
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
369
|
+
},
|
|
370
|
+
summary: "Get a document revision",
|
|
371
|
+
description: "Return one history snapshot with its content.",
|
|
372
|
+
tags: ["documents"],
|
|
373
|
+
responseBody: revisionSummarySchema.extend({ content: z.string() }),
|
|
374
|
+
handler: ({ pathParams }) => {
|
|
375
|
+
const revision = parseRevisionParam(pathParams!.revision);
|
|
376
|
+
const snapshot = getDocumentRevision(pathParams!.id, revision);
|
|
377
|
+
if (!snapshot) {
|
|
378
|
+
throw new NotFoundError("Revision not found");
|
|
379
|
+
}
|
|
380
|
+
return snapshot;
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
{
|
|
385
|
+
operationId: "restoreDocumentRevision",
|
|
386
|
+
endpoint: "documents/:id/revisions/:revision/restore",
|
|
387
|
+
method: "POST",
|
|
388
|
+
policy: {
|
|
389
|
+
requiredScopes: ["settings.write"],
|
|
390
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
391
|
+
},
|
|
392
|
+
summary: "Restore a document revision",
|
|
393
|
+
description:
|
|
394
|
+
"Write a snapshot's title and content back as a new revision. The current state is snapshotted first, so the restore can itself be undone.",
|
|
395
|
+
tags: ["documents"],
|
|
396
|
+
responseBody: z.object({
|
|
397
|
+
success: z.literal(true),
|
|
398
|
+
surfaceId: z.string(),
|
|
399
|
+
revision: z
|
|
400
|
+
.number()
|
|
401
|
+
.describe("The document's revision after the restore"),
|
|
402
|
+
title: z.string(),
|
|
403
|
+
content: z.string(),
|
|
404
|
+
}),
|
|
405
|
+
handler: ({ pathParams, headers }) => {
|
|
406
|
+
const surfaceId = pathParams!.id;
|
|
407
|
+
const revision = parseRevisionParam(pathParams!.revision);
|
|
408
|
+
const result = restoreDocumentRevision(surfaceId, revision);
|
|
409
|
+
if (!result.success) {
|
|
410
|
+
throw new NotFoundError(
|
|
411
|
+
result.notFound === "document"
|
|
412
|
+
? "Document not found"
|
|
413
|
+
: "Revision not found",
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
const doc = getDocumentById(surfaceId);
|
|
417
|
+
if (doc) {
|
|
418
|
+
broadcastMessage(
|
|
419
|
+
{
|
|
420
|
+
type: "document_editor_update",
|
|
421
|
+
conversationId: doc.conversationId,
|
|
422
|
+
surfaceId,
|
|
423
|
+
markdown: result.content,
|
|
424
|
+
mode: "replace",
|
|
425
|
+
revision: result.revision,
|
|
426
|
+
title: result.title,
|
|
427
|
+
},
|
|
428
|
+
doc.conversationId,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
publishDocumentsChanged(getOriginClientId(headers));
|
|
432
|
+
log.info(
|
|
433
|
+
{ surfaceId, restoredRevision: revision, revision: result.revision },
|
|
434
|
+
"Restored document revision via HTTP",
|
|
435
|
+
);
|
|
436
|
+
return {
|
|
437
|
+
success: true as const,
|
|
438
|
+
surfaceId,
|
|
439
|
+
revision: result.revision,
|
|
440
|
+
title: result.title,
|
|
441
|
+
content: result.content,
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
|
|
308
446
|
{
|
|
309
447
|
operationId: "exportDocumentPDF",
|
|
310
448
|
endpoint: "documents/:id/pdf",
|
|
@@ -492,7 +492,7 @@ export function handleSubscribeAssistantEvents(
|
|
|
492
492
|
});
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
|
|
495
|
+
return new ReadableStream<Uint8Array>(
|
|
496
496
|
{
|
|
497
497
|
start(controller) {
|
|
498
498
|
controllerRef = controller;
|
|
@@ -573,8 +573,6 @@ export function handleSubscribeAssistantEvents(
|
|
|
573
573
|
},
|
|
574
574
|
new CountQueuingStrategy({ highWaterMark: 16 }),
|
|
575
575
|
);
|
|
576
|
-
|
|
577
|
-
return stream;
|
|
578
576
|
}
|
|
579
577
|
|
|
580
578
|
/**
|
|
@@ -91,6 +91,7 @@ async function handleHostAppControlResult({ body, headers }: RouteHandlerArgs) {
|
|
|
91
91
|
await assertHostProxyResultBinding({
|
|
92
92
|
headers,
|
|
93
93
|
targetClientId: peeked.targetClientId,
|
|
94
|
+
targetConnectionId: peeked.targetConnectionId,
|
|
94
95
|
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
95
96
|
op: "host_app_control",
|
|
96
97
|
missingClientIdMessage:
|
|
@@ -64,6 +64,7 @@ async function handleHostBashResult({ body, headers }: RouteHandlerArgs) {
|
|
|
64
64
|
await assertHostProxyResultBinding({
|
|
65
65
|
headers,
|
|
66
66
|
targetClientId: peeked.targetClientId,
|
|
67
|
+
targetConnectionId: peeked.targetConnectionId,
|
|
67
68
|
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
68
69
|
op: "host_bash",
|
|
69
70
|
missingClientIdMessage:
|
|
@@ -122,6 +122,21 @@ export async function resolveHostBrowserResultByRequestId(
|
|
|
122
122
|
message: `Client "${submittingClientId}" is not the target for this request (expected "${peeked.targetClientId}"). The targeted client must submit the result.`,
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
+
if (
|
|
126
|
+
peeked.targetConnectionId &&
|
|
127
|
+
!assistantEventHub.hasActiveClientConnection(
|
|
128
|
+
peeked.targetConnectionId,
|
|
129
|
+
peeked.targetClientId,
|
|
130
|
+
)
|
|
131
|
+
) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
code: "FORBIDDEN",
|
|
135
|
+
status: 403,
|
|
136
|
+
message:
|
|
137
|
+
"This host-proxy result is bound to a connection that is no longer active.",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
125
140
|
|
|
126
141
|
// Defense-in-depth: require the submitting actor's principal id to match
|
|
127
142
|
// the actor principal id captured when the target client opened its SSE
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
+
import { prepareComputerUseObservation } from "../../daemon/computer-use-observation.js";
|
|
9
10
|
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
10
11
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
11
12
|
import { SAME_ACTOR_FORBIDDEN_DESCRIPTION } from "../auth/same-actor.js";
|
|
@@ -77,6 +78,7 @@ async function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
|
|
|
77
78
|
await assertHostProxyResultBinding({
|
|
78
79
|
headers: headers as Record<string, string | undefined> | undefined,
|
|
79
80
|
targetClientId: peeked.targetClientId,
|
|
81
|
+
targetConnectionId: peeked.targetConnectionId,
|
|
80
82
|
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
81
83
|
op: "host_cu",
|
|
82
84
|
missingClientIdMessage:
|
|
@@ -112,7 +114,7 @@ async function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
|
|
|
112
114
|
throw new NotFoundError("No host CU proxy for conversation");
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
const observation = await prepareComputerUseObservation({
|
|
116
118
|
axTree,
|
|
117
119
|
axDiff,
|
|
118
120
|
screenshot,
|
|
@@ -126,6 +128,7 @@ async function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
|
|
|
126
128
|
userGuidance,
|
|
127
129
|
timings,
|
|
128
130
|
});
|
|
131
|
+
conversation.hostCuProxy.processObservation(requestId, observation);
|
|
129
132
|
|
|
130
133
|
return { accepted: true };
|
|
131
134
|
}
|
|
@@ -63,6 +63,7 @@ async function handleHostFileResult({ body, headers }: RouteHandlerArgs) {
|
|
|
63
63
|
await assertHostProxyResultBinding({
|
|
64
64
|
headers: headers as Record<string, string | undefined> | undefined,
|
|
65
65
|
targetClientId: peeked.targetClientId,
|
|
66
|
+
targetConnectionId: peeked.targetConnectionId,
|
|
66
67
|
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
67
68
|
op: "host_file",
|
|
68
69
|
missingClientIdMessage:
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
4
|
+
import * as pendingInteractions from "../pending-interactions.js";
|
|
5
|
+
import { ForbiddenError } from "./errors.js";
|
|
6
|
+
import { assertHostProxyResultBinding } from "./host-proxy-result-binding.js";
|
|
7
|
+
|
|
8
|
+
const ACTOR = "actor-host-proxy-bind";
|
|
9
|
+
const subscriptions: Array<{ dispose(): void }> = [];
|
|
10
|
+
|
|
11
|
+
function subscribeClient(clientId: string) {
|
|
12
|
+
const sub = assistantEventHub.subscribe({
|
|
13
|
+
type: "client",
|
|
14
|
+
clientId,
|
|
15
|
+
interfaceId: "macos",
|
|
16
|
+
capabilities: ["host_bash"],
|
|
17
|
+
actorPrincipalId: ACTOR,
|
|
18
|
+
callback: () => {},
|
|
19
|
+
});
|
|
20
|
+
subscriptions.push(sub);
|
|
21
|
+
return sub;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("assertHostProxyResultBinding connection snapshot", () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
pendingInteractions.clear();
|
|
27
|
+
for (const sub of subscriptions.splice(0)) {
|
|
28
|
+
sub.dispose();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("allows a result while the dispatch connection is still active", async () => {
|
|
33
|
+
const sub = subscribeClient("host-proxy-bind-1");
|
|
34
|
+
pendingInteractions.register("req-active", {
|
|
35
|
+
kind: "host_bash",
|
|
36
|
+
targetClientId: "host-proxy-bind-1",
|
|
37
|
+
});
|
|
38
|
+
const pending = pendingInteractions.get("req-active");
|
|
39
|
+
expect(pending?.targetConnectionId).toBe(sub.connectionId);
|
|
40
|
+
|
|
41
|
+
await assertHostProxyResultBinding({
|
|
42
|
+
headers: {
|
|
43
|
+
"x-vellum-client-id": "host-proxy-bind-1",
|
|
44
|
+
"x-vellum-actor-principal-id": ACTOR,
|
|
45
|
+
},
|
|
46
|
+
targetClientId: pending?.targetClientId,
|
|
47
|
+
targetConnectionId: pending?.targetConnectionId,
|
|
48
|
+
targetActorPrincipalId: ACTOR,
|
|
49
|
+
op: "host_bash",
|
|
50
|
+
missingClientIdMessage: "missing",
|
|
51
|
+
});
|
|
52
|
+
sub.dispose();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("rejects a result after the same clientId reconnects", async () => {
|
|
56
|
+
const first = subscribeClient("host-proxy-bind-2");
|
|
57
|
+
pendingInteractions.register("req-stale", {
|
|
58
|
+
kind: "host_bash",
|
|
59
|
+
targetClientId: "host-proxy-bind-2",
|
|
60
|
+
});
|
|
61
|
+
const pending = pendingInteractions.get("req-stale");
|
|
62
|
+
expect(pending?.targetConnectionId).toBe(first.connectionId);
|
|
63
|
+
|
|
64
|
+
const second = subscribeClient("host-proxy-bind-2");
|
|
65
|
+
expect(first.active).toBe(false);
|
|
66
|
+
expect(second.connectionId).not.toBe(first.connectionId);
|
|
67
|
+
|
|
68
|
+
await expect(
|
|
69
|
+
assertHostProxyResultBinding({
|
|
70
|
+
headers: {
|
|
71
|
+
"x-vellum-client-id": "host-proxy-bind-2",
|
|
72
|
+
"x-vellum-actor-principal-id": ACTOR,
|
|
73
|
+
},
|
|
74
|
+
targetClientId: pending?.targetClientId,
|
|
75
|
+
targetConnectionId: pending?.targetConnectionId,
|
|
76
|
+
targetActorPrincipalId: ACTOR,
|
|
77
|
+
op: "host_bash",
|
|
78
|
+
missingClientIdMessage: "missing",
|
|
79
|
+
}),
|
|
80
|
+
).rejects.toBeInstanceOf(ForbiddenError);
|
|
81
|
+
second.dispose();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -18,9 +18,29 @@ import {
|
|
|
18
18
|
import { resolveActorPrincipalIdForLocalGuardian } from "../local-actor-identity.js";
|
|
19
19
|
import { BadRequestError, ForbiddenError } from "./errors.js";
|
|
20
20
|
|
|
21
|
+
export function assertHostProxyConnectionBinding(args: {
|
|
22
|
+
targetClientId?: string;
|
|
23
|
+
targetConnectionId?: string;
|
|
24
|
+
}): void {
|
|
25
|
+
if (!args.targetConnectionId) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (
|
|
29
|
+
!assistantEventHub.hasActiveClientConnection(
|
|
30
|
+
args.targetConnectionId,
|
|
31
|
+
args.targetClientId,
|
|
32
|
+
)
|
|
33
|
+
) {
|
|
34
|
+
throw new ForbiddenError(
|
|
35
|
+
"This host-proxy result is bound to a connection that is no longer active.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
export async function assertHostProxyResultBinding(args: {
|
|
22
41
|
headers?: Record<string, string | undefined>;
|
|
23
42
|
targetClientId?: string;
|
|
43
|
+
targetConnectionId?: string;
|
|
24
44
|
targetActorPrincipalId?: string;
|
|
25
45
|
op: SameActorOp;
|
|
26
46
|
missingClientIdMessage: string;
|
|
@@ -43,6 +63,11 @@ export async function assertHostProxyResultBinding(args: {
|
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
|
|
66
|
+
assertHostProxyConnectionBinding({
|
|
67
|
+
targetClientId: args.targetClientId,
|
|
68
|
+
targetConnectionId: args.targetConnectionId,
|
|
69
|
+
});
|
|
70
|
+
|
|
46
71
|
if (!args.targetClientId && !args.targetActorPrincipalId) {
|
|
47
72
|
return;
|
|
48
73
|
}
|
|
@@ -249,6 +249,7 @@ async function handleTransferResult({ body, headers }: RouteHandlerArgs) {
|
|
|
249
249
|
await assertHostProxyResultBinding({
|
|
250
250
|
headers: headers as Record<string, string | undefined> | undefined,
|
|
251
251
|
targetClientId: peeked.targetClientId,
|
|
252
|
+
targetConnectionId: peeked.targetConnectionId,
|
|
252
253
|
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
253
254
|
op: "host_transfer",
|
|
254
255
|
missingClientIdMessage:
|
|
@@ -159,6 +159,7 @@ async function getDetailedHealth() {
|
|
|
159
159
|
memoryOptOut: true,
|
|
160
160
|
retryLastTurn: true,
|
|
161
161
|
appPins: true,
|
|
162
|
+
chatsSettings: true,
|
|
162
163
|
},
|
|
163
164
|
...(profiler ? { profiler } : {}),
|
|
164
165
|
...migrationHealthFields,
|
|
@@ -226,8 +227,6 @@ function getIdentity() {
|
|
|
226
227
|
const content = readFileSync(identityPath, "utf-8");
|
|
227
228
|
const fields = parseIdentityFields(content);
|
|
228
229
|
|
|
229
|
-
const version = APP_VERSION;
|
|
230
|
-
|
|
231
230
|
const createdAt = resolveHatchedAtReadOnly(identityPath);
|
|
232
231
|
|
|
233
232
|
return {
|
|
@@ -236,7 +235,7 @@ function getIdentity() {
|
|
|
236
235
|
personality: fields.personality ?? "",
|
|
237
236
|
emoji: fields.emoji ?? "",
|
|
238
237
|
home: fields.home ?? "",
|
|
239
|
-
version,
|
|
238
|
+
version: APP_VERSION,
|
|
240
239
|
createdAt,
|
|
241
240
|
};
|
|
242
241
|
}
|
|
@@ -280,6 +279,7 @@ const healthCapabilitiesSchema = z.object({
|
|
|
280
279
|
memoryOptOut: z.boolean(),
|
|
281
280
|
retryLastTurn: z.boolean(),
|
|
282
281
|
appPins: z.boolean(),
|
|
282
|
+
chatsSettings: z.boolean().optional(),
|
|
283
283
|
});
|
|
284
284
|
|
|
285
285
|
const healthDiskSchema = z.object({
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "../../../contacts/guardian-delivery-reader.js";
|
|
20
20
|
import { isConversationBusyError } from "../../../daemon/conversation-messaging.js";
|
|
21
21
|
import type { TrustContext } from "../../../daemon/trust-context-types.js";
|
|
22
|
+
import { negotiateLocale } from "../../../i18n/index.js";
|
|
22
23
|
import type { ProviderMessageMetadata } from "../../../messaging/provider-message-metadata.js";
|
|
23
24
|
import {
|
|
24
25
|
channelActivityRefreshMs,
|
|
@@ -288,6 +289,9 @@ export function processChannelMessageInBackground(
|
|
|
288
289
|
trustContext: trustCtx,
|
|
289
290
|
author: trustCtx,
|
|
290
291
|
isInteractive: resolveRoutingState(trustCtx).promptWaitingAllowed,
|
|
292
|
+
...(sourceLanguageCode
|
|
293
|
+
? { locale: negotiateLocale([sourceLanguageCode]) }
|
|
294
|
+
: {}),
|
|
291
295
|
...(displayContent !== undefined ? { displayContent } : {}),
|
|
292
296
|
...(cmdIntent ? { commandIntent: cmdIntent } : {}),
|
|
293
297
|
...(slackInbound ? { slackInbound } : {}),
|
|
@@ -317,7 +317,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
317
317
|
operationId: "integrations_a2a_invite_accept_post",
|
|
318
318
|
endpoint: "integrations/a2a/invite/accept",
|
|
319
319
|
method: "POST",
|
|
320
|
-
policy:
|
|
320
|
+
policy: {
|
|
321
|
+
requiredScopes: ["settings.write"],
|
|
322
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
323
|
+
},
|
|
321
324
|
summary: "Accept A2A invite (self-hosted broker)",
|
|
322
325
|
description:
|
|
323
326
|
"Orchestrate cross-daemon invite acceptance for self-hosted deployments. Calls the sender's invite/complete, then creates a local contact via invite/redeem.",
|
|
@@ -2056,7 +2056,9 @@ export async function handleMigrationPreflightFromGcs({
|
|
|
2056
2056
|
}
|
|
2057
2057
|
|
|
2058
2058
|
const bytes = new Uint8Array(await upstream.arrayBuffer());
|
|
2059
|
-
const validationResult = validateVBundle(bytes
|
|
2059
|
+
const validationResult = validateVBundle(bytes, {
|
|
2060
|
+
enforceStreamingLimits: true,
|
|
2061
|
+
});
|
|
2060
2062
|
|
|
2061
2063
|
if (!validationResult.is_valid || !validationResult.manifest) {
|
|
2062
2064
|
return {
|
|
@@ -94,8 +94,7 @@ async function handleEmitSignal({ body = {} }: RouteHandlerArgs) {
|
|
|
94
94
|
parsed.error.issues[0]?.message ?? "Invalid signal parameters",
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
|
-
const
|
|
98
|
-
const result = await emitNotificationSignal(validated);
|
|
97
|
+
const result = await emitNotificationSignal(parsed.data);
|
|
99
98
|
return {
|
|
100
99
|
signalId: result.signalId,
|
|
101
100
|
dispatched: result.dispatched,
|
|
@@ -135,7 +135,11 @@ export class UserRouteDispatcher {
|
|
|
135
135
|
* @returns A Response from the handler, or an error response (404, 405, 500).
|
|
136
136
|
*/
|
|
137
137
|
async dispatch(routePath: string, request: Request): Promise<Response> {
|
|
138
|
-
|
|
138
|
+
// ".." covers classic dot-segment traversal. "\" is rejected alongside it
|
|
139
|
+
// because some URL normalizers treat a backslash as a path separator, so
|
|
140
|
+
// a routePath that looks in-namespace after percent-decoding can still
|
|
141
|
+
// resolve outside the routes directory once such a normalizer runs.
|
|
142
|
+
if (routePath.includes("..") || routePath.includes("\\")) {
|
|
139
143
|
return httpError("BAD_REQUEST", "Path traversal is not allowed", 400);
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -174,8 +174,7 @@ export function composeVerificationText(
|
|
|
174
174
|
templateKey: TextVerifyTemplateKey,
|
|
175
175
|
vars: GuardianVerifyTemplateVars,
|
|
176
176
|
): string {
|
|
177
|
-
|
|
178
|
-
return composer(vars);
|
|
177
|
+
return templates[templateKey](vars);
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
// ---------------------------------------------------------------------------
|