@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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Repair the retired Fireworks MiniMax M2.7 model ID in workspace LLM
|
|
9
|
+
* config.
|
|
10
|
+
*
|
|
11
|
+
* Fireworks has no serverless deployment of
|
|
12
|
+
* `accounts/fireworks/models/minimax-m2p7`: the model page claims
|
|
13
|
+
* serverless support, but chat/completions calls fail with a 404 "Model
|
|
14
|
+
* not found, inaccessible, and/or not deployed". Existing configs can pin
|
|
15
|
+
* the ID in `llm.default`, `llm.callSites.*`, and `llm.profiles.*`.
|
|
16
|
+
*
|
|
17
|
+
* Repair those leaves only on an exact stale match, replacing with
|
|
18
|
+
* `accounts/fireworks/models/minimax-m3`: the only MiniMax model Fireworks
|
|
19
|
+
* serves serverless, at the same per-token pricing. Because m3 is also the
|
|
20
|
+
* balanced-intent template's model, a repaired hand-edited `custom-*`
|
|
21
|
+
* profile can read as unedited to `ensureByokDefaultProfiles` and collapse
|
|
22
|
+
* into the balanced default. That is acceptable: the collapsed copy routes
|
|
23
|
+
* to the same model this migration writes.
|
|
24
|
+
*
|
|
25
|
+
* Provider guard: the stale ID belongs to the `fireworks` provider and also
|
|
26
|
+
* appears in managed profiles stamped `provider: "vellum"` (which route
|
|
27
|
+
* Fireworks-account model IDs through the managed proxy). Under the entries
|
|
28
|
+
* model (migration 145) `provider` can also hold a `provider_connections`
|
|
29
|
+
* entry name whose row kind drives dispatch. A fragment is repaired when
|
|
30
|
+
* its `provider` is `"fireworks"`, `"vellum"`, absent, or an entry name
|
|
31
|
+
* whose row kind is one of those. Any other provider is left untouched: an
|
|
32
|
+
* `openai-compatible` endpoint may legitimately serve a model by the stale
|
|
33
|
+
* name.
|
|
34
|
+
*/
|
|
35
|
+
export const repairRetiredFireworksMinimaxM2p7ModelIdMigration: WorkspaceMigration =
|
|
36
|
+
{
|
|
37
|
+
id: "152-repair-retired-fireworks-minimax-m2p7-model-id",
|
|
38
|
+
description:
|
|
39
|
+
"Repair retired Fireworks accounts/fireworks/models/minimax-m2p7 model ID in workspace LLM config",
|
|
40
|
+
run(workspaceDir: string): void {
|
|
41
|
+
const configPath = join(workspaceDir, "config.json");
|
|
42
|
+
if (!existsSync(configPath)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Read outside the parse catch: a transient filesystem error (EIO,
|
|
47
|
+
// EACCES) must reach the runner so the migration retries, while
|
|
48
|
+
// malformed JSON is a permanent state this migration cannot repair.
|
|
49
|
+
const rawText = readFileSync(configPath, "utf-8");
|
|
50
|
+
|
|
51
|
+
let config: Record<string, unknown>;
|
|
52
|
+
try {
|
|
53
|
+
const raw = JSON.parse(rawText);
|
|
54
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
config = raw as Record<string, unknown>;
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const llm = readObject(config.llm);
|
|
63
|
+
if (llm === null) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Entry rows load lazily: only a stale fragment whose provider is
|
|
68
|
+
// neither a repairable vendor nor absent needs them. An unreadable DB
|
|
69
|
+
// then fails the run (retried next boot) rather than checkpointing a
|
|
70
|
+
// pass that skips entry-bound profiles.
|
|
71
|
+
let rows: Map<string, string> | null | undefined;
|
|
72
|
+
const isRepairableProvider = (provider: unknown): boolean => {
|
|
73
|
+
if (provider === undefined) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (typeof provider !== "string") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (REPAIRABLE_PROVIDERS.has(provider)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (rows === undefined) {
|
|
83
|
+
rows = readConnectionRows(workspaceDir);
|
|
84
|
+
}
|
|
85
|
+
if (rows === null) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"provider_connections is not readable; retrying the model-ID repair on the next run",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const kind = rows.get(provider);
|
|
91
|
+
return kind !== undefined && REPAIRABLE_PROVIDERS.has(kind);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let changed = false;
|
|
95
|
+
|
|
96
|
+
changed =
|
|
97
|
+
repairFragment(readObject(llm.default), isRepairableProvider) ||
|
|
98
|
+
changed;
|
|
99
|
+
|
|
100
|
+
const callSites = readObject(llm.callSites);
|
|
101
|
+
if (callSites !== null) {
|
|
102
|
+
for (const rawConfig of Object.values(callSites)) {
|
|
103
|
+
changed =
|
|
104
|
+
repairFragment(readObject(rawConfig), isRepairableProvider) ||
|
|
105
|
+
changed;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const profiles = readObject(llm.profiles);
|
|
110
|
+
if (profiles !== null) {
|
|
111
|
+
for (const rawProfile of Object.values(profiles)) {
|
|
112
|
+
changed =
|
|
113
|
+
repairFragment(readObject(rawProfile), isRepairableProvider) ||
|
|
114
|
+
changed;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!changed) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Write-then-rename so an interrupted write cannot leave config.json
|
|
123
|
+
// truncated: a torn in-place write would parse as invalid JSON on the
|
|
124
|
+
// retry, which the catch above treats as "nothing to do", letting the
|
|
125
|
+
// runner checkpoint the migration as completed against a corrupt file.
|
|
126
|
+
const tmpPath = `${configPath}.migration-152.tmp`;
|
|
127
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
128
|
+
renameSync(tmpPath, configPath);
|
|
129
|
+
},
|
|
130
|
+
// The exact-match rewrite is idempotent, so a transient failure (full
|
|
131
|
+
// disk, I/O error) is safe to retry on later startups.
|
|
132
|
+
retryFailedCheckpoint: true,
|
|
133
|
+
down(_workspaceDir: string): void {
|
|
134
|
+
// Forward-only: reintroducing the retired model ID would break
|
|
135
|
+
// Fireworks calls.
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Helpers: self-contained per workspace migrations AGENTS.md
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
const STALE_MODEL_ID = "accounts/fireworks/models/minimax-m2p7";
|
|
144
|
+
const REPLACEMENT_MODEL_ID = "accounts/fireworks/models/minimax-m3";
|
|
145
|
+
const REPAIRABLE_PROVIDERS = new Set(["fireworks", "vellum"]);
|
|
146
|
+
|
|
147
|
+
function repairFragment(
|
|
148
|
+
fragment: Record<string, unknown> | null,
|
|
149
|
+
isRepairableProvider: (provider: unknown) => boolean,
|
|
150
|
+
): boolean {
|
|
151
|
+
if (fragment === null) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (fragment.model !== STALE_MODEL_ID) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (!isRepairableProvider(fragment.provider)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
fragment.model = REPLACEMENT_MODEL_ID;
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Connection name -> provider kind, or null when the DB or table is not
|
|
166
|
+
* readable. The caller fails the run on null: entry-name providers must be
|
|
167
|
+
* judged against real rows, never guessed. An absent DB file is a real
|
|
168
|
+
* state (no rows, so every entry name is dangling and stays untouched).
|
|
169
|
+
*/
|
|
170
|
+
function readConnectionRows(workspaceDir: string): Map<string, string> | null {
|
|
171
|
+
const dbPath = join(workspaceDir, "data", "db", "assistant.db");
|
|
172
|
+
if (!existsSync(dbPath)) {
|
|
173
|
+
return new Map();
|
|
174
|
+
}
|
|
175
|
+
let db: Database;
|
|
176
|
+
try {
|
|
177
|
+
db = new Database(dbPath);
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const rows = db
|
|
183
|
+
.query(`SELECT name, provider FROM provider_connections`)
|
|
184
|
+
.all() as Array<{ name: string; provider: string }>;
|
|
185
|
+
return new Map(rows.map((r) => [r.name, r.provider]));
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
} finally {
|
|
189
|
+
db.close();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
194
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return value as Record<string, unknown>;
|
|
198
|
+
}
|
|
@@ -4,7 +4,6 @@ import { join } from "node:path";
|
|
|
4
4
|
import { describe, expect, test } from "bun:test";
|
|
5
5
|
|
|
6
6
|
import { sttFluxProviderToModelFamilyMigration } from "../150-stt-flux-provider-to-model-family.js";
|
|
7
|
-
import { WORKSPACE_MIGRATIONS } from "../registry.js";
|
|
8
7
|
|
|
9
8
|
function workspaceWith(config: unknown): string {
|
|
10
9
|
const dir = mkdtempSync(join(tmpdir(), "stt-flux-migration-"));
|
|
@@ -17,15 +16,6 @@ function readConfig(dir: string): any {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
describe("150-stt-flux-provider-to-model-family", () => {
|
|
20
|
-
test("is registered as the newest migration", () => {
|
|
21
|
-
expect(sttFluxProviderToModelFamilyMigration.id).toBe(
|
|
22
|
-
"150-stt-flux-provider-to-model-family",
|
|
23
|
-
);
|
|
24
|
-
expect(WORKSPACE_MIGRATIONS.at(-1)?.id).toBe(
|
|
25
|
-
"150-stt-flux-provider-to-model-family",
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
19
|
test("rewrites a Flux provider id to its parent plus a model family", () => {
|
|
30
20
|
const dir = workspaceWith({
|
|
31
21
|
services: { stt: { provider: "deepgram-flux", providers: {} } },
|
|
@@ -148,6 +148,8 @@ import { renameCollidingBackupProfileNamesMigration } from "./147-rename-collidi
|
|
|
148
148
|
import { stripUnsupportedFallbackProfilesMigration } from "./148-strip-unsupported-fallback-profiles.js";
|
|
149
149
|
import { repointBackupProfileSelectionsMigration } from "./149-repoint-backup-profile-selections.js";
|
|
150
150
|
import { sttFluxProviderToModelFamilyMigration } from "./150-stt-flux-provider-to-model-family.js";
|
|
151
|
+
import { repairRenamedFireworksDeepseekProModelIdMigration } from "./151-repair-renamed-fireworks-deepseek-pro-model-id.js";
|
|
152
|
+
import { repairRetiredFireworksMinimaxM2p7ModelIdMigration } from "./152-repair-retired-fireworks-minimax-m2p7-model-id.js";
|
|
151
153
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
152
154
|
import type { WorkspaceMigration } from "./types.js";
|
|
153
155
|
|
|
@@ -311,4 +313,6 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
311
313
|
stripUnsupportedFallbackProfilesMigration,
|
|
312
314
|
repointBackupProfileSelectionsMigration,
|
|
313
315
|
sttFluxProviderToModelFamilyMigration,
|
|
316
|
+
repairRenamedFireworksDeepseekProModelIdMigration,
|
|
317
|
+
repairRetiredFireworksMinimaxM2p7ModelIdMigration,
|
|
314
318
|
];
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env sh
|
|
2
|
-
# Runs pip inside the persistent apt chroot. Must be invoked under `unshare -m`
|
|
3
|
-
# (see docker-kata-pip.sh): the bind mounts below live in a private mount
|
|
4
|
-
# namespace so they disappear with the pip process and are never visible to
|
|
5
|
-
# other processes — a lingering /data bind inside the chroot would create a
|
|
6
|
-
# path cycle (/data/system/data/system/...) for anything walking /data.
|
|
7
|
-
set -eu
|
|
8
|
-
|
|
9
|
-
DATA_ROOT="$1"
|
|
10
|
-
CALLER_CWD="$2"
|
|
11
|
-
PIP_BIN="$3"
|
|
12
|
-
shift 3
|
|
13
|
-
|
|
14
|
-
for dir in /workspace /data /tmp /var/tmp; do
|
|
15
|
-
[ -d "${dir}" ] || continue
|
|
16
|
-
mkdir -p "${DATA_ROOT}${dir}"
|
|
17
|
-
mount --bind "${dir}" "${DATA_ROOT}${dir}" 2>/dev/null || true
|
|
18
|
-
done
|
|
19
|
-
|
|
20
|
-
# chroot(1) always chdirs to /; restore the caller's cwd when it exists inside
|
|
21
|
-
# the chroot so relative paths keep working.
|
|
22
|
-
exec chroot "${DATA_ROOT}" /bin/sh -c 'cd "$1" 2>/dev/null || cd /; shift; exec "$@"' sh "${CALLER_CWD}" "${PIP_BIN}" "$@"
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { reactionDecisionForEmoji } from "../runtime/routes/channel-route-shared.js";
|
|
4
|
-
|
|
5
|
-
// =============================================================================
|
|
6
|
-
// reactionDecisionForEmoji
|
|
7
|
-
// =============================================================================
|
|
8
|
-
|
|
9
|
-
describe("reactionDecisionForEmoji", () => {
|
|
10
|
-
test("maps +1 emoji to approve_once", () => {
|
|
11
|
-
const result = reactionDecisionForEmoji("+1");
|
|
12
|
-
expect(result).toEqual({
|
|
13
|
-
action: "approve_once",
|
|
14
|
-
source: "reaction",
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("maps thumbsup emoji to approve_once", () => {
|
|
19
|
-
const result = reactionDecisionForEmoji("thumbsup");
|
|
20
|
-
expect(result).toEqual({
|
|
21
|
-
action: "approve_once",
|
|
22
|
-
source: "reaction",
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("maps -1 emoji to reject", () => {
|
|
27
|
-
const result = reactionDecisionForEmoji("-1");
|
|
28
|
-
expect(result).toEqual({
|
|
29
|
-
action: "reject",
|
|
30
|
-
source: "reaction",
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("maps thumbsdown emoji to reject", () => {
|
|
35
|
-
const result = reactionDecisionForEmoji("thumbsdown");
|
|
36
|
-
expect(result).toEqual({
|
|
37
|
-
action: "reject",
|
|
38
|
-
source: "reaction",
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("alarm_clock emoji maps to approve_once (legacy compat)", () => {
|
|
43
|
-
const result = reactionDecisionForEmoji("alarm_clock");
|
|
44
|
-
expect(result).toEqual({
|
|
45
|
-
action: "approve_once",
|
|
46
|
-
source: "reaction",
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("white_check_mark emoji maps to approve_once (legacy compat)", () => {
|
|
51
|
-
const result = reactionDecisionForEmoji("white_check_mark");
|
|
52
|
-
expect(result).toEqual({
|
|
53
|
-
action: "approve_once",
|
|
54
|
-
source: "reaction",
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("unicode vocabulary: thumbs up, check mark, and alarm clock approve", () => {
|
|
59
|
-
for (const emoji of ["\u{1F44D}", "\u2705", "\u23F0"]) {
|
|
60
|
-
expect(reactionDecisionForEmoji(emoji)).toEqual({
|
|
61
|
-
action: "approve_once",
|
|
62
|
-
source: "reaction",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("unicode vocabulary: thumbs down rejects", () => {
|
|
68
|
-
expect(reactionDecisionForEmoji("\u{1F44E}")).toEqual({
|
|
69
|
-
action: "reject",
|
|
70
|
-
source: "reaction",
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("a Discord custom emoji in mention form never resolves a decision", () => {
|
|
75
|
-
// A guild custom emoji can be named after a Slack decision word; the
|
|
76
|
-
// gateway forwards it as its mention form, which must stay unmapped.
|
|
77
|
-
expect(
|
|
78
|
-
reactionDecisionForEmoji("<:white_check_mark:999888777>"),
|
|
79
|
-
).toBeNull();
|
|
80
|
-
expect(reactionDecisionForEmoji("<:thumbsup:999888777>")).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("returns null for unknown emoji", () => {
|
|
84
|
-
const result = reactionDecisionForEmoji("tada");
|
|
85
|
-
expect(result).toBeNull();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("returns null for an unmapped emoji", () => {
|
|
89
|
-
const result = reactionDecisionForEmoji("eyes");
|
|
90
|
-
expect(result).toBeNull();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("returns null for an empty emoji name", () => {
|
|
94
|
-
const result = reactionDecisionForEmoji("");
|
|
95
|
-
expect(result).toBeNull();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for guardian approval-by-reaction on the guardian decision pipeline.
|
|
3
|
-
*
|
|
4
|
-
* A Slack emoji reaction (✅ / ❌) on a delivered approval card is routed
|
|
5
|
-
* through `routeGuardianReply` exactly like a button press or text reply. The
|
|
6
|
-
* emoji maps to an action and the reacted card's message id (`reactedMessageTs`)
|
|
7
|
-
* resolves the target request via its canonical delivery record — so the
|
|
8
|
-
* decision is applied precisely even when several cards are pending in the same
|
|
9
|
-
* chat, with no clarification prompt.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
-
|
|
14
|
-
const _conversationMocks = new Map<string, unknown>();
|
|
15
|
-
mock.module("../daemon/conversation-registry.js", () => ({
|
|
16
|
-
findConversation: (id: string) => _conversationMocks.get(id),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
import { createGuardianGatewaySim } from "./guardian-gateway-sim.js";
|
|
20
|
-
|
|
21
|
-
const sim = createGuardianGatewaySim();
|
|
22
|
-
mock.module("../channels/gateway-guardian-requests.js", () => sim.module);
|
|
23
|
-
|
|
24
|
-
import type { Conversation } from "../daemon/conversation.js";
|
|
25
|
-
import { initializeDb } from "../persistence/db-init.js";
|
|
26
|
-
import {
|
|
27
|
-
inferDecisionActionFromFreeText,
|
|
28
|
-
routeGuardianReply,
|
|
29
|
-
} from "../runtime/guardian-reply-router.js";
|
|
30
|
-
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
31
|
-
|
|
32
|
-
await initializeDb();
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Constants & helpers
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
const GUARDIAN_USER = "U_GUARDIAN";
|
|
39
|
-
const PRINCIPAL = "principal-1";
|
|
40
|
-
const CHAT_ID = "D_GUARDIAN_DM";
|
|
41
|
-
const TOOL_NAME = "execute_shell";
|
|
42
|
-
|
|
43
|
-
function resetTables(): void {
|
|
44
|
-
sim.reset();
|
|
45
|
-
pendingInteractions.clear();
|
|
46
|
-
_conversationMocks.clear();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Seed a pending tool-approval (gateway request + delivery + pending
|
|
51
|
-
* interaction with a mock conversation) and return the mocked
|
|
52
|
-
* `handleConfirmationResponse` so callers can assert it was driven.
|
|
53
|
-
*/
|
|
54
|
-
function seedApproval(opts: {
|
|
55
|
-
requestId: string;
|
|
56
|
-
ts: string;
|
|
57
|
-
chatId?: string;
|
|
58
|
-
principal?: string;
|
|
59
|
-
}): ReturnType<typeof mock> {
|
|
60
|
-
const chatId = opts.chatId ?? CHAT_ID;
|
|
61
|
-
const principal = opts.principal ?? PRINCIPAL;
|
|
62
|
-
const conversationId = `conv-${opts.requestId}`;
|
|
63
|
-
|
|
64
|
-
sim.seedRequest({
|
|
65
|
-
id: opts.requestId,
|
|
66
|
-
kind: "tool_approval",
|
|
67
|
-
sourceChannel: "slack",
|
|
68
|
-
sourceConversationId: conversationId,
|
|
69
|
-
requesterExternalUserId: "requester-1",
|
|
70
|
-
requesterChatId: chatId,
|
|
71
|
-
guardianExternalUserId: GUARDIAN_USER,
|
|
72
|
-
guardianPrincipalId: principal,
|
|
73
|
-
toolName: TOOL_NAME,
|
|
74
|
-
status: "pending",
|
|
75
|
-
expiresAt: Date.now() + 300_000,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
sim.seedDelivery({
|
|
79
|
-
requestId: opts.requestId,
|
|
80
|
-
destinationChannel: "slack",
|
|
81
|
-
destinationChatId: chatId,
|
|
82
|
-
destinationMessageId: opts.ts,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const handleConfirmationResponse = mock(() => {});
|
|
86
|
-
_conversationMocks.set(conversationId, {
|
|
87
|
-
handleConfirmationResponse,
|
|
88
|
-
ensureActorScopedHistory: async () => {},
|
|
89
|
-
} as unknown as Conversation);
|
|
90
|
-
pendingInteractions.register(opts.requestId, {
|
|
91
|
-
conversationId,
|
|
92
|
-
kind: "confirmation",
|
|
93
|
-
confirmationDetails: {
|
|
94
|
-
toolName: TOOL_NAME,
|
|
95
|
-
input: { command: "echo hi" },
|
|
96
|
-
riskLevel: "high",
|
|
97
|
-
allowlistOptions: [{ label: "t", description: "t", pattern: "t" }],
|
|
98
|
-
scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
return handleConfirmationResponse;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function reaction(opts: {
|
|
105
|
-
emoji: string;
|
|
106
|
-
reactedMessageTs?: string;
|
|
107
|
-
chatId?: string;
|
|
108
|
-
principal?: string;
|
|
109
|
-
}): Parameters<typeof routeGuardianReply>[0] {
|
|
110
|
-
return {
|
|
111
|
-
messageText: "",
|
|
112
|
-
channel: "slack",
|
|
113
|
-
actor: {
|
|
114
|
-
actorPrincipalId: opts.principal ?? PRINCIPAL,
|
|
115
|
-
actorExternalUserId: GUARDIAN_USER,
|
|
116
|
-
channel: "slack",
|
|
117
|
-
guardianPrincipalId: opts.principal ?? PRINCIPAL,
|
|
118
|
-
},
|
|
119
|
-
conversationId: "guardian-conv",
|
|
120
|
-
reaction: {
|
|
121
|
-
op: "added" as const,
|
|
122
|
-
emoji: opts.emoji,
|
|
123
|
-
targetMessageId: opts.reactedMessageTs ?? "",
|
|
124
|
-
},
|
|
125
|
-
reactedMessageTs: opts.reactedMessageTs,
|
|
126
|
-
channelDeliveryContext: {
|
|
127
|
-
replyCallbackUrl: "https://gateway.test/deliver",
|
|
128
|
-
guardianChatId: opts.chatId ?? CHAT_ID,
|
|
129
|
-
assistantId: "self",
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
// Reaction routing
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
describe("routeGuardianReply / reactions", () => {
|
|
139
|
-
beforeEach(resetTables);
|
|
140
|
-
|
|
141
|
-
test("✅ on a delivered card approves the matching request", async () => {
|
|
142
|
-
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
143
|
-
|
|
144
|
-
const result = await routeGuardianReply(
|
|
145
|
-
reaction({ emoji: "white_check_mark", reactedMessageTs: "111.1" }),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
expect(result.consumed).toBe(true);
|
|
149
|
-
expect(result.decisionApplied).toBe(true);
|
|
150
|
-
expect(result.type).toBe("canonical_decision_applied");
|
|
151
|
-
expect(sim.getRequest("req-1")?.status).toBe("approved");
|
|
152
|
-
expect(hcr).toHaveBeenCalledTimes(1);
|
|
153
|
-
expect(hcr.mock.calls[0]?.[0]).toBe("req-1");
|
|
154
|
-
expect(hcr.mock.calls[0]?.[1]).toBe("allow");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("❌ on a delivered card rejects the matching request", async () => {
|
|
158
|
-
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
159
|
-
|
|
160
|
-
const result = await routeGuardianReply(
|
|
161
|
-
reaction({ emoji: "-1", reactedMessageTs: "111.1" }),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
expect(result.decisionApplied).toBe(true);
|
|
165
|
-
expect(sim.getRequest("req-1")?.status).toBe("denied");
|
|
166
|
-
expect(hcr.mock.calls[0]?.[1]).toBe("deny");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("a person-addressed delivery resolves through the reactor's own id", async () => {
|
|
170
|
-
// Person-addressed adapters (Discord) record the guardian's user id as
|
|
171
|
-
// the destination, while the reaction arrives addressed by its chat, so
|
|
172
|
-
// the chat probe misses and the actor-id probe must find the card.
|
|
173
|
-
const hcr = seedApproval({
|
|
174
|
-
requestId: "req-person",
|
|
175
|
-
ts: "300.3",
|
|
176
|
-
chatId: GUARDIAN_USER,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const result = await routeGuardianReply(
|
|
180
|
-
reaction({
|
|
181
|
-
emoji: "white_check_mark",
|
|
182
|
-
reactedMessageTs: "300.3",
|
|
183
|
-
chatId: "DM_CHANNEL_9",
|
|
184
|
-
}),
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
expect(result.consumed).toBe(true);
|
|
188
|
-
expect(result.decisionApplied).toBe(true);
|
|
189
|
-
expect(sim.getRequest("req-person")?.status).toBe("approved");
|
|
190
|
-
expect(hcr).toHaveBeenCalledTimes(1);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("reacting on a specific card resolves only that card (disambiguates N>1)", async () => {
|
|
194
|
-
const hcrA = seedApproval({ requestId: "req-A", ts: "100.1" });
|
|
195
|
-
const hcrB = seedApproval({ requestId: "req-B", ts: "200.2" });
|
|
196
|
-
|
|
197
|
-
// React on card B's message — only B should resolve.
|
|
198
|
-
const result = await routeGuardianReply(
|
|
199
|
-
reaction({ emoji: "white_check_mark", reactedMessageTs: "200.2" }),
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
expect(result.requestId).toBe("req-B");
|
|
203
|
-
expect(sim.getRequest("req-B")?.status).toBe("approved");
|
|
204
|
-
expect(sim.getRequest("req-A")?.status).toBe("pending");
|
|
205
|
-
expect(hcrB).toHaveBeenCalledTimes(1);
|
|
206
|
-
expect(hcrA).not.toHaveBeenCalled();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("a reaction on an unknown message is not consumed (left for transcript)", async () => {
|
|
210
|
-
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
211
|
-
|
|
212
|
-
const result = await routeGuardianReply(
|
|
213
|
-
reaction({ emoji: "white_check_mark", reactedMessageTs: "no-such-ts" }),
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
expect(result.consumed).toBe(false);
|
|
217
|
-
expect(result.type).toBe("not_consumed");
|
|
218
|
-
expect(sim.getRequest("req-1")?.status).toBe("pending");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test("an unknown emoji is not consumed", async () => {
|
|
222
|
-
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
223
|
-
|
|
224
|
-
const result = await routeGuardianReply(
|
|
225
|
-
reaction({ emoji: "tada", reactedMessageTs: "111.1" }),
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
expect(result.consumed).toBe(false);
|
|
229
|
-
expect(sim.getRequest("req-1")?.status).toBe("pending");
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("a reaction without a reacted message id is not consumed", async () => {
|
|
233
|
-
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
234
|
-
|
|
235
|
-
const result = await routeGuardianReply(
|
|
236
|
-
reaction({ emoji: "white_check_mark" }),
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
expect(result.consumed).toBe(false);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test("a reaction from a different principal is surfaced, not silently dropped", async () => {
|
|
243
|
-
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
244
|
-
|
|
245
|
-
const result = await routeGuardianReply(
|
|
246
|
-
reaction({
|
|
247
|
-
emoji: "white_check_mark",
|
|
248
|
-
reactedMessageTs: "111.1",
|
|
249
|
-
principal: "someone-else",
|
|
250
|
-
}),
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
// Consumed with a user-facing reply, request untouched, side effect not run.
|
|
254
|
-
expect(result.consumed).toBe(true);
|
|
255
|
-
expect(result.decisionApplied).toBe(false);
|
|
256
|
-
expect(result.replyText).toMatch(/permission/i);
|
|
257
|
-
expect(sim.getRequest("req-1")?.status).toBe("pending");
|
|
258
|
-
expect(hcr).not.toHaveBeenCalled();
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// ---------------------------------------------------------------------------
|
|
263
|
-
// Free-text decision inference (no request code)
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
|
-
|
|
266
|
-
describe("inferDecisionActionFromFreeText / introduction verbs", () => {
|
|
267
|
-
// Regression: a bare "block" on an access request must map to the block
|
|
268
|
-
// action (revoke), not the weaker generic reject (leave_unverified).
|
|
269
|
-
test('"block" maps to block for access requests, reject elsewhere', () => {
|
|
270
|
-
expect(inferDecisionActionFromFreeText("block", "access_request")).toBe(
|
|
271
|
-
"block",
|
|
272
|
-
);
|
|
273
|
-
expect(inferDecisionActionFromFreeText("Block!", "access_request")).toBe(
|
|
274
|
-
"block",
|
|
275
|
-
);
|
|
276
|
-
expect(inferDecisionActionFromFreeText("block")).toBe("reject");
|
|
277
|
-
expect(inferDecisionActionFromFreeText("block", "tool_approval")).toBe(
|
|
278
|
-
"reject",
|
|
279
|
-
);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test("trust and verify verbs are recognized for access requests", () => {
|
|
283
|
-
expect(inferDecisionActionFromFreeText("trust", "access_request")).toBe(
|
|
284
|
-
"trust",
|
|
285
|
-
);
|
|
286
|
-
expect(inferDecisionActionFromFreeText("verify", "access_request")).toBe(
|
|
287
|
-
"verify_code",
|
|
288
|
-
);
|
|
289
|
-
// Unknown outside access requests — falls through to null (no cue).
|
|
290
|
-
expect(inferDecisionActionFromFreeText("trust")).toBeNull();
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test("generic vocabulary keeps its semantics per kind", () => {
|
|
294
|
-
expect(inferDecisionActionFromFreeText("no", "access_request")).toBe(
|
|
295
|
-
"leave_unverified",
|
|
296
|
-
);
|
|
297
|
-
expect(inferDecisionActionFromFreeText("reject", "access_request")).toBe(
|
|
298
|
-
"leave_unverified",
|
|
299
|
-
);
|
|
300
|
-
expect(inferDecisionActionFromFreeText("yes", "access_request")).toBe(
|
|
301
|
-
"approve_once",
|
|
302
|
-
);
|
|
303
|
-
expect(inferDecisionActionFromFreeText("no", "tool_approval")).toBe(
|
|
304
|
-
"reject",
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
});
|