@vellumai/assistant 0.11.8 → 0.11.9-staging.2
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/ARCHITECTURE.md +2 -2
- package/Dockerfile +8 -48
- package/docker-entrypoint.sh +5 -1
- package/docker-kata-apt-env.sh +3 -0
- package/docker-kata-apt-shims.sh +127 -0
- package/docker-kata-apt-wrapper.sh +45 -0
- package/docker-kata-chroot-exec.sh +35 -0
- package/docker-kata-pip.sh +8 -2
- package/docs/architecture/memory.md +8 -4
- package/docs/guardian-request-flow.md +18 -14
- package/docs/trusted-contact-access.md +1 -7
- package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -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/platform-credential.ts +41 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -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/platform-credential.ts +41 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/openapi.yaml +193 -4
- package/package.json +2 -2
- package/src/__tests__/access-request-card-view.test.ts +6 -5
- package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
- package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
- package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
- package/src/__tests__/attachments-store.test.ts +27 -0
- package/src/__tests__/attachments.test.ts +43 -0
- package/src/__tests__/canned-reply-release.test.ts +48 -5
- package/src/__tests__/channel-delivery-store.test.ts +129 -18
- package/src/__tests__/channel-reply-delivery.test.ts +553 -93
- package/src/__tests__/channel-retry-sweep.test.ts +199 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
- package/src/__tests__/conversation-error.test.ts +15 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
- package/src/__tests__/conversation-pairing.test.ts +141 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
- package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
- package/src/__tests__/conversation-sync-tags.test.ts +2 -37
- package/src/__tests__/credential-health-service.test.ts +98 -10
- package/src/__tests__/delete-propagation.test.ts +469 -0
- package/src/__tests__/dm-persistence.test.ts +16 -0
- package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
- package/src/__tests__/gemini-provider.test.ts +46 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
- package/src/__tests__/guardian-gateway-sim.ts +0 -23
- package/src/__tests__/guardian-question-mode.test.ts +108 -0
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
- package/src/__tests__/host-proxy-interface.test.ts +10 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
- package/src/__tests__/injector-v3-suppression.test.ts +43 -9
- package/src/__tests__/list-messages-page-latest.test.ts +127 -0
- package/src/__tests__/list-messages-system-card.test.ts +103 -0
- package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
- package/src/__tests__/notification-decision-fallback.test.ts +199 -77
- package/src/__tests__/notification-decision-strategy.test.ts +198 -213
- package/src/__tests__/notification-discord-adapter.test.ts +25 -0
- package/src/__tests__/notification-slack-adapter.test.ts +133 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
- package/src/__tests__/openai-provider.test.ts +5 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
- package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
- package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/process-message-display-content.test.ts +20 -9
- package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
- package/src/__tests__/provider-meta-persistence.test.ts +67 -0
- package/src/__tests__/reaction-persistence.test.ts +22 -198
- package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
- package/src/__tests__/skill-load-tool.test.ts +27 -0
- package/src/__tests__/skills.test.ts +34 -1
- package/src/__tests__/strip-memory-injections.test.ts +3 -4
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/thread-backfill.test.ts +5 -3
- package/src/__tests__/unified-turn-context-location.test.ts +76 -0
- package/src/__tests__/voice-session-bridge.test.ts +18 -0
- package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
- package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
- package/src/agent/attachments.ts +13 -2
- package/src/agent/loop.ts +3 -19
- package/src/api/README.md +9 -5
- package/src/api/index.ts +9 -8
- package/src/api/package.json +1 -0
- package/src/api/responses/conversation-message.ts +8 -0
- package/src/api/responses/home.ts +7 -18
- package/src/api/surfaces.ts +114 -7
- package/src/approvals/AGENTS.md +1 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
- package/src/channels/__tests__/types.test.ts +22 -1
- package/src/channels/gateway-guardian-requests.ts +0 -22
- package/src/channels/types.ts +8 -6
- package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
- package/src/cli/commands/channels/index.help.ts +19 -6
- package/src/cli/commands/channels/index.ts +6 -2
- package/src/cli/commands/db/__tests__/status.test.ts +22 -0
- package/src/cli/commands/db/index.help.ts +1 -1
- package/src/cli/commands/db/status.ts +172 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
- package/src/cli/commands/platform/connect.ts +14 -5
- package/src/cli/commands/plugins.help.ts +6 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
- package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
- package/src/cli/lib/bundled-marketplace.json +14 -0
- package/src/cli/lib/install-from-github.ts +9 -5
- package/src/cli/lib/install-from-platform.ts +12 -1
- package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
- package/src/config/assistant-initiated-threads-gate.ts +50 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -3
- package/src/config/bundled-skills/schedule/SKILL.md +25 -11
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
- package/src/config/call-site-defaults.ts +4 -1
- package/src/config/feature-flag-registry.json +21 -4
- package/src/config/schemas/memory-v3.ts +4 -3
- package/src/context/strip-injections.ts +17 -56
- package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
- package/src/conversations/message-consolidation.ts +4 -3
- package/src/credential-health/credential-health-service.ts +130 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
- package/src/daemon/conversation-error.ts +2 -0
- package/src/daemon/conversation-messaging.ts +195 -47
- package/src/daemon/conversation-process.ts +20 -5
- package/src/daemon/conversation-runtime-assembly.ts +48 -39
- package/src/daemon/conversation-store.ts +159 -11
- package/src/daemon/conversation-tool-setup.ts +9 -2
- package/src/daemon/conversation.ts +290 -19
- package/src/daemon/dictation-text-processing.ts +2 -7
- package/src/daemon/handlers/config-channels.ts +33 -3
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/shared.ts +9 -0
- package/src/daemon/port-oversized-content.test.ts +117 -0
- package/src/daemon/port-oversized-content.ts +110 -0
- package/src/daemon/process-message.ts +87 -16
- package/src/daemon/reaction-record.test.ts +23 -11
- package/src/daemon/reaction-record.ts +6 -9
- package/src/documents/document-store.ts +1 -5
- package/src/home/conversation-starter-validation.ts +1 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
- package/src/live-voice/live-voice-photo.ts +544 -61
- package/src/live-voice/live-voice-session.ts +6 -2
- package/src/live-voice/protocol.ts +20 -1
- package/src/messaging/provider-message-metadata.ts +40 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
- package/src/messaging/providers/channel-transport.ts +28 -0
- package/src/messaging/providers/discord/send.ts +3 -2
- package/src/messaging/providers/slack/api.ts +11 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
- package/src/messaging/providers/slack/message-metadata.ts +141 -1
- package/src/messaging/providers/slack/send.test.ts +58 -0
- package/src/messaging/providers/slack/send.ts +4 -4
- package/src/messaging/providers/slack/transport.ts +28 -2
- package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
- package/src/messaging/providers/telegram-bot/send.ts +93 -0
- package/src/messaging/providers/telegram-bot/transport.ts +71 -0
- package/src/messaging/reaction-envelopes.test.ts +73 -0
- package/src/messaging/reaction-envelopes.ts +33 -5
- package/src/messaging/read-provider-metadata.ts +4 -3
- package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
- package/src/monitoring/recovery/db.ts +35 -0
- package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
- package/src/monitoring/recovery/run-recovery.ts +6 -2
- package/src/monitoring/recovery/stale-processing.ts +3 -20
- package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
- package/src/notifications/AGENTS.md +2 -2
- package/src/notifications/README.md +2 -2
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
- package/src/notifications/__tests__/broadcaster.test.ts +35 -4
- package/src/notifications/__tests__/notification-utils.test.ts +31 -0
- package/src/notifications/access-request-copy.ts +126 -114
- package/src/notifications/adapters/discord.ts +9 -5
- package/src/notifications/adapters/shared.ts +17 -4
- package/src/notifications/adapters/slack.ts +14 -4
- package/src/notifications/adapters/telegram.ts +8 -4
- package/src/notifications/approval-card-data.ts +5 -2
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/broadcaster.ts +57 -25
- package/src/notifications/conversation-pairing.ts +68 -2
- package/src/notifications/copy-composer.ts +13 -32
- package/src/notifications/decision-engine.ts +56 -237
- package/src/notifications/guardian-delivery-recorder.ts +5 -9
- package/src/notifications/guardian-feed-projection.ts +0 -15
- package/src/notifications/guardian-question-mode.ts +147 -6
- package/src/notifications/notification-utils.ts +117 -1
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -1
- package/src/persistence/conversation-crud.ts +93 -14
- package/src/persistence/conversation-queries.ts +144 -17
- package/src/persistence/conversation-types.ts +39 -5
- package/src/persistence/delivery-crud.ts +224 -28
- package/src/persistence/delivery-status.ts +21 -2
- package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
- package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
- package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
- package/src/persistence/schema/conversations.ts +71 -2
- package/src/persistence/schema-contract.test.ts +78 -0
- package/src/persistence/schema-contract.ts +96 -0
- package/src/persistence/steps.ts +4 -0
- package/src/platform/client.ts +55 -0
- package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
- package/src/plugins/defaults/injector-order.ts +2 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
- package/src/plugins/defaults/memory/injectors.ts +1 -1
- package/src/plugins/defaults/memory/memory-marker.ts +17 -7
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
- package/src/plugins/defaults/memory/v3/injector.ts +14 -15
- package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
- package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
- package/src/plugins/defaults/memory/v3/types.ts +15 -4
- package/src/plugins/defaults/turn-context/injectors.ts +3 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
- package/src/plugins/mcp-servers.ts +14 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
- package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
- package/src/providers/anthropic/client.ts +21 -24
- package/src/providers/call-site-routing.ts +5 -4
- package/src/providers/connection-resolution.ts +29 -1
- package/src/providers/content-block-size.test.ts +82 -0
- package/src/providers/content-block-size.ts +99 -0
- package/src/providers/file-block-text.test.ts +64 -0
- package/src/providers/file-block-text.ts +28 -0
- package/src/providers/gemini/client.ts +15 -9
- package/src/providers/inference/auth.ts +6 -6
- package/src/providers/media-resolve.ts +14 -0
- package/src/providers/model-catalog.ts +47 -34
- package/src/providers/openai/chat-completions-provider.ts +9 -25
- package/src/providers/openai/responses-provider.ts +11 -18
- package/src/providers/registry.ts +10 -7
- package/src/providers/routing-identity.ts +2 -1
- package/src/providers/types.ts +1 -4
- package/src/providers/vellum-model-routing.ts +3 -2
- package/src/runtime/AGENTS.md +40 -15
- package/src/runtime/approval-message-composer.ts +1 -6
- package/src/runtime/assistant-event-hub.ts +2 -39
- package/src/runtime/channel-approval-types.ts +1 -5
- package/src/runtime/channel-reply-delivery.ts +182 -115
- package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
- package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
- package/src/runtime/channel-retry-sweep.ts +102 -1
- package/src/runtime/finalize-event-delivery.ts +4 -4
- package/src/runtime/guardian-action-message-composer.ts +1 -4
- package/src/runtime/guardian-reply-router.ts +4 -75
- package/src/runtime/http-router.ts +1 -5
- package/src/runtime/question-request-guardian-bridge.ts +2 -3
- package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
- package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
- package/src/runtime/routes/acp-routes.ts +1 -1
- package/src/runtime/routes/canned-reply-release.ts +19 -9
- package/src/runtime/routes/channel-route-shared.ts +0 -39
- package/src/runtime/routes/channel-verification-routes.ts +4 -1
- package/src/runtime/routes/conversation-list-routes.ts +44 -3
- package/src/runtime/routes/conversation-management-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +138 -59
- package/src/runtime/routes/diagnostics-routes.ts +111 -60
- package/src/runtime/routes/guardian-action-routes.ts +10 -14
- package/src/runtime/routes/guardian-approval-interception.ts +0 -5
- package/src/runtime/routes/inbound-message-handler.ts +170 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +99 -10
- package/src/runtime/routes/sight-frame-routes.ts +136 -0
- package/src/runtime/sync/resource-sync-events.ts +14 -37
- package/src/runtime/sync/sync-publisher.test.ts +5 -4
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/client-os.ts +11 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +4 -3
- package/src/tools/watch/watch-retro-report.ts +205 -0
- package/src/watch/__tests__/watch-retro.test.ts +268 -40
- package/src/watch/watch-retro.ts +190 -77
- package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +4 -0
- package/docker-kata-pip-chroot.sh +0 -22
- package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
- package/src/api/events/conversation-list-invalidated.ts +0 -38
|
@@ -441,7 +441,7 @@ const NAMESPACE_IMPORT = "*";
|
|
|
441
441
|
* namespace import yields the {@link NAMESPACE_IMPORT} sentinel — it reaches
|
|
442
442
|
* the whole surface, so it must not slip past the anti-backslide check. */
|
|
443
443
|
function symbolsImportedFrom(source: string, hostPathSuffix: string): string[] {
|
|
444
|
-
const escaped =
|
|
444
|
+
const escaped = RegExp.escape(hostPathSuffix);
|
|
445
445
|
const from = String.raw`\s*from\s*['"]([^'"]*` + escaped + String.raw`)['"]`;
|
|
446
446
|
const namedRegex = new RegExp(
|
|
447
447
|
String.raw`import\s+(?:type\s+)?\{([^}]*)\}` + from,
|
|
@@ -224,6 +224,17 @@ describe("resolvePricing", () => {
|
|
|
224
224
|
);
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
+
test("returns priced for gemini-3.8-flash", () => {
|
|
228
|
+
const result = resolvePricing(
|
|
229
|
+
"gemini",
|
|
230
|
+
"gemini-3.8-flash",
|
|
231
|
+
1_000_000,
|
|
232
|
+
1_000_000,
|
|
233
|
+
);
|
|
234
|
+
expect(result.pricingStatus).toBe("priced");
|
|
235
|
+
expect(result.estimatedCostUsd).toBe(1.5 + 7.5);
|
|
236
|
+
});
|
|
237
|
+
|
|
227
238
|
test("returns priced for gemini-3-flash-preview", () => {
|
|
228
239
|
const result = resolvePricing(
|
|
229
240
|
"gemini",
|
|
@@ -160,6 +160,7 @@ function makeTestConversation() {
|
|
|
160
160
|
size: () => 0,
|
|
161
161
|
} as unknown as MessageQueue;
|
|
162
162
|
let processing = false;
|
|
163
|
+
let owner = 0;
|
|
163
164
|
const messagingCtx: MessagingConversationContext = {
|
|
164
165
|
conversationId: "conv-display-content",
|
|
165
166
|
messages,
|
|
@@ -167,6 +168,21 @@ function makeTestConversation() {
|
|
|
167
168
|
setProcessing: (value: boolean) => {
|
|
168
169
|
processing = value;
|
|
169
170
|
},
|
|
171
|
+
acquireProcessingFenced: async () => {
|
|
172
|
+
if (processing) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
processing = true;
|
|
176
|
+
owner += 1;
|
|
177
|
+
return owner;
|
|
178
|
+
},
|
|
179
|
+
releaseProcessing: (claim: number) => {
|
|
180
|
+
if (claim !== owner) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
processing = false;
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
170
186
|
abortController: null,
|
|
171
187
|
queue: queueStub,
|
|
172
188
|
getTurnChannelContext: () => turnChannelContext,
|
|
@@ -398,20 +414,15 @@ describe("processMessage displayContent", () => {
|
|
|
398
414
|
type: "text",
|
|
399
415
|
text: modelContent,
|
|
400
416
|
});
|
|
401
|
-
|
|
402
|
-
string,
|
|
403
|
-
unknown
|
|
404
|
-
>;
|
|
405
|
-
expect(inMemoryFileBlock._attachmentId).toBe("att-1");
|
|
406
|
-
expect(inMemoryFileBlock).toMatchObject({
|
|
417
|
+
expect(inMemoryMessage.content[1]).toEqual({
|
|
407
418
|
type: "file",
|
|
408
419
|
source: {
|
|
409
|
-
type: "
|
|
420
|
+
type: "workspace_ref",
|
|
410
421
|
media_type: "application/pdf",
|
|
411
|
-
|
|
422
|
+
attachmentId: "att-stored",
|
|
423
|
+
sizeBytes: 9,
|
|
412
424
|
filename: "attachment.pdf",
|
|
413
425
|
},
|
|
414
|
-
extracted_text: undefined,
|
|
415
426
|
});
|
|
416
427
|
});
|
|
417
428
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every acquisition of the processing flag goes through the fenced helper.
|
|
3
|
+
*
|
|
4
|
+
* `acquireProcessing` hands back a live claim, and the marker fence that has
|
|
5
|
+
* to follow it can fail. A site that calls the two itself owns the ordering,
|
|
6
|
+
* and getting it wrong (fencing outside the try whose finally releases) leaves
|
|
7
|
+
* the conversation processing for good with every later send queued behind a
|
|
8
|
+
* dead hold. `acquireProcessingFenced` owns that ordering once, so the rule is
|
|
9
|
+
* simply that nothing else calls the primitive.
|
|
10
|
+
*/
|
|
11
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
/** Where the primitive and its fenced wrapper legitimately live together. */
|
|
16
|
+
const OWNER_FILE = join("src", "daemon", "conversation.ts");
|
|
17
|
+
|
|
18
|
+
function sourceFiles(dir: string, found: string[] = []): string[] {
|
|
19
|
+
for (const entry of readdirSync(dir)) {
|
|
20
|
+
const full = join(dir, entry);
|
|
21
|
+
if (statSync(full).isDirectory()) {
|
|
22
|
+
if (entry !== "node_modules" && entry !== "__tests__") {
|
|
23
|
+
sourceFiles(full, found);
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry.endsWith(".ts") && !entry.endsWith(".test.ts")) {
|
|
28
|
+
found.push(full);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("processing flag acquisition", () => {
|
|
35
|
+
test("only the fenced helper calls the raw acquire", () => {
|
|
36
|
+
const offenders = sourceFiles("src").filter(
|
|
37
|
+
(file) =>
|
|
38
|
+
file !== OWNER_FILE &&
|
|
39
|
+
/\bacquireProcessing\s*\(\s*\)/.test(readFileSync(file, "utf8")),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(offenders).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("only the fenced helper awaits the marker fence", () => {
|
|
46
|
+
// Same rule for the other half: a site that awaits the fence itself is a
|
|
47
|
+
// site that can await it in the wrong place.
|
|
48
|
+
const offenders = sourceFiles("src").filter(
|
|
49
|
+
(file) =>
|
|
50
|
+
file !== OWNER_FILE &&
|
|
51
|
+
/\bensureProcessingMarker\s*\(/.test(readFileSync(file, "utf8")),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(offenders).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { buildProviderMetaForPersistence } from "../daemon/conversation-messaging.js";
|
|
4
|
+
import type { ProviderMessageMetadata } from "../messaging/provider-message-metadata.js";
|
|
5
|
+
import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
|
|
6
|
+
|
|
7
|
+
const telegramInbound: ProviderMessageMetadata = {
|
|
8
|
+
source: "telegram",
|
|
9
|
+
conversationExternalId: "chat-42",
|
|
10
|
+
messageId: "msg-100",
|
|
11
|
+
threadId: "topic-7",
|
|
12
|
+
actorExternalId: "user-9",
|
|
13
|
+
displayName: "Alice",
|
|
14
|
+
eventKind: "message",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("buildProviderMetaForPersistence", () => {
|
|
18
|
+
test("a matching non-Slack turn persists an envelope the neutral reader round-trips", () => {
|
|
19
|
+
const serialized = buildProviderMetaForPersistence({
|
|
20
|
+
channelInbound: telegramInbound,
|
|
21
|
+
turnChannel: "telegram",
|
|
22
|
+
});
|
|
23
|
+
expect(serialized).not.toBeNull();
|
|
24
|
+
|
|
25
|
+
// The stored row's metadata JSON carries the envelope under the
|
|
26
|
+
// `providerMeta` key; the canonical reader must resolve it.
|
|
27
|
+
const rowMetadata = JSON.stringify({
|
|
28
|
+
userMessageChannel: "telegram",
|
|
29
|
+
providerMeta: serialized,
|
|
30
|
+
});
|
|
31
|
+
const read = readProviderMetadata(rowMetadata);
|
|
32
|
+
expect(read).toEqual(telegramInbound);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a Slack turn never gains a providerMeta key", () => {
|
|
36
|
+
expect(
|
|
37
|
+
buildProviderMetaForPersistence({
|
|
38
|
+
channelInbound: { ...telegramInbound, source: "slack" },
|
|
39
|
+
turnChannel: "slack",
|
|
40
|
+
}),
|
|
41
|
+
).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a turn channel that does not match the envelope's source is refused", () => {
|
|
45
|
+
expect(
|
|
46
|
+
buildProviderMetaForPersistence({
|
|
47
|
+
channelInbound: telegramInbound,
|
|
48
|
+
turnChannel: "discord",
|
|
49
|
+
}),
|
|
50
|
+
).toBeNull();
|
|
51
|
+
expect(
|
|
52
|
+
buildProviderMetaForPersistence({
|
|
53
|
+
channelInbound: telegramInbound,
|
|
54
|
+
turnChannel: undefined,
|
|
55
|
+
}),
|
|
56
|
+
).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("an absent envelope persists nothing", () => {
|
|
60
|
+
expect(
|
|
61
|
+
buildProviderMetaForPersistence({
|
|
62
|
+
channelInbound: undefined,
|
|
63
|
+
turnChannel: "telegram",
|
|
64
|
+
}),
|
|
65
|
+
).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -20,6 +20,15 @@ mock.module("../config/env.js", () => ({
|
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
22
|
const _conversationMocks = new Map<string, unknown>();
|
|
23
|
+
// Wake dispatches are captured, not run: this suite pins persistence, and
|
|
24
|
+
// the real background machinery would race the assertions.
|
|
25
|
+
const dispatchedWakes: Array<Record<string, unknown>> = [];
|
|
26
|
+
mock.module("../runtime/routes/inbound-stages/background-dispatch.js", () => ({
|
|
27
|
+
processChannelMessageInBackground: (params: Record<string, unknown>) => {
|
|
28
|
+
dispatchedWakes.push(params);
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
23
32
|
mock.module("../daemon/conversation-registry.js", () => ({
|
|
24
33
|
findConversation: (id: string) => _conversationMocks.get(id),
|
|
25
34
|
}));
|
|
@@ -78,14 +87,12 @@ import { getDb } from "../persistence/db-connection.js";
|
|
|
78
87
|
import { initializeDb } from "../persistence/db-init.js";
|
|
79
88
|
import { linkMessage, recordInbound } from "../persistence/delivery-crud.js";
|
|
80
89
|
import { messages } from "../persistence/schema/conversations.js";
|
|
81
|
-
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
82
90
|
import { isReactionEvent } from "../runtime/routes/inbound-stages/reaction-intercept.js";
|
|
83
91
|
import {
|
|
84
92
|
handleChannelInbound,
|
|
85
93
|
seedContactChannel,
|
|
86
94
|
} from "./helpers/channel-test-adapter.js";
|
|
87
95
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
88
|
-
import { bridgeState } from "./helpers/gateway-guardian-requests-store-bridge.js";
|
|
89
96
|
|
|
90
97
|
await initializeDb();
|
|
91
98
|
|
|
@@ -286,6 +293,8 @@ describe("Slack reaction event persistence", () => {
|
|
|
286
293
|
expect(slackMeta!.actorExternalUserId).toBe(SLACK_USER_ID);
|
|
287
294
|
expect(slackMeta!.reaction).toEqual({
|
|
288
295
|
emoji: "thumbsup",
|
|
296
|
+
emojiKind: "shortcode",
|
|
297
|
+
emojiName: "thumbsup",
|
|
289
298
|
actorDisplayName: SLACK_DISPLAY_NAME,
|
|
290
299
|
targetChannelTs: "1700000000.111111",
|
|
291
300
|
op: "added",
|
|
@@ -490,13 +499,21 @@ describe("Slack reaction event persistence", () => {
|
|
|
490
499
|
TEST_BEARER_TOKEN,
|
|
491
500
|
);
|
|
492
501
|
expect(resp.status).toBe(200);
|
|
493
|
-
|
|
502
|
+
const body = (await resp.json()) as Record<string, unknown>;
|
|
503
|
+
|
|
504
|
+
// An admitted actor adding a reaction to the assistant's own post wakes
|
|
505
|
+
// a discretion turn instead of writing the passive row here: the turn's
|
|
506
|
+
// own persisted user row is the reaction row.
|
|
507
|
+
expect(body.reaction).toBe("wake_dispatched");
|
|
508
|
+
expect(dispatchedWakes).toHaveLength(1);
|
|
509
|
+
expect(dispatchedWakes[0].conversationId).toBe(conversationId);
|
|
510
|
+
expect(typeof dispatchedWakes[0].slackReactionRowMeta).toBe("string");
|
|
494
511
|
const reactionRow = db.$client
|
|
495
512
|
.prepare(
|
|
496
513
|
"SELECT conversation_id AS conversationId FROM messages WHERE content = '[reaction]'",
|
|
497
514
|
)
|
|
498
515
|
.get() as { conversationId: string } | null;
|
|
499
|
-
expect(reactionRow
|
|
516
|
+
expect(reactionRow).toBeNull();
|
|
500
517
|
});
|
|
501
518
|
|
|
502
519
|
test("a reaction leaves the reacted message resolvable by its own id", async () => {
|
|
@@ -606,200 +623,6 @@ describe("Slack reaction event persistence", () => {
|
|
|
606
623
|
});
|
|
607
624
|
});
|
|
608
625
|
|
|
609
|
-
// ---------------------------------------------------------------------------
|
|
610
|
-
// Guardian approval-by-reaction integration test
|
|
611
|
-
// ---------------------------------------------------------------------------
|
|
612
|
-
//
|
|
613
|
-
// Verifies that approval interception runs before reaction persistence so a
|
|
614
|
-
// guardian's `reaction:` event on a pending approval prompt applies the
|
|
615
|
-
// decision and no transcript-line row is written for the reaction itself.
|
|
616
|
-
|
|
617
|
-
const GUARDIAN_USER_ID = "U_GUARDIAN_REACT";
|
|
618
|
-
const GUARDIAN_DISPLAY_NAME = "Guardian Reactor";
|
|
619
|
-
const GUARDIAN_REACTION_TOOL = "execute_shell";
|
|
620
|
-
const GUARDIAN_REACTION_INPUT = { command: "rm -rf /tmp/test" };
|
|
621
|
-
|
|
622
|
-
function seedGuardianForChannel(): void {
|
|
623
|
-
seedGatewayGuardian({
|
|
624
|
-
channelType: "slack",
|
|
625
|
-
address: GUARDIAN_USER_ID,
|
|
626
|
-
principalId: GUARDIAN_USER_ID,
|
|
627
|
-
externalChatId: SLACK_CHANNEL_ID,
|
|
628
|
-
});
|
|
629
|
-
createGuardianBinding({
|
|
630
|
-
channel: "slack",
|
|
631
|
-
guardianExternalUserId: GUARDIAN_USER_ID,
|
|
632
|
-
guardianDeliveryChatId: SLACK_CHANNEL_ID,
|
|
633
|
-
guardianPrincipalId: GUARDIAN_USER_ID,
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function seedPendingGuardianApprovalForReaction(
|
|
638
|
-
requestId: string,
|
|
639
|
-
conversationId: string,
|
|
640
|
-
reactedTs: string,
|
|
641
|
-
): void {
|
|
642
|
-
// Canonical tool_approval request keyed by the confirmation requestId — the
|
|
643
|
-
// same record assistant-event-hub creates for every confirmation.
|
|
644
|
-
bridgeState.seedRequest({
|
|
645
|
-
id: requestId,
|
|
646
|
-
kind: "tool_approval",
|
|
647
|
-
sourceType: "channel",
|
|
648
|
-
sourceChannel: "slack",
|
|
649
|
-
sourceConversationId: conversationId,
|
|
650
|
-
requesterExternalUserId: "requester-user-1",
|
|
651
|
-
requesterChatId: SLACK_CHANNEL_ID,
|
|
652
|
-
guardianExternalUserId: GUARDIAN_USER_ID,
|
|
653
|
-
guardianPrincipalId: GUARDIAN_USER_ID,
|
|
654
|
-
toolName: GUARDIAN_REACTION_TOOL,
|
|
655
|
-
status: "pending",
|
|
656
|
-
expiresAt: Date.now() + 300_000,
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
// The delivered approval card → request mapping the reaction resolves
|
|
660
|
-
// against (destination message id = the reacted Slack ts).
|
|
661
|
-
bridgeState.seedDelivery({
|
|
662
|
-
requestId,
|
|
663
|
-
destinationChannel: "slack",
|
|
664
|
-
destinationChatId: SLACK_CHANNEL_ID,
|
|
665
|
-
destinationMessageId: reactedTs,
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
// Register a pending interaction so the tool_approval resolver finds the
|
|
669
|
-
// requester-side hook to drive `allow`.
|
|
670
|
-
const handleConfirmationResponse = mock(() => {});
|
|
671
|
-
const _mockSession = {
|
|
672
|
-
handleConfirmationResponse,
|
|
673
|
-
ensureActorScopedHistory: async () => {},
|
|
674
|
-
} as unknown as Conversation;
|
|
675
|
-
_conversationMocks.set(conversationId, _mockSession);
|
|
676
|
-
pendingInteractions.register(requestId, {
|
|
677
|
-
conversationId,
|
|
678
|
-
kind: "confirmation",
|
|
679
|
-
confirmationDetails: {
|
|
680
|
-
toolName: GUARDIAN_REACTION_TOOL,
|
|
681
|
-
input: GUARDIAN_REACTION_INPUT,
|
|
682
|
-
riskLevel: "high",
|
|
683
|
-
allowlistOptions: [
|
|
684
|
-
{ label: "test", description: "test", pattern: "test" },
|
|
685
|
-
],
|
|
686
|
-
scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
|
|
687
|
-
},
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
describe("guardian approval-by-reaction integration via handleChannelInbound", () => {
|
|
692
|
-
beforeEach(() => {
|
|
693
|
-
const db = getDb();
|
|
694
|
-
db.run("DELETE FROM messages");
|
|
695
|
-
db.run("DELETE FROM channel_inbound_events");
|
|
696
|
-
db.run("DELETE FROM conversations");
|
|
697
|
-
db.run("DELETE FROM contact_channels");
|
|
698
|
-
db.run("DELETE FROM contacts");
|
|
699
|
-
bridgeState.reset();
|
|
700
|
-
gatewayGuardians = [];
|
|
701
|
-
pendingInteractions.clear();
|
|
702
|
-
msgCounter = 0;
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
test("guardian reaction on pending approval applies decision and persists no transcript row", async () => {
|
|
706
|
-
seedGuardianForChannel();
|
|
707
|
-
const requestId = "req-guardian-react-1";
|
|
708
|
-
// Back the guardian request and its pending interaction with a real
|
|
709
|
-
// conversation row, inserted directly via the DB layer.
|
|
710
|
-
const db = getDb();
|
|
711
|
-
const conversationId = "conv-react-test-1";
|
|
712
|
-
const now = Date.now();
|
|
713
|
-
db.$client
|
|
714
|
-
.prepare(
|
|
715
|
-
`INSERT INTO conversations (
|
|
716
|
-
id, title, created_at, updated_at, total_input_tokens, total_output_tokens,
|
|
717
|
-
total_estimated_cost, context_compacted_message_count, conversation_type,
|
|
718
|
-
source, memory_scope_id, host_access, is_auto_title
|
|
719
|
-
) VALUES (?, NULL, ?, ?, 0, 0, 0, 0, 'standard', 'user', 'default', 0, 1)`,
|
|
720
|
-
)
|
|
721
|
-
.run(conversationId, now, now);
|
|
722
|
-
|
|
723
|
-
const reactedTs = "1700000099.000001";
|
|
724
|
-
// The approval card was delivered on this ts; its canonical delivery row
|
|
725
|
-
// is how the guardian reaction is scoped to a known approval message.
|
|
726
|
-
seedPendingGuardianApprovalForReaction(
|
|
727
|
-
requestId,
|
|
728
|
-
conversationId,
|
|
729
|
-
reactedTs,
|
|
730
|
-
);
|
|
731
|
-
|
|
732
|
-
const body = {
|
|
733
|
-
sourceChannel: "slack",
|
|
734
|
-
interface: "slack",
|
|
735
|
-
conversationExternalId: SLACK_CHANNEL_ID,
|
|
736
|
-
externalMessageId: `${SLACK_CHANNEL_ID}:${reactedTs}:guardian-react`,
|
|
737
|
-
content: "reaction:white_check_mark",
|
|
738
|
-
callbackData: "reaction:white_check_mark",
|
|
739
|
-
actorExternalId: GUARDIAN_USER_ID,
|
|
740
|
-
actorDisplayName: GUARDIAN_DISPLAY_NAME,
|
|
741
|
-
replyCallbackUrl: "http://localhost:7830/deliver/slack",
|
|
742
|
-
sourceMetadata: {
|
|
743
|
-
messageId: reactedTs,
|
|
744
|
-
chatType: "channel",
|
|
745
|
-
},
|
|
746
|
-
};
|
|
747
|
-
const req = new Request("http://localhost:8080/channels/inbound", {
|
|
748
|
-
method: "POST",
|
|
749
|
-
headers: {
|
|
750
|
-
"Content-Type": "application/json",
|
|
751
|
-
"X-Gateway-Origin": TEST_BEARER_TOKEN,
|
|
752
|
-
},
|
|
753
|
-
body: JSON.stringify(body),
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
// Wire a non-undefined `approvalConversationGenerator` to mirror the
|
|
757
|
-
// production configuration. The deterministic reaction handler must
|
|
758
|
-
// short-circuit before the generator is consulted — if it runs, it
|
|
759
|
-
// returns `keep_pending` so the assertions below would fail loudly.
|
|
760
|
-
const approvalConversationGenerator = mock(async () => ({
|
|
761
|
-
disposition: "keep_pending" as const,
|
|
762
|
-
replyText: "mock conversational turn should not be invoked for reactions",
|
|
763
|
-
}));
|
|
764
|
-
|
|
765
|
-
const resp = await handleChannelInbound(
|
|
766
|
-
req,
|
|
767
|
-
undefined,
|
|
768
|
-
TEST_BEARER_TOKEN,
|
|
769
|
-
undefined,
|
|
770
|
-
approvalConversationGenerator,
|
|
771
|
-
);
|
|
772
|
-
const json = (await resp.json()) as Record<string, unknown>;
|
|
773
|
-
|
|
774
|
-
expect(resp.status).toBe(200);
|
|
775
|
-
expect(json.accepted).toBe(true);
|
|
776
|
-
expect(json.canonicalRouter).toBe("canonical_decision_applied");
|
|
777
|
-
expect(approvalConversationGenerator).not.toHaveBeenCalled();
|
|
778
|
-
|
|
779
|
-
// The guardian request is resolved (no longer pending).
|
|
780
|
-
expect(bridgeState.getRequest(requestId)?.status).toBe("approved");
|
|
781
|
-
|
|
782
|
-
// No transcript row was written for the reaction itself — resolved
|
|
783
|
-
// guardian approval reactions have no transcript representation.
|
|
784
|
-
const reactionRows = readPersistedMessages().filter((row) => {
|
|
785
|
-
if (!row.metadata) {
|
|
786
|
-
return false;
|
|
787
|
-
}
|
|
788
|
-
try {
|
|
789
|
-
const env = JSON.parse(row.metadata) as Record<string, unknown>;
|
|
790
|
-
if (typeof env.slackMeta !== "string") {
|
|
791
|
-
return false;
|
|
792
|
-
}
|
|
793
|
-
const meta = readSlackMetadata(env.slackMeta);
|
|
794
|
-
return meta?.eventKind === "reaction";
|
|
795
|
-
} catch {
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
expect(reactionRows.length).toBe(0);
|
|
800
|
-
});
|
|
801
|
-
});
|
|
802
|
-
|
|
803
626
|
// ---------------------------------------------------------------------------
|
|
804
627
|
// Reaction access-control regression (LUM-2489)
|
|
805
628
|
// ---------------------------------------------------------------------------
|
|
@@ -813,6 +636,7 @@ describe("guardian approval-by-reaction integration via handleChannelInbound", (
|
|
|
813
636
|
describe("reaction access control (no verification handshake)", () => {
|
|
814
637
|
const STRANGER_USER_ID = "U_REACTION_STRANGER";
|
|
815
638
|
const CONTACT_USER_ID = "U_REACTION_CONTACT";
|
|
639
|
+
const GUARDIAN_USER_ID = "U_GUARDIAN_REACT";
|
|
816
640
|
// Guardian's approval channel is a DM, distinct from the public channel the
|
|
817
641
|
// reaction lands in (mirroring production). Reusing the public channel id
|
|
818
642
|
// would let a reactor match the guardian's channel via findContactChannel's
|
|
@@ -196,8 +196,11 @@ describe("runConversationTurn persistence", () => {
|
|
|
196
196
|
// send-message route.
|
|
197
197
|
expect(listInvalidations()).toEqual([creationTags(result.conversationId)]);
|
|
198
198
|
expect(
|
|
199
|
-
broadcasts.filter(
|
|
200
|
-
|
|
199
|
+
broadcasts.filter(
|
|
200
|
+
(msg) =>
|
|
201
|
+
(msg as { type: string }).type === "conversation_list_invalidated",
|
|
202
|
+
),
|
|
203
|
+
).toEqual([]);
|
|
201
204
|
});
|
|
202
205
|
|
|
203
206
|
test("adopts a caller-supplied conversation id verbatim when no row exists", async () => {
|
|
@@ -89,6 +89,7 @@ function createContext(): MessagingConversationContext {
|
|
|
89
89
|
size: () => 0,
|
|
90
90
|
} as unknown as MessageQueue;
|
|
91
91
|
let processing = false;
|
|
92
|
+
let owner = 0;
|
|
92
93
|
return {
|
|
93
94
|
conversationId: "conv-scripted-test",
|
|
94
95
|
messages: [],
|
|
@@ -96,6 +97,21 @@ function createContext(): MessagingConversationContext {
|
|
|
96
97
|
setProcessing: (value: boolean) => {
|
|
97
98
|
processing = value;
|
|
98
99
|
},
|
|
100
|
+
acquireProcessingFenced: async () => {
|
|
101
|
+
if (processing) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
processing = true;
|
|
105
|
+
owner += 1;
|
|
106
|
+
return owner;
|
|
107
|
+
},
|
|
108
|
+
releaseProcessing: (claim: number) => {
|
|
109
|
+
if (claim !== owner) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
processing = false;
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
99
115
|
abortController: null,
|
|
100
116
|
queue: queueStub,
|
|
101
117
|
clientOs: undefined,
|
|
@@ -127,6 +127,20 @@ describe("skill_load tool", () => {
|
|
|
127
127
|
);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
test("description routes Claude Code and Codex mentions to acp", () => {
|
|
131
|
+
// skill_load is always in the tool list. Naming the agents here is what
|
|
132
|
+
// makes a mention load ACP instead of treating Claude Code as a competitor.
|
|
133
|
+
expect(skillLoadTool.description).toContain("Claude Code");
|
|
134
|
+
expect(skillLoadTool.description).toContain("Codex");
|
|
135
|
+
expect(skillLoadTool.description).toContain('skill: "acp"');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("routes recurring monitoring and reminders to the schedule skill", () => {
|
|
139
|
+
const description = skillLoadTool.description;
|
|
140
|
+
expect(description).toContain('skill: "schedule"');
|
|
141
|
+
expect(description.toLowerCase()).toContain("recurring monitoring");
|
|
142
|
+
});
|
|
143
|
+
|
|
130
144
|
test("loads a skill by exact id", async () => {
|
|
131
145
|
writeSkill(
|
|
132
146
|
"release-checklist",
|
|
@@ -393,6 +407,19 @@ describe("skill_load tool", () => {
|
|
|
393
407
|
expect(result.content).toContain('<loaded_skill id="app-builder"');
|
|
394
408
|
});
|
|
395
409
|
|
|
410
|
+
test("bundled acp skill offers a one-time connect when the user names an agent", async () => {
|
|
411
|
+
const result = await executeSkillLoad({ skill: "acp" });
|
|
412
|
+
|
|
413
|
+
expect(result.isError).toBe(false);
|
|
414
|
+
expect(result.content).toContain("Skill: ACP");
|
|
415
|
+
expect(result.content).toContain(
|
|
416
|
+
"When the user names Claude Code or Codex",
|
|
417
|
+
);
|
|
418
|
+
expect(result.content).toContain("offer once, in one short sentence");
|
|
419
|
+
expect(result.content).toContain("Do not spawn unless they accept");
|
|
420
|
+
expect(result.content).toContain('<loaded_skill id="acp"');
|
|
421
|
+
});
|
|
422
|
+
|
|
396
423
|
test("bundled phone-calls loads when setup includes are unavailable", async () => {
|
|
397
424
|
const result = await executeSkillLoad({ skill: "phone-calls" });
|
|
398
425
|
|
|
@@ -869,16 +869,49 @@ describe("always-candidate frontmatter parsing", () => {
|
|
|
869
869
|
expect(card).toContain("Avoid when:");
|
|
870
870
|
});
|
|
871
871
|
|
|
872
|
+
test("the bundled schedule skill's card fits the default budget", async () => {
|
|
873
|
+
// Recurring-monitoring turns only reach this skill if the card still
|
|
874
|
+
// carries that vocabulary after the default-budget truncation. An overrun
|
|
875
|
+
// drops trailing hints and the whole avoid-when list.
|
|
876
|
+
const { DEFAULT_CARD_CHARS, buildSkillContent } =
|
|
877
|
+
await import("../plugins/defaults/memory/substrate/skill-content.js");
|
|
878
|
+
const schedule = loadSkillCatalog().find((skill) => skill.id === "schedule");
|
|
879
|
+
expect(schedule).toBeDefined();
|
|
880
|
+
|
|
881
|
+
const card = buildSkillContent(schedule!);
|
|
882
|
+
expect(card.length).toBeLessThan(DEFAULT_CARD_CHARS);
|
|
883
|
+
expect(card).toContain("Avoid when:");
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test("the bundled schedule card carries its monitoring vocabulary", async () => {
|
|
887
|
+
const { buildSkillContent } =
|
|
888
|
+
await import("../plugins/defaults/memory/substrate/skill-content.js");
|
|
889
|
+
const schedule = loadSkillCatalog().find((skill) => skill.id === "schedule");
|
|
890
|
+
const card = buildSkillContent(schedule!).toLowerCase();
|
|
891
|
+
|
|
892
|
+
for (const term of ["monitor", "page", "dashboard", "status", "alert"]) {
|
|
893
|
+
expect(card).toContain(term);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
872
897
|
test("the bundled acp card carries its setup and sign-in vocabulary", async () => {
|
|
873
898
|
// A "configure Claude Code" or "let's do the auth" turn only reaches this
|
|
874
899
|
// skill if the card actually says it covers setup and authentication;
|
|
875
900
|
// delegation-only wording routes those turns to a terminal skill instead.
|
|
901
|
+
// "names" keeps a mention ("claude code will do it") from falling through
|
|
902
|
+
// as a competitor reference.
|
|
876
903
|
const { buildSkillContent } =
|
|
877
904
|
await import("../plugins/defaults/memory/substrate/skill-content.js");
|
|
878
905
|
const acp = loadSkillCatalog().find((skill) => skill.id === "acp");
|
|
879
906
|
const card = buildSkillContent(acp!).toLowerCase();
|
|
880
907
|
|
|
881
|
-
for (const term of [
|
|
908
|
+
for (const term of [
|
|
909
|
+
"names",
|
|
910
|
+
"set up",
|
|
911
|
+
"install",
|
|
912
|
+
"authenticate",
|
|
913
|
+
"connect",
|
|
914
|
+
]) {
|
|
882
915
|
expect(card).toContain(term);
|
|
883
916
|
}
|
|
884
917
|
});
|
|
@@ -220,10 +220,9 @@ describe("stripExistingMemoryInjections", () => {
|
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
// ---------------------------------------------------------------------------
|
|
223
|
-
// stripSpotlightInjections
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
// the old whole-layer stripAllMemoryInjections replace is gone.
|
|
223
|
+
// stripSpotlightInjections: leftover memory-v3 spotlight blocks are removed
|
|
224
|
+
// from every user message (compaction leftover cleanup). Frozen `<memory>`
|
|
225
|
+
// card blocks must never be touched (the cache contract).
|
|
227
226
|
// ---------------------------------------------------------------------------
|
|
228
227
|
|
|
229
228
|
const spotlightBlock = (inner: string): ContentBlock => ({
|
|
@@ -146,6 +146,15 @@ describe("buildSanitizedEnv", () => {
|
|
|
146
146
|
expect(env.VELLUM_APT_DATA_ROOT).toBe("/data/system");
|
|
147
147
|
expect(env.PATH.split(":")).toContain("/data/system/usr/bin");
|
|
148
148
|
expect(env.PATH.split(":")).toContain("/data/system/usr/local/bin");
|
|
149
|
+
// Shims must shadow the chroot bin dirs so a shim beats a wrapper script
|
|
150
|
+
// whose hardcoded absolute path only resolves inside the chroot.
|
|
151
|
+
const kataPathParts = env.PATH.split(":");
|
|
152
|
+
expect(
|
|
153
|
+
kataPathParts.indexOf("/data/system/.host-shims"),
|
|
154
|
+
).toBeGreaterThanOrEqual(0);
|
|
155
|
+
expect(kataPathParts.indexOf("/data/system/.host-shims")).toBeLessThan(
|
|
156
|
+
kataPathParts.indexOf("/data/system/bin"),
|
|
157
|
+
);
|
|
149
158
|
expect(env.LD_LIBRARY_PATH.split(":")).toContain("/data/system/usr/lib");
|
|
150
159
|
expect(env.LD_LIBRARY_PATH.split(":")).toContain(
|
|
151
160
|
"/data/system/usr/local/lib",
|
|
@@ -104,6 +104,7 @@ import * as slackBackfill from "../messaging/providers/slack/backfill.js";
|
|
|
104
104
|
import {
|
|
105
105
|
buildSlackTimezoneMetadata,
|
|
106
106
|
readSlackMetadata,
|
|
107
|
+
readSlackMetadataFromMessageMetadata,
|
|
107
108
|
writeSlackMetadata,
|
|
108
109
|
} from "../messaging/providers/slack/message-metadata.js";
|
|
109
110
|
import type { MessageRow } from "../persistence/conversation-crud.js";
|
|
@@ -360,12 +361,13 @@ function readPersistedSlackRows(conversationId: string): PersistedRow[] {
|
|
|
360
361
|
out.push(blank);
|
|
361
362
|
continue;
|
|
362
363
|
}
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
// Either envelope: a person's row still writes `slackMeta`, a
|
|
365
|
+
// bot-authored row the neutral envelope with Slack's fields riding it.
|
|
366
|
+
const slackMeta = readSlackMetadataFromMessageMetadata(row.metadata);
|
|
367
|
+
if (slackMeta === null) {
|
|
365
368
|
out.push(blank);
|
|
366
369
|
continue;
|
|
367
370
|
}
|
|
368
|
-
const slackMeta = readSlackMetadata(slackMetaRaw);
|
|
369
371
|
out.push({
|
|
370
372
|
role: row.role,
|
|
371
373
|
content: unwrapExternalContent(row.content),
|