@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
|
@@ -37,14 +37,20 @@ mock.module("../daemon/identity-helpers.js", () => ({
|
|
|
37
37
|
getAssistantName: () => mockAssistantName,
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
createConversation,
|
|
42
|
+
recordConversationPersistedSeq,
|
|
43
|
+
setConversationProcessingStartedAt,
|
|
44
|
+
} from "../memory/conversation-crud.js";
|
|
41
45
|
import { getDb } from "../memory/db-connection.js";
|
|
42
46
|
import { initializeDb } from "../memory/db-init.js";
|
|
43
47
|
import { messages } from "../memory/schema.js";
|
|
44
48
|
import { writeSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
49
|
+
import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
45
50
|
import {
|
|
46
51
|
_resetStreamStateForTesting,
|
|
47
|
-
|
|
52
|
+
getCurrentSeq,
|
|
53
|
+
stampAndBuffer,
|
|
48
54
|
} from "../runtime/assistant-stream-state.js";
|
|
49
55
|
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
50
56
|
import { BadRequestError } from "../runtime/routes/errors.js";
|
|
@@ -103,6 +109,13 @@ interface MessagePayload {
|
|
|
103
109
|
sender?: { displayName?: string; externalUserId?: string };
|
|
104
110
|
messageLink?: { appUrl?: string; webUrl?: string };
|
|
105
111
|
threadLink?: { appUrl?: string; webUrl?: string };
|
|
112
|
+
eventKind?: "message" | "reaction";
|
|
113
|
+
reaction?: {
|
|
114
|
+
emoji: string;
|
|
115
|
+
op: "added" | "removed";
|
|
116
|
+
actorDisplayName?: string;
|
|
117
|
+
targetChannelTs: string;
|
|
118
|
+
};
|
|
106
119
|
};
|
|
107
120
|
}
|
|
108
121
|
|
|
@@ -112,6 +125,28 @@ interface ListResponse {
|
|
|
112
125
|
oldestTimestamp?: number | null;
|
|
113
126
|
oldestMessageId?: string | null;
|
|
114
127
|
seq?: number | null;
|
|
128
|
+
processing?: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Stamp `count` conversation-scoped events to advance the global `seq`
|
|
133
|
+
* counter, mirroring `mkEvent` in assistant-stream-state.test.ts. The
|
|
134
|
+
* conversationId is irrelevant — `getCurrentSeq()` is process-global — so
|
|
135
|
+
* any value works for nudging the high-water mark before a creation.
|
|
136
|
+
*/
|
|
137
|
+
function advanceGlobalSeq(count: number): void {
|
|
138
|
+
for (let i = 0; i < count; i++) {
|
|
139
|
+
stampAndBuffer({
|
|
140
|
+
id: `seq-bump-${i}`,
|
|
141
|
+
conversationId: "seq-bump-conv",
|
|
142
|
+
emittedAt: new Date().toISOString(),
|
|
143
|
+
message: {
|
|
144
|
+
type: "assistant_text_delta",
|
|
145
|
+
conversationId: "seq-bump-conv",
|
|
146
|
+
text: "x",
|
|
147
|
+
},
|
|
148
|
+
} as AssistantEvent);
|
|
149
|
+
}
|
|
115
150
|
}
|
|
116
151
|
|
|
117
152
|
function callList(query: Record<string, string>): ListResponse {
|
|
@@ -138,7 +173,7 @@ describe("handleListMessages page=latest", () => {
|
|
|
138
173
|
const conv = createConversation();
|
|
139
174
|
seedMessages(conv.id, 3);
|
|
140
175
|
// AND the daemon has recorded a persisted seq for it
|
|
141
|
-
|
|
176
|
+
recordConversationPersistedSeq(conv.id, 42);
|
|
142
177
|
|
|
143
178
|
// WHEN the snapshot is fetched
|
|
144
179
|
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
@@ -171,7 +206,7 @@ describe("handleListMessages page=latest", () => {
|
|
|
171
206
|
// GIVEN a conversation with a recorded persisted seq
|
|
172
207
|
const conv = createConversation();
|
|
173
208
|
seedMessages(conv.id, 2);
|
|
174
|
-
|
|
209
|
+
recordConversationPersistedSeq(conv.id, 7);
|
|
175
210
|
|
|
176
211
|
// WHEN fetched with no pagination params
|
|
177
212
|
const body = callList({ conversationId: conv.id });
|
|
@@ -179,6 +214,138 @@ describe("handleListMessages page=latest", () => {
|
|
|
179
214
|
// THEN the seq still rides along
|
|
180
215
|
expect(body.seq).toBe(7);
|
|
181
216
|
});
|
|
217
|
+
|
|
218
|
+
test("recording advances the seq monotonically and never regresses", () => {
|
|
219
|
+
/**
|
|
220
|
+
* Out-of-order async commits must not lower the high-water mark — the
|
|
221
|
+
* `WHERE seq < ?` guard on the persist UPDATE keeps it monotonic.
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
// GIVEN a conversation whose persisted seq has been recorded
|
|
225
|
+
const conv = createConversation();
|
|
226
|
+
recordConversationPersistedSeq(conv.id, 12);
|
|
227
|
+
|
|
228
|
+
// WHEN a lower seq is recorded (a late, out-of-order commit)
|
|
229
|
+
recordConversationPersistedSeq(conv.id, 8);
|
|
230
|
+
|
|
231
|
+
// THEN the high-water seq is unchanged
|
|
232
|
+
expect(callList({ conversationId: conv.id }).seq).toBe(12);
|
|
233
|
+
|
|
234
|
+
// AND a higher seq still advances it
|
|
235
|
+
recordConversationPersistedSeq(conv.id, 20);
|
|
236
|
+
expect(callList({ conversationId: conv.id }).seq).toBe(20);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("recording ignores non-positive and non-finite seq values", () => {
|
|
240
|
+
// GIVEN a freshly created conversation (no stream activity → null seed)
|
|
241
|
+
const conv = createConversation();
|
|
242
|
+
|
|
243
|
+
// WHEN non-positive / non-finite seqs are recorded
|
|
244
|
+
recordConversationPersistedSeq(conv.id, 0);
|
|
245
|
+
recordConversationPersistedSeq(conv.id, -3);
|
|
246
|
+
recordConversationPersistedSeq(conv.id, Number.NaN);
|
|
247
|
+
recordConversationPersistedSeq(conv.id, Number.POSITIVE_INFINITY);
|
|
248
|
+
|
|
249
|
+
// THEN none take effect and seq stays null
|
|
250
|
+
expect(callList({ conversationId: conv.id }).seq).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("a freshly created conversation is anchored to the current global seq", () => {
|
|
254
|
+
/**
|
|
255
|
+
* Conversations created after some stream activity must not report a
|
|
256
|
+
* null `seq` — that would force the client to cold-start with no
|
|
257
|
+
* alignment baseline. They inherit the current high-water global seq
|
|
258
|
+
* at creation time so the first snapshot can align with the stream.
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
// GIVEN the global stream has already advanced past seq 0
|
|
262
|
+
advanceGlobalSeq(5);
|
|
263
|
+
expect(getCurrentSeq()).toBe(5);
|
|
264
|
+
|
|
265
|
+
// WHEN a brand-new conversation is created and its snapshot fetched
|
|
266
|
+
const conv = createConversation();
|
|
267
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
268
|
+
|
|
269
|
+
// THEN the snapshot is anchored to the global seq at creation time
|
|
270
|
+
expect(body.seq).toBe(5);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("a conversation created before any stream activity stays null", () => {
|
|
274
|
+
/**
|
|
275
|
+
* With nothing stamped this process, `getCurrentSeq()` is 0 and the
|
|
276
|
+
* creation seed stores NULL (non-positive seqs aren't recorded), so the
|
|
277
|
+
* snapshot legitimately reports null and the client cold-starts.
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
// GIVEN no events have been stamped (stream reset in beforeEach)
|
|
281
|
+
expect(getCurrentSeq()).toBe(0);
|
|
282
|
+
|
|
283
|
+
// WHEN a conversation is created and its snapshot fetched
|
|
284
|
+
const conv = createConversation();
|
|
285
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
286
|
+
|
|
287
|
+
// THEN seq remains null
|
|
288
|
+
expect(body.seq).toBeNull();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("processing state", () => {
|
|
293
|
+
test("reports processing=true while the conversation is mid-turn", () => {
|
|
294
|
+
/**
|
|
295
|
+
* The authoritative processing flag lets a client recover from a
|
|
296
|
+
* dropped stream: it can ask the server whether a turn is still
|
|
297
|
+
* running rather than spinning forever.
|
|
298
|
+
*/
|
|
299
|
+
|
|
300
|
+
// GIVEN a conversation marked as processing
|
|
301
|
+
const conv = createConversation();
|
|
302
|
+
seedMessages(conv.id, 1);
|
|
303
|
+
setConversationProcessingStartedAt(conv.id, Date.now());
|
|
304
|
+
|
|
305
|
+
// WHEN the snapshot is fetched
|
|
306
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
307
|
+
|
|
308
|
+
// THEN it reports the conversation is processing
|
|
309
|
+
expect(body.processing).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("reports processing=false when the conversation is idle", () => {
|
|
313
|
+
// GIVEN an idle conversation (processing_started_at is null)
|
|
314
|
+
const conv = createConversation();
|
|
315
|
+
seedMessages(conv.id, 2);
|
|
316
|
+
|
|
317
|
+
// WHEN the snapshot is fetched
|
|
318
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
319
|
+
|
|
320
|
+
// THEN it reports the conversation is not processing
|
|
321
|
+
expect(body.processing).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("the no-pagination path also reports processing state", () => {
|
|
325
|
+
/** `processing` is present on every resolved-conversation shape. */
|
|
326
|
+
|
|
327
|
+
// GIVEN a processing conversation
|
|
328
|
+
const conv = createConversation();
|
|
329
|
+
seedMessages(conv.id, 1);
|
|
330
|
+
setConversationProcessingStartedAt(conv.id, Date.now());
|
|
331
|
+
|
|
332
|
+
// WHEN fetched with no pagination params
|
|
333
|
+
const body = callList({ conversationId: conv.id });
|
|
334
|
+
|
|
335
|
+
// THEN processing rides along
|
|
336
|
+
expect(body.processing).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("an unresolved conversationKey reports processing=false (page=latest)", () => {
|
|
340
|
+
// WHEN a never-created key is fetched with the stable contract
|
|
341
|
+
const body = callList({
|
|
342
|
+
conversationKey: "no-such-key",
|
|
343
|
+
page: "latest",
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// THEN it cannot be processing
|
|
347
|
+
expect(body.processing).toBe(false);
|
|
348
|
+
});
|
|
182
349
|
});
|
|
183
350
|
|
|
184
351
|
test("page=latest with no limit returns all messages chronologically", () => {
|
|
@@ -322,6 +489,7 @@ describe("handleListMessages page=latest", () => {
|
|
|
322
489
|
webUrl:
|
|
323
490
|
"https://example.slack.com/archives/C123ABCDEF/p1710000000000100",
|
|
324
491
|
},
|
|
492
|
+
eventKind: "message",
|
|
325
493
|
});
|
|
326
494
|
});
|
|
327
495
|
|
|
@@ -367,6 +535,7 @@ describe("handleListMessages page=latest", () => {
|
|
|
367
535
|
webUrl:
|
|
368
536
|
"https://example.slack.com/archives/C123ABCDEF/p1710000000000200",
|
|
369
537
|
},
|
|
538
|
+
eventKind: "message",
|
|
370
539
|
});
|
|
371
540
|
});
|
|
372
541
|
|
|
@@ -8,6 +8,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
8
8
|
getLogger: () => makeMockLogger(),
|
|
9
9
|
initLogger: () => {},
|
|
10
10
|
pruneOldLogFiles: () => 0,
|
|
11
|
+
getCurrentLogFilePath: () => "/tmp/test-assistant.log",
|
|
11
12
|
truncateForLog: (value: string, maxLen = 500) => value.slice(0, maxLen),
|
|
12
13
|
}));
|
|
13
14
|
|
|
@@ -110,6 +111,7 @@ mock.module("../providers/registry.js", () => ({
|
|
|
110
111
|
isNativeWebSearchCapableProvider: () => false,
|
|
111
112
|
listProviders: () => [],
|
|
112
113
|
resolveProviderFromConnection: async () => null,
|
|
114
|
+
shouldUseNativeWebSearch: () => false,
|
|
113
115
|
}));
|
|
114
116
|
|
|
115
117
|
mock.module("../memory/embedding-backend.js", () => ({
|
|
@@ -121,6 +123,7 @@ mock.module("../memory/embedding-backend.js", () => ({
|
|
|
121
123
|
model: "test",
|
|
122
124
|
vectors: [],
|
|
123
125
|
}),
|
|
126
|
+
geminiCacheExtras: () => [],
|
|
124
127
|
generateSparseEmbedding: () => ({ indices: [], values: [] }),
|
|
125
128
|
getMemoryBackendStatus: async () => ({
|
|
126
129
|
enabled: false,
|
|
@@ -551,10 +551,14 @@ describe("access-request-helper unit tests", () => {
|
|
|
551
551
|
expect(payload.guardianBindingChannel).toBe("telegram");
|
|
552
552
|
});
|
|
553
553
|
|
|
554
|
-
test("notifyGuardianOfAccessRequest
|
|
555
|
-
// Gateway delivery
|
|
556
|
-
|
|
557
|
-
|
|
554
|
+
test("notifyGuardianOfAccessRequest resolves the source-channel guardian from the gateway delivery", async () => {
|
|
555
|
+
// Gateway delivery serves a telegram guardian matching the vellum anchor.
|
|
556
|
+
seedGatewayGuardian({
|
|
557
|
+
channelType: "telegram",
|
|
558
|
+
address: "guardian-tg",
|
|
559
|
+
externalChatId: "tg-chat",
|
|
560
|
+
principalId: anchorPrincipalId,
|
|
561
|
+
});
|
|
558
562
|
createGuardianBinding({
|
|
559
563
|
channel: "telegram",
|
|
560
564
|
guardianExternalUserId: "guardian-tg",
|
|
@@ -578,7 +582,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
578
582
|
kind: "access_request",
|
|
579
583
|
});
|
|
580
584
|
expect(pending.length).toBe(1);
|
|
581
|
-
// Request is decidable:
|
|
585
|
+
// Request is decidable: gateway delivery supplied the principal + source binding.
|
|
582
586
|
expect(pending[0].guardianPrincipalId).toBe(anchorPrincipalId);
|
|
583
587
|
expect(pending[0].guardianExternalUserId).toBe("guardian-tg");
|
|
584
588
|
|
|
@@ -589,10 +593,8 @@ describe("access-request-helper unit tests", () => {
|
|
|
589
593
|
expect(payload.guardianBindingChannel).toBe("telegram");
|
|
590
594
|
});
|
|
591
595
|
|
|
592
|
-
test("notifyGuardianOfAccessRequest
|
|
593
|
-
//
|
|
594
|
-
gatewayGuardians = [];
|
|
595
|
-
|
|
596
|
+
test("notifyGuardianOfAccessRequest resolves the vellum anchor from the gateway delivery", async () => {
|
|
597
|
+
// Only the vellum anchor (seeded in resetState) is served by the gateway.
|
|
596
598
|
const result = await notifyGuardianOfAccessRequest({
|
|
597
599
|
canonicalAssistantId: "self",
|
|
598
600
|
sourceChannel: "telegram",
|
|
@@ -608,7 +610,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
608
610
|
kind: "access_request",
|
|
609
611
|
});
|
|
610
612
|
expect(pending.length).toBe(1);
|
|
611
|
-
// Decidable via the
|
|
613
|
+
// Decidable via the gateway-served vellum anchor principal.
|
|
612
614
|
expect(pending[0].guardianPrincipalId).toBe(anchorPrincipalId);
|
|
613
615
|
|
|
614
616
|
const payload = emitSignalCalls[0].contextPayload as Record<
|
|
@@ -618,9 +620,9 @@ describe("access-request-helper unit tests", () => {
|
|
|
618
620
|
expect(payload.guardianBindingChannel).toBe("vellum");
|
|
619
621
|
});
|
|
620
622
|
|
|
621
|
-
test("notifyGuardianOfAccessRequest does not create a decisionable request when
|
|
622
|
-
// Genuinely unbound assistant: gateway
|
|
623
|
-
//
|
|
623
|
+
test("notifyGuardianOfAccessRequest does not create a decisionable request when the gateway delivery is empty", async () => {
|
|
624
|
+
// Genuinely unbound assistant: gateway serves no guardian. The guard
|
|
625
|
+
// rejects creation of a decisionable request without a principal.
|
|
624
626
|
gatewayGuardians = [];
|
|
625
627
|
const db = getDb();
|
|
626
628
|
db.run("DELETE FROM contact_channels");
|
|
@@ -30,11 +30,24 @@ import {
|
|
|
30
30
|
|
|
31
31
|
// ── Mock state ────────────────────────────────────────────────────
|
|
32
32
|
|
|
33
|
+
interface GuardianDeliveryStub {
|
|
34
|
+
channelType: string;
|
|
35
|
+
address: string;
|
|
36
|
+
status: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
let mockWorkspaceDir: string = "";
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
// Gateway guardian delivery cache; the guardian's userFile (local INFO) is
|
|
41
|
+
// joined via findContactByAddress on the delivery's address.
|
|
42
|
+
let mockGuardianDeliveries: GuardianDeliveryStub[] = [];
|
|
43
|
+
let mockContactsByAddress: Record<string, { userFile: string | null }> = {};
|
|
44
|
+
|
|
45
|
+
function seedVellumGuardian(userFile: string | null): void {
|
|
46
|
+
mockGuardianDeliveries = [
|
|
47
|
+
{ channelType: "vellum", address: "vellum:self", status: "active" },
|
|
48
|
+
];
|
|
49
|
+
mockContactsByAddress["vellum:vellum:self"] = { userFile };
|
|
50
|
+
}
|
|
38
51
|
|
|
39
52
|
// ── Mock modules (must precede imports from the module under test) ──
|
|
40
53
|
|
|
@@ -59,10 +72,20 @@ mock.module("../util/platform.js", () => ({
|
|
|
59
72
|
}));
|
|
60
73
|
|
|
61
74
|
mock.module("../contacts/contact-store.js", () => ({
|
|
62
|
-
findContactByAddress: () =>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
findContactByAddress: (channelType: string, address: string) =>
|
|
76
|
+
mockContactsByAddress[`${channelType}:${address}`] ?? null,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
80
|
+
peekCachedGuardianDelivery: (input?: { channelTypes?: string[] }) => {
|
|
81
|
+
if (!input?.channelTypes) return mockGuardianDeliveries;
|
|
82
|
+
return mockGuardianDeliveries.filter((g) =>
|
|
83
|
+
input.channelTypes!.includes(g.channelType),
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
guardianForChannel: (list: GuardianDeliveryStub[], channelType: string) =>
|
|
87
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
88
|
+
anyGuardian: (list: GuardianDeliveryStub[]) => list[0],
|
|
66
89
|
}));
|
|
67
90
|
|
|
68
91
|
// Import AFTER mocks so the module under test binds to the stubbed
|
|
@@ -87,7 +110,8 @@ afterAll(() => {
|
|
|
87
110
|
|
|
88
111
|
beforeEach(() => {
|
|
89
112
|
mockWorkspaceDir = mkdtempSync(join(testRoot, "ws-"));
|
|
90
|
-
|
|
113
|
+
mockGuardianDeliveries = [];
|
|
114
|
+
mockContactsByAddress = {};
|
|
91
115
|
});
|
|
92
116
|
|
|
93
117
|
afterEach(() => {
|
|
@@ -98,10 +122,7 @@ afterEach(() => {
|
|
|
98
122
|
|
|
99
123
|
describe("writeOnboardingSection", () => {
|
|
100
124
|
test("writes section to guardian persona file when it exists", () => {
|
|
101
|
-
|
|
102
|
-
contact: { userFile: "alice.md" },
|
|
103
|
-
channel: {},
|
|
104
|
-
};
|
|
125
|
+
seedVellumGuardian("alice.md");
|
|
105
126
|
const guardianPath = workspacePath("users/alice.md");
|
|
106
127
|
mkdirSync(workspacePath("users"), { recursive: true });
|
|
107
128
|
writeFileSync(guardianPath, "# User Profile\n\n- **Name:** Alice\n");
|
|
@@ -123,7 +144,8 @@ describe("writeOnboardingSection", () => {
|
|
|
123
144
|
});
|
|
124
145
|
|
|
125
146
|
test("falls back to users/default.md when guardian path is null", () => {
|
|
126
|
-
|
|
147
|
+
mockGuardianDeliveries = [];
|
|
148
|
+
mockContactsByAddress = {};
|
|
127
149
|
mkdirSync(workspacePath("users"), { recursive: true });
|
|
128
150
|
writeFileSync(
|
|
129
151
|
workspacePath("users/default.md"),
|
|
@@ -147,7 +169,8 @@ describe("writeOnboardingSection", () => {
|
|
|
147
169
|
});
|
|
148
170
|
|
|
149
171
|
test("falls back to USER.md when no users/ files exist", () => {
|
|
150
|
-
|
|
172
|
+
mockGuardianDeliveries = [];
|
|
173
|
+
mockContactsByAddress = {};
|
|
151
174
|
|
|
152
175
|
writeOnboardingSection({
|
|
153
176
|
preferredName: "Alice",
|
|
@@ -162,7 +185,8 @@ describe("writeOnboardingSection", () => {
|
|
|
162
185
|
});
|
|
163
186
|
|
|
164
187
|
test("creates file with header + section when target doesn't exist", () => {
|
|
165
|
-
|
|
188
|
+
mockGuardianDeliveries = [];
|
|
189
|
+
mockContactsByAddress = {};
|
|
166
190
|
|
|
167
191
|
writeOnboardingSection({
|
|
168
192
|
preferredName: "Alice",
|
|
@@ -179,7 +203,8 @@ describe("writeOnboardingSection", () => {
|
|
|
179
203
|
});
|
|
180
204
|
|
|
181
205
|
test("idempotent: calling twice produces the same file content", () => {
|
|
182
|
-
|
|
206
|
+
mockGuardianDeliveries = [];
|
|
207
|
+
mockContactsByAddress = {};
|
|
183
208
|
const normalized = {
|
|
184
209
|
preferredName: "Alice",
|
|
185
210
|
commonWork: ["builds code, apps, or tools"],
|
|
@@ -196,7 +221,8 @@ describe("writeOnboardingSection", () => {
|
|
|
196
221
|
});
|
|
197
222
|
|
|
198
223
|
test("replaces existing onboarding section with updated data", () => {
|
|
199
|
-
|
|
224
|
+
mockGuardianDeliveries = [];
|
|
225
|
+
mockContactsByAddress = {};
|
|
200
226
|
|
|
201
227
|
writeOnboardingSection({
|
|
202
228
|
preferredName: "Alice",
|
|
@@ -222,7 +248,8 @@ describe("writeOnboardingSection", () => {
|
|
|
222
248
|
});
|
|
223
249
|
|
|
224
250
|
test("preserves content outside the managed section", () => {
|
|
225
|
-
|
|
251
|
+
mockGuardianDeliveries = [];
|
|
252
|
+
mockContactsByAddress = {};
|
|
226
253
|
writeFileSync(
|
|
227
254
|
workspacePath("USER.md"),
|
|
228
255
|
"# User Profile\n\n- **Name:** Alice\n- **Role:** Engineer\n",
|
|
@@ -242,7 +269,8 @@ describe("writeOnboardingSection", () => {
|
|
|
242
269
|
});
|
|
243
270
|
|
|
244
271
|
test("omits empty fields", () => {
|
|
245
|
-
|
|
272
|
+
mockGuardianDeliveries = [];
|
|
273
|
+
mockContactsByAddress = {};
|
|
246
274
|
|
|
247
275
|
writeOnboardingSection({
|
|
248
276
|
commonWork: [],
|
|
@@ -257,7 +285,8 @@ describe("writeOnboardingSection", () => {
|
|
|
257
285
|
});
|
|
258
286
|
|
|
259
287
|
test("omits preferredName when undefined", () => {
|
|
260
|
-
|
|
288
|
+
mockGuardianDeliveries = [];
|
|
289
|
+
mockContactsByAddress = {};
|
|
261
290
|
|
|
262
291
|
writeOnboardingSection({
|
|
263
292
|
preferredName: undefined,
|
|
@@ -272,7 +301,8 @@ describe("writeOnboardingSection", () => {
|
|
|
272
301
|
});
|
|
273
302
|
|
|
274
303
|
test("preserves content after onboarding section when followed by another heading", () => {
|
|
275
|
-
|
|
304
|
+
mockGuardianDeliveries = [];
|
|
305
|
+
mockContactsByAddress = {};
|
|
276
306
|
writeFileSync(
|
|
277
307
|
workspacePath("USER.md"),
|
|
278
308
|
[
|