@vellumai/assistant 0.10.3-staging.2 → 0.10.4-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -69,7 +69,6 @@ mock.module("../daemon/approval-generators.js", () => ({
|
|
|
69
69
|
createApprovalConversationGenerator: () => _testApprovalConversationGenerator,
|
|
70
70
|
}));
|
|
71
71
|
|
|
72
|
-
import { upsertContact } from "../contacts/contact-store.js";
|
|
73
72
|
import type { Conversation } from "../daemon/conversation.js";
|
|
74
73
|
import {
|
|
75
74
|
createCanonicalGuardianDelivery,
|
|
@@ -86,7 +85,10 @@ import * as gatewayClient from "../runtime/gateway-client.js";
|
|
|
86
85
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
87
86
|
import { _setTestPollMaxWait } from "../runtime/routes/channel-route-shared.js";
|
|
88
87
|
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
89
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
handleChannelInbound,
|
|
90
|
+
seedContactChannel,
|
|
91
|
+
} from "./helpers/channel-test-adapter.js";
|
|
90
92
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
91
93
|
|
|
92
94
|
await initializeDb();
|
|
@@ -212,22 +214,19 @@ function makeInboundRequest(overrides: Record<string, unknown> = {}): Request {
|
|
|
212
214
|
const noopProcessMessage = mock(async () => ({ messageId: "msg-1" }));
|
|
213
215
|
|
|
214
216
|
function ensureTestContact(): void {
|
|
215
|
-
|
|
217
|
+
seedContactChannel({
|
|
218
|
+
sourceChannel: "telegram",
|
|
219
|
+
externalUserId: "telegram-user-default",
|
|
216
220
|
displayName: "Test User",
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
address: "slack-user-default",
|
|
227
|
-
status: "active",
|
|
228
|
-
policy: "allow",
|
|
229
|
-
},
|
|
230
|
-
],
|
|
221
|
+
status: "active",
|
|
222
|
+
policy: "allow",
|
|
223
|
+
});
|
|
224
|
+
seedContactChannel({
|
|
225
|
+
sourceChannel: "slack",
|
|
226
|
+
externalUserId: "slack-user-default",
|
|
227
|
+
displayName: "Test User",
|
|
228
|
+
status: "active",
|
|
229
|
+
policy: "allow",
|
|
231
230
|
});
|
|
232
231
|
}
|
|
233
232
|
|
|
@@ -1926,16 +1925,12 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
|
|
|
1926
1925
|
guardianDeliveryChatId: "guardian-tc-selfapproval-chat",
|
|
1927
1926
|
guardianPrincipalId: "guardian-tc-selfapproval",
|
|
1928
1927
|
});
|
|
1929
|
-
|
|
1928
|
+
seedContactChannel({
|
|
1929
|
+
sourceChannel: "telegram",
|
|
1930
|
+
externalUserId: "tc-selfapproval-user",
|
|
1930
1931
|
displayName: "TC Self-Approval User",
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
type: "telegram",
|
|
1934
|
-
address: "tc-selfapproval-user",
|
|
1935
|
-
status: "active",
|
|
1936
|
-
policy: "allow",
|
|
1937
|
-
},
|
|
1938
|
-
],
|
|
1932
|
+
status: "active",
|
|
1933
|
+
policy: "allow",
|
|
1939
1934
|
});
|
|
1940
1935
|
});
|
|
1941
1936
|
|
|
@@ -493,6 +493,34 @@ describe("channel-delivery-store", () => {
|
|
|
493
493
|
});
|
|
494
494
|
});
|
|
495
495
|
|
|
496
|
+
test("conversation detail omits Slack metadata for non-Slack channels", () => {
|
|
497
|
+
const result = recordInbound("telegram", "tg-chat-1", "msg-1", {
|
|
498
|
+
sourceThreadId: "9001",
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
upsertBinding({
|
|
502
|
+
conversationId: result.conversationId,
|
|
503
|
+
sourceChannel: "telegram",
|
|
504
|
+
externalChatId: "tg-chat-1",
|
|
505
|
+
externalChatName: "Family",
|
|
506
|
+
externalThreadId: "9001",
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const detail = buildConversationDetailResponse(result.conversationId);
|
|
510
|
+
const binding = detail?.conversation.channelBinding;
|
|
511
|
+
|
|
512
|
+
// The channel-neutral fields pass through for any source channel...
|
|
513
|
+
expect(binding).toMatchObject({
|
|
514
|
+
sourceChannel: "telegram",
|
|
515
|
+
externalChatId: "tg-chat-1",
|
|
516
|
+
externalChatName: "Family",
|
|
517
|
+
externalThreadId: "9001",
|
|
518
|
+
});
|
|
519
|
+
// ...but Slack-only deep-link metadata is not synthesized.
|
|
520
|
+
expect(binding).not.toHaveProperty("slackThread");
|
|
521
|
+
expect(binding).not.toHaveProperty("slackChannel");
|
|
522
|
+
});
|
|
523
|
+
|
|
496
524
|
test("binding upsert preserves existing chat name when incoming name is missing", () => {
|
|
497
525
|
const result = recordInbound("slack", "C0123ABCDEF", "msg-1", {
|
|
498
526
|
sourceThreadId: "1710000000.000100",
|
|
@@ -65,45 +65,77 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
65
65
|
|
|
66
66
|
// Gateway relay mock — the revoke path relays the ACL downgrade over IPC and
|
|
67
67
|
// validates the response; return a well-formed mark_channel_revoked result.
|
|
68
|
+
// The gateway owns the revoke and dual-writes the local assistant row to
|
|
69
|
+
// "revoked"; mirror that dual-write here so guardian-resolution reads under
|
|
70
|
+
// test observe the downgrade (the assistant-side teardown is now a no-op shim).
|
|
68
71
|
mock.module("../ipc/gateway-client.js", () => ({
|
|
69
72
|
ipcCallPersistent: async (
|
|
70
|
-
|
|
73
|
+
method: string,
|
|
71
74
|
params?: Record<string, unknown>,
|
|
72
|
-
) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
) => {
|
|
76
|
+
if (method === "mark_channel_revoked") {
|
|
77
|
+
const { getDb } = await import("../memory/db-connection.js");
|
|
78
|
+
const { contactChannels } = await import("../memory/schema.js");
|
|
79
|
+
const { eq } = await import("drizzle-orm");
|
|
80
|
+
const channelId = params?.contactChannelId as string | undefined;
|
|
81
|
+
if (channelId) {
|
|
82
|
+
getDb()
|
|
83
|
+
.update(contactChannels)
|
|
84
|
+
.set({ status: "revoked" })
|
|
85
|
+
.where(eq(contactChannels.id, channelId))
|
|
86
|
+
.run();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
didWrite: true,
|
|
92
|
+
channel: {
|
|
93
|
+
id: (params?.contactChannelId as string) ?? "ch1",
|
|
94
|
+
contactId: "c1",
|
|
95
|
+
type: "phone",
|
|
96
|
+
address: "addr",
|
|
97
|
+
status: "revoked",
|
|
98
|
+
revokedReason: (params?.reason as string) ?? null,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
84
102
|
}));
|
|
85
103
|
|
|
86
104
|
// Guardian-delivery reader mock — the inbound challenge guard reads guardian
|
|
87
105
|
// existence from the gateway. Derive the list from the local binding state so
|
|
88
106
|
// the gateway-backed presence guard mirrors the DB the rest of the test sets up.
|
|
89
107
|
const resolveGuardianList = async (input?: { channelTypes?: string[] }) => {
|
|
90
|
-
const {
|
|
91
|
-
|
|
92
|
-
);
|
|
108
|
+
const { getDb } = await import("../memory/db-connection.js");
|
|
109
|
+
const { contacts, contactChannels } = await import("../memory/schema.js");
|
|
110
|
+
const { and, eq } = await import("drizzle-orm");
|
|
93
111
|
const channels = input?.channelTypes ?? [];
|
|
94
112
|
return channels
|
|
95
113
|
.map((channelType) => {
|
|
96
|
-
const
|
|
97
|
-
|
|
114
|
+
const row = getDb()
|
|
115
|
+
.select({ contact: contacts, channel: contactChannels })
|
|
116
|
+
.from(contacts)
|
|
117
|
+
.innerJoin(
|
|
118
|
+
contactChannels,
|
|
119
|
+
eq(contacts.id, contactChannels.contactId),
|
|
120
|
+
)
|
|
121
|
+
.where(
|
|
122
|
+
and(
|
|
123
|
+
eq(contacts.role, "guardian"),
|
|
124
|
+
eq(contactChannels.type, channelType),
|
|
125
|
+
eq(contactChannels.status, "active"),
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
.get();
|
|
129
|
+
if (!row) return null;
|
|
98
130
|
return {
|
|
99
131
|
channelType,
|
|
100
|
-
contactId:
|
|
101
|
-
principalId:
|
|
102
|
-
displayName:
|
|
103
|
-
address:
|
|
104
|
-
externalChatId:
|
|
132
|
+
contactId: row.contact.id,
|
|
133
|
+
principalId: row.contact.principalId ?? null,
|
|
134
|
+
displayName: row.contact.displayName ?? null,
|
|
135
|
+
address: row.channel.address,
|
|
136
|
+
externalChatId: row.channel.externalChatId ?? null,
|
|
105
137
|
status: "active",
|
|
106
|
-
verifiedAt:
|
|
138
|
+
verifiedAt: row.channel.verifiedAt ?? null,
|
|
107
139
|
};
|
|
108
140
|
})
|
|
109
141
|
.filter((g) => g !== null);
|
|
@@ -147,6 +179,7 @@ import {
|
|
|
147
179
|
} from "../memory/guardian-rate-limits.js";
|
|
148
180
|
import {
|
|
149
181
|
channelVerificationSessions,
|
|
182
|
+
contactChannels,
|
|
150
183
|
conversations,
|
|
151
184
|
} from "../memory/schema.js";
|
|
152
185
|
import {
|
|
@@ -192,6 +225,19 @@ function resetTables(): void {
|
|
|
192
225
|
mockBotUsername = "test_bot";
|
|
193
226
|
}
|
|
194
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Revoke a guardian channel's local ACL state directly. The production revoke
|
|
230
|
+
* is gateway-owned (relayed via mark_channel_revoked); this stamps the local
|
|
231
|
+
* mirror so the guardian-resolution reads still under test see the downgrade.
|
|
232
|
+
*/
|
|
233
|
+
function revokeGuardianChannelLocally(channelType: string): void {
|
|
234
|
+
getDb()
|
|
235
|
+
.update(contactChannels)
|
|
236
|
+
.set({ status: "revoked" })
|
|
237
|
+
.where(eq(contactChannels.type, channelType))
|
|
238
|
+
.run();
|
|
239
|
+
}
|
|
240
|
+
|
|
195
241
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
196
242
|
// 2. Verification Challenge Lifecycle (Store)
|
|
197
243
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -558,7 +604,7 @@ describe("guardian identity check", () => {
|
|
|
558
604
|
guardianDeliveryChatId: "chat-42",
|
|
559
605
|
});
|
|
560
606
|
|
|
561
|
-
|
|
607
|
+
revokeGuardianChannelLocally("telegram");
|
|
562
608
|
|
|
563
609
|
expect(await isGuardian("asst-1", "telegram", "user-42")).toBe(false);
|
|
564
610
|
});
|
|
@@ -595,7 +641,7 @@ describe("guardian identity check", () => {
|
|
|
595
641
|
expect(await isGuardian("asst-1", "telegram", "phone-user-1")).toBe(false);
|
|
596
642
|
});
|
|
597
643
|
|
|
598
|
-
test("
|
|
644
|
+
test("guardian binding read reflects a gateway-owned revoke", async () => {
|
|
599
645
|
createGuardianBinding({
|
|
600
646
|
channel: "telegram",
|
|
601
647
|
guardianExternalUserId: "user-42",
|
|
@@ -603,8 +649,10 @@ describe("guardian identity check", () => {
|
|
|
603
649
|
guardianDeliveryChatId: "chat-42",
|
|
604
650
|
});
|
|
605
651
|
|
|
606
|
-
|
|
607
|
-
|
|
652
|
+
// The revoke is gateway-owned; serviceRevokeBinding's local teardown is a
|
|
653
|
+
// no-op shim. Stamp the local downgrade and assert the read reflects it.
|
|
654
|
+
serviceRevokeBinding("asst-1", "telegram");
|
|
655
|
+
revokeGuardianChannelLocally("telegram");
|
|
608
656
|
expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
|
|
609
657
|
});
|
|
610
658
|
});
|
|
@@ -958,7 +1006,7 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
958
1006
|
guardianDeliveryChatId: "chat-beta",
|
|
959
1007
|
});
|
|
960
1008
|
|
|
961
|
-
|
|
1009
|
+
revokeGuardianChannelLocally("telegram");
|
|
962
1010
|
|
|
963
1011
|
expect(await getGuardianBinding("self", "telegram")).toBeNull();
|
|
964
1012
|
expect(await getGuardianBinding("self", "phone")).not.toBeNull();
|
|
@@ -1472,8 +1520,10 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1472
1520
|
guardianDeliveryChatId: "voice-chat-1",
|
|
1473
1521
|
});
|
|
1474
1522
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1523
|
+
// The revoke is gateway-owned; serviceRevokeBinding's local teardown is a
|
|
1524
|
+
// no-op shim. Stamp the local downgrade and assert the read reflects it.
|
|
1525
|
+
serviceRevokeBinding("asst-1", "phone");
|
|
1526
|
+
revokeGuardianChannelLocally("phone");
|
|
1477
1527
|
expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
|
|
1478
1528
|
});
|
|
1479
1529
|
|
|
@@ -1491,7 +1541,7 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1491
1541
|
guardianDeliveryChatId: "tg-chat-1",
|
|
1492
1542
|
});
|
|
1493
1543
|
|
|
1494
|
-
|
|
1544
|
+
revokeGuardianChannelLocally("phone");
|
|
1495
1545
|
|
|
1496
1546
|
expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
|
|
1497
1547
|
expect(await getGuardianBinding("asst-1", "telegram")).not.toBeNull();
|
|
@@ -59,7 +59,6 @@ mock.module("../daemon/disk-pressure-guard.js", () => ({
|
|
|
59
59
|
diskPressureStatusSequence?.shift() ?? diskPressureStatus,
|
|
60
60
|
}));
|
|
61
61
|
|
|
62
|
-
import { upsertContact } from "../contacts/contact-store.js";
|
|
63
62
|
import { getDb } from "../memory/db-connection.js";
|
|
64
63
|
import { initializeDb } from "../memory/db-init.js";
|
|
65
64
|
import * as deliveryCrud from "../memory/delivery-crud.js";
|
|
@@ -71,6 +70,7 @@ import {
|
|
|
71
70
|
import { sweepFailedEvents } from "../runtime/channel-retry-sweep.js";
|
|
72
71
|
import {
|
|
73
72
|
handleChannelInbound,
|
|
73
|
+
seedContactChannel,
|
|
74
74
|
setAdapterProcessMessage,
|
|
75
75
|
} from "./helpers/channel-test-adapter.js";
|
|
76
76
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
@@ -90,16 +90,12 @@ function resetTables(): void {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function seedTrustedContact(policy: "allow" | "escalate" = "allow"): void {
|
|
93
|
-
|
|
93
|
+
seedContactChannel({
|
|
94
|
+
sourceChannel: "telegram",
|
|
95
|
+
externalUserId: "telegram-user-1",
|
|
94
96
|
displayName: "Example User",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
type: "telegram",
|
|
98
|
-
address: "telegram-user-1",
|
|
99
|
-
status: "active",
|
|
100
|
-
policy,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
97
|
+
status: "active",
|
|
98
|
+
policy,
|
|
103
99
|
});
|
|
104
100
|
}
|
|
105
101
|
|
|
@@ -236,16 +232,12 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
236
232
|
});
|
|
237
233
|
|
|
238
234
|
test("blocks non-guardian Slack reactions silently (no reply) before persistence while locked", async () => {
|
|
239
|
-
|
|
235
|
+
seedContactChannel({
|
|
236
|
+
sourceChannel: "slack",
|
|
237
|
+
externalUserId: "slack-user-1",
|
|
240
238
|
displayName: "Example Slack User",
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
type: "slack",
|
|
244
|
-
address: "slack-user-1",
|
|
245
|
-
status: "active",
|
|
246
|
-
policy: "allow",
|
|
247
|
-
},
|
|
248
|
-
],
|
|
239
|
+
status: "active",
|
|
240
|
+
policy: "allow",
|
|
249
241
|
});
|
|
250
242
|
const processMessage = mock(async () => {
|
|
251
243
|
throw new Error("processMessage should not run");
|
|
@@ -81,8 +81,8 @@ mock.module("../runtime/gateway-client.js", () => ({
|
|
|
81
81
|
}));
|
|
82
82
|
|
|
83
83
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
setConversationProcessingStartedAt: () => {},
|
|
85
|
+
isConversationProcessing: () => false,
|
|
86
86
|
setConversationOriginChannelIfUnset: () => {},
|
|
87
87
|
updateConversationContextWindow: () => {},
|
|
88
88
|
deleteMessageById: () => {},
|
|
@@ -240,6 +240,7 @@ describe("channel-reply-delivery", () => {
|
|
|
240
240
|
payload: {
|
|
241
241
|
chatId: "chat-1",
|
|
242
242
|
text: "Before tool.",
|
|
243
|
+
useBlocks: true,
|
|
243
244
|
attachments: undefined,
|
|
244
245
|
assistantId: "assistant-1",
|
|
245
246
|
},
|
|
@@ -249,6 +250,7 @@ describe("channel-reply-delivery", () => {
|
|
|
249
250
|
payload: {
|
|
250
251
|
chatId: "chat-1",
|
|
251
252
|
text: "After tool.",
|
|
253
|
+
useBlocks: true,
|
|
252
254
|
attachments,
|
|
253
255
|
assistantId: "assistant-1",
|
|
254
256
|
},
|
|
@@ -307,12 +309,14 @@ describe("channel-reply-delivery", () => {
|
|
|
307
309
|
expect(deliveryCalls[0].payload).toEqual({
|
|
308
310
|
chatId: "chat-3",
|
|
309
311
|
text: "Before tool.",
|
|
312
|
+
useBlocks: true,
|
|
310
313
|
attachments: undefined,
|
|
311
314
|
assistantId: "assistant-2",
|
|
312
315
|
});
|
|
313
316
|
expect(deliveryCalls[1].payload).toEqual({
|
|
314
317
|
chatId: "chat-3",
|
|
315
318
|
text: "After tool.",
|
|
319
|
+
useBlocks: true,
|
|
316
320
|
attachments: [
|
|
317
321
|
{
|
|
318
322
|
id: "att-2",
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { makeMockLogger } from "./helpers/mock-logger.js";
|
|
6
|
+
|
|
7
|
+
mock.module("../util/logger.js", () => ({
|
|
8
|
+
getLogger: () => makeMockLogger(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
appendCompactionEvent,
|
|
13
|
+
forkCompactionLedger,
|
|
14
|
+
getLatestCompactionEventAtOrBefore,
|
|
15
|
+
} from "../memory/compaction-ledger-store.js";
|
|
16
|
+
import { getDb } from "../memory/db-connection.js";
|
|
17
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
18
|
+
import {
|
|
19
|
+
conversationCompactionEvents,
|
|
20
|
+
conversations,
|
|
21
|
+
} from "../memory/schema.js";
|
|
22
|
+
|
|
23
|
+
await initializeDb();
|
|
24
|
+
|
|
25
|
+
function reset(): void {
|
|
26
|
+
const db = getDb();
|
|
27
|
+
db.delete(conversationCompactionEvents).run();
|
|
28
|
+
db.delete(conversations).run();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeConversation(id: string): void {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
getDb()
|
|
34
|
+
.insert(conversations)
|
|
35
|
+
.values({ id, title: id, createdAt: now, updatedAt: now })
|
|
36
|
+
.run();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function forkEventsFor(conversationId: string) {
|
|
40
|
+
return getDb()
|
|
41
|
+
.select()
|
|
42
|
+
.from(conversationCompactionEvents)
|
|
43
|
+
.where(eq(conversationCompactionEvents.conversationId, conversationId))
|
|
44
|
+
.all();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("compaction-ledger-store", () => {
|
|
48
|
+
beforeEach(reset);
|
|
49
|
+
|
|
50
|
+
test("getLatestCompactionEventAtOrBefore returns the newest event at-or-before the cutoff", () => {
|
|
51
|
+
makeConversation("conv");
|
|
52
|
+
appendCompactionEvent("conv", {
|
|
53
|
+
compactedAt: 100,
|
|
54
|
+
summary: "s100",
|
|
55
|
+
compactedMessageCount: 1,
|
|
56
|
+
});
|
|
57
|
+
appendCompactionEvent("conv", {
|
|
58
|
+
compactedAt: 200,
|
|
59
|
+
summary: "s200",
|
|
60
|
+
compactedMessageCount: 3,
|
|
61
|
+
});
|
|
62
|
+
appendCompactionEvent("conv", {
|
|
63
|
+
compactedAt: 300,
|
|
64
|
+
summary: "s300",
|
|
65
|
+
compactedMessageCount: 5,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(getLatestCompactionEventAtOrBefore("conv", 50)).toBeNull();
|
|
69
|
+
expect(
|
|
70
|
+
getLatestCompactionEventAtOrBefore("conv", 100)?.compactedMessageCount,
|
|
71
|
+
).toBe(1);
|
|
72
|
+
expect(
|
|
73
|
+
getLatestCompactionEventAtOrBefore("conv", 250)?.compactedMessageCount,
|
|
74
|
+
).toBe(3);
|
|
75
|
+
expect(
|
|
76
|
+
getLatestCompactionEventAtOrBefore("conv", 999)?.compactedMessageCount,
|
|
77
|
+
).toBe(5);
|
|
78
|
+
expect(getLatestCompactionEventAtOrBefore("conv", null)).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("getLatestCompactionEventAtOrBefore is scoped per conversation", () => {
|
|
82
|
+
makeConversation("a");
|
|
83
|
+
makeConversation("b");
|
|
84
|
+
appendCompactionEvent("a", {
|
|
85
|
+
compactedAt: 100,
|
|
86
|
+
summary: "a",
|
|
87
|
+
compactedMessageCount: 1,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(getLatestCompactionEventAtOrBefore("b", 999)).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("forkCompactionLedger copies only events at-or-before the boundary", () => {
|
|
94
|
+
makeConversation("src");
|
|
95
|
+
makeConversation("fork");
|
|
96
|
+
appendCompactionEvent("src", {
|
|
97
|
+
compactedAt: 100,
|
|
98
|
+
summary: "s100",
|
|
99
|
+
compactedMessageCount: 1,
|
|
100
|
+
});
|
|
101
|
+
appendCompactionEvent("src", {
|
|
102
|
+
compactedAt: 300,
|
|
103
|
+
summary: "s300",
|
|
104
|
+
compactedMessageCount: 5,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
forkCompactionLedger(getDb(), "src", "fork", 200);
|
|
108
|
+
|
|
109
|
+
const copied = forkEventsFor("fork");
|
|
110
|
+
expect(copied).toHaveLength(1);
|
|
111
|
+
expect(copied[0]?.compactedAt).toBe(100);
|
|
112
|
+
expect(copied[0]?.summary).toBe("s100");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("forkCompactionLedger with a null boundary copies nothing", () => {
|
|
116
|
+
makeConversation("src");
|
|
117
|
+
makeConversation("fork");
|
|
118
|
+
appendCompactionEvent("src", {
|
|
119
|
+
compactedAt: 100,
|
|
120
|
+
summary: "s100",
|
|
121
|
+
compactedMessageCount: 1,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
forkCompactionLedger(getDb(), "src", "fork", null);
|
|
125
|
+
|
|
126
|
+
expect(forkEventsFor("fork")).toHaveLength(0);
|
|
127
|
+
});
|
|
128
|
+
});
|