@vellumai/assistant 0.10.3 → 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
|
@@ -1,126 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for workspace migration `031-drop-user-md`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 3. Post-017 state (users/<slug>.md already populated, USER.md still on disk
|
|
9
|
-
* as template) — migration does NOT overwrite users/<slug>.md, deletes USER.md.
|
|
10
|
-
* 4. Idempotent re-run — running twice has no additional effect.
|
|
11
|
-
* 5. Guardian with missing users/ directory — migration creates the directory.
|
|
4
|
+
* The migration resolves the guardian contact via a frozen raw-SQL read of
|
|
5
|
+
* the local `contacts`/`contact_channels` tables, backfills a `user_file`
|
|
6
|
+
* slug when missing, migrates any customized `USER.md` into
|
|
7
|
+
* `users/<slug>.md`, and deletes the legacy root `USER.md`.
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `getDb()` to persist backfilled slugs. These tests stub the contact
|
|
16
|
-
* store and DB layer so no real DB is exercised.
|
|
9
|
+
* These tests seed the guardian directly in the local DB so the migration
|
|
10
|
+
* is exercised end-to-end against real SQLite — no gateway or mocks.
|
|
17
11
|
*/
|
|
18
12
|
|
|
19
13
|
import {
|
|
20
14
|
existsSync,
|
|
21
15
|
mkdirSync,
|
|
22
|
-
mkdtempSync,
|
|
23
16
|
readFileSync,
|
|
24
17
|
rmSync,
|
|
25
18
|
writeFileSync,
|
|
26
19
|
} from "node:fs";
|
|
27
|
-
import { tmpdir } from "node:os";
|
|
28
20
|
import { join } from "node:path";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
22
|
+
|
|
23
|
+
import { getSqlite } from "../memory/db-connection.js";
|
|
24
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
25
|
+
|
|
26
|
+
await initializeDb();
|
|
27
|
+
|
|
28
|
+
import { dropUserMdMigration } from "../workspace/migrations/031-drop-user-md.js";
|
|
29
|
+
|
|
30
|
+
// ── DB seeding helpers ────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function resetContactTables(): void {
|
|
33
|
+
const sqlite = getSqlite();
|
|
34
|
+
sqlite.run("DELETE FROM contact_channels");
|
|
35
|
+
sqlite.run("DELETE FROM contacts");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Insert a guardian contact plus an active channel so the migration's
|
|
40
|
+
* raw-SQL guardian read resolves it.
|
|
41
|
+
*/
|
|
42
|
+
function seedGuardian(input: {
|
|
43
43
|
id: string;
|
|
44
44
|
displayName: string;
|
|
45
45
|
userFile: string | null;
|
|
46
|
+
channelType?: string;
|
|
47
|
+
address?: string;
|
|
48
|
+
verifiedAt?: number;
|
|
49
|
+
}): void {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const sqlite = getSqlite();
|
|
52
|
+
sqlite.run(
|
|
53
|
+
`INSERT INTO contacts (id, display_name, created_at, updated_at, role, user_file, contact_type)
|
|
54
|
+
VALUES (?, ?, ?, ?, 'guardian', ?, 'human')`,
|
|
55
|
+
[input.id, input.displayName, now, now, input.userFile],
|
|
56
|
+
);
|
|
57
|
+
sqlite.run(
|
|
58
|
+
`INSERT INTO contact_channels (id, contact_id, type, address, status, verified_at, created_at)
|
|
59
|
+
VALUES (?, ?, ?, ?, 'active', ?, ?)`,
|
|
60
|
+
[
|
|
61
|
+
`${input.id}-ch`,
|
|
62
|
+
input.id,
|
|
63
|
+
input.channelType ?? "vellum",
|
|
64
|
+
input.address ?? "vellum:self",
|
|
65
|
+
input.verifiedAt ?? now,
|
|
66
|
+
now,
|
|
67
|
+
],
|
|
68
|
+
);
|
|
46
69
|
}
|
|
47
70
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
let mockSlugOverride: ((displayName: string) => string) | null = null;
|
|
57
|
-
|
|
58
|
-
// Records drizzle `.update(contacts).set({userFile: ...}).where(...).run()` calls.
|
|
59
|
-
let updatedUserFiles: Array<{ contactId: string; userFile: string }> = [];
|
|
60
|
-
|
|
61
|
-
// ── Mock modules (must precede migration import) ──────────────────
|
|
62
|
-
|
|
63
|
-
mock.module("../contacts/contact-store.js", () => ({
|
|
64
|
-
findGuardianForChannel: (channelType: string) =>
|
|
65
|
-
channelType === "vellum" ? mockVellumGuardian : null,
|
|
66
|
-
listGuardianChannels: () => mockAnyGuardian,
|
|
67
|
-
generateUserFileSlug: (displayName: string) => {
|
|
68
|
-
if (mockSlugOverride) return mockSlugOverride(displayName);
|
|
69
|
-
const base =
|
|
70
|
-
displayName
|
|
71
|
-
.toLowerCase()
|
|
72
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
73
|
-
.replace(/^-+|-+$/g, "") || "user";
|
|
74
|
-
return `${base}.md`;
|
|
75
|
-
},
|
|
76
|
-
}));
|
|
77
|
-
|
|
78
|
-
// Minimal drizzle-compatible stub for the single `update` call in the
|
|
79
|
-
// migration. The migration builds:
|
|
80
|
-
// db.update(contacts).set({ userFile: slug }).where(eq(contacts.id, guardian.id)).run();
|
|
81
|
-
// The stub captures the payload into `updatedUserFiles` and also mutates
|
|
82
|
-
// the active mock guardian in place so downstream reads observe the new slug.
|
|
83
|
-
mock.module("../memory/db-connection.js", () => ({
|
|
84
|
-
getDb: () => ({
|
|
85
|
-
update: () => ({
|
|
86
|
-
set: (values: { userFile: string }) => ({
|
|
87
|
-
where: () => ({
|
|
88
|
-
run: () => {
|
|
89
|
-
const guardian =
|
|
90
|
-
mockVellumGuardian?.contact ?? mockAnyGuardian?.contact ?? null;
|
|
91
|
-
if (guardian) {
|
|
92
|
-
guardian.userFile = values.userFile;
|
|
93
|
-
updatedUserFiles.push({
|
|
94
|
-
contactId: guardian.id,
|
|
95
|
-
userFile: values.userFile,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
}),
|
|
100
|
-
}),
|
|
101
|
-
}),
|
|
102
|
-
}),
|
|
103
|
-
}));
|
|
104
|
-
|
|
105
|
-
// drizzle-orm's `eq()` is invoked by the migration; stub it out so we
|
|
106
|
-
// don't need the real module loaded for unit tests.
|
|
107
|
-
mock.module("drizzle-orm", () => ({
|
|
108
|
-
eq: (_col: unknown, value: unknown) => ({ col: _col, value }),
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
// Stub the schema import so drizzle operand construction doesn't touch
|
|
112
|
-
// the real sqlite schema (which pulls in the DB).
|
|
113
|
-
mock.module("../memory/schema/contacts.js", () => ({
|
|
114
|
-
contacts: { id: "id", userFile: "userFile" },
|
|
115
|
-
}));
|
|
116
|
-
|
|
117
|
-
// Import AFTER mocks so the migration binds to the stubs above.
|
|
118
|
-
import { dropUserMdMigration } from "../workspace/migrations/031-drop-user-md.js";
|
|
71
|
+
function guardianUserFile(id: string): string | null {
|
|
72
|
+
const row = getSqlite()
|
|
73
|
+
.query<{ user_file: string | null }, [string]>(
|
|
74
|
+
`SELECT user_file FROM contacts WHERE id = ?`,
|
|
75
|
+
)
|
|
76
|
+
.get(id);
|
|
77
|
+
return row?.user_file ?? null;
|
|
78
|
+
}
|
|
119
79
|
|
|
120
80
|
// ── Test workspace scaffold ───────────────────────────────────────
|
|
121
81
|
|
|
122
|
-
|
|
123
|
-
|
|
82
|
+
function workspaceDir(): string {
|
|
83
|
+
const dir = process.env.VELLUM_WORKSPACE_DIR;
|
|
84
|
+
if (!dir) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"VELLUM_WORKSPACE_DIR should be set by the test preload — aborting",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return dir;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function userMdPath(): string {
|
|
93
|
+
return join(workspaceDir(), "USER.md");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cleanupWorkspaceFiles(): void {
|
|
97
|
+
const dir = workspaceDir();
|
|
98
|
+
for (const p of [join(dir, "USER.md"), join(dir, "users")]) {
|
|
99
|
+
rmSync(p, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
124
102
|
|
|
125
103
|
function templateContent(): string {
|
|
126
104
|
return `_ Lines starting with _ are comments - they won't appear in the system prompt
|
|
@@ -151,24 +129,9 @@ function customizedContent(): string {
|
|
|
151
129
|
`;
|
|
152
130
|
}
|
|
153
131
|
|
|
154
|
-
beforeAll(() => {
|
|
155
|
-
testRoot = mkdtempSync(join(tmpdir(), "drop-user-md-test-"));
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
afterAll(() => {
|
|
159
|
-
rmSync(testRoot, { recursive: true, force: true });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
132
|
beforeEach(() => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
mockAnyGuardian = null;
|
|
166
|
-
mockSlugOverride = null;
|
|
167
|
-
updatedUserFiles = [];
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
afterEach(() => {
|
|
171
|
-
rmSync(workspaceDir, { recursive: true, force: true });
|
|
133
|
+
resetContactTables();
|
|
134
|
+
cleanupWorkspaceFiles();
|
|
172
135
|
});
|
|
173
136
|
|
|
174
137
|
// ── Tests ─────────────────────────────────────────────────────────
|
|
@@ -181,188 +144,183 @@ describe("workspace migration 031-drop-user-md", () => {
|
|
|
181
144
|
);
|
|
182
145
|
});
|
|
183
146
|
|
|
147
|
+
test("does not declare retryFailedCheckpoint (no gateway coupling)", () => {
|
|
148
|
+
expect(dropUserMdMigration.retryFailedCheckpoint).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
184
151
|
test("fresh install (no guardian, no USER.md) is a no-op", () => {
|
|
185
|
-
|
|
186
|
-
|
|
152
|
+
dropUserMdMigration.run(workspaceDir());
|
|
153
|
+
|
|
154
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
155
|
+
expect(existsSync(join(workspaceDir(), "users"))).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("no guardian with unmodified-template USER.md — deletes it", () => {
|
|
159
|
+
writeFileSync(userMdPath(), templateContent(), "utf-8");
|
|
160
|
+
|
|
161
|
+
dropUserMdMigration.run(workspaceDir());
|
|
187
162
|
|
|
188
|
-
expect(existsSync(
|
|
189
|
-
|
|
190
|
-
|
|
163
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("no guardian with customized USER.md — preserves it (mirror may be stale)", () => {
|
|
167
|
+
const content = customizedContent();
|
|
168
|
+
writeFileSync(userMdPath(), content, "utf-8");
|
|
169
|
+
|
|
170
|
+
dropUserMdMigration.run(workspaceDir());
|
|
171
|
+
|
|
172
|
+
// The local guardian mirror can be stale, so a customized profile must
|
|
173
|
+
// not be destroyed when no guardian row resolves.
|
|
174
|
+
expect(existsSync(userMdPath())).toBe(true);
|
|
175
|
+
expect(readFileSync(userMdPath(), "utf-8")).toBe(content);
|
|
191
176
|
});
|
|
192
177
|
|
|
193
178
|
test("pre-017 customized USER.md with guardian missing userFile backfills slug and migrates content", () => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
contact: {
|
|
197
|
-
id: "guardian-1",
|
|
198
|
-
displayName: "Chris",
|
|
199
|
-
userFile: null,
|
|
200
|
-
},
|
|
201
|
-
channel: { type: "vellum" },
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const userMdPath = join(workspaceDir, "USER.md");
|
|
179
|
+
seedGuardian({ id: "guardian-1", displayName: "Chris", userFile: null });
|
|
180
|
+
|
|
205
181
|
const content = customizedContent();
|
|
206
|
-
writeFileSync(userMdPath, content, "utf-8");
|
|
182
|
+
writeFileSync(userMdPath(), content, "utf-8");
|
|
207
183
|
|
|
208
|
-
dropUserMdMigration.run(workspaceDir);
|
|
184
|
+
dropUserMdMigration.run(workspaceDir());
|
|
209
185
|
|
|
210
|
-
// Backfill happened:
|
|
211
|
-
expect(
|
|
212
|
-
expect(updatedUserFiles[0].contactId).toBe("guardian-1");
|
|
213
|
-
expect(updatedUserFiles[0].userFile).toBe("chris.md");
|
|
186
|
+
// Backfill happened: the contact's user_file is now the generated slug.
|
|
187
|
+
expect(guardianUserFile("guardian-1")).toBe("chris.md");
|
|
214
188
|
|
|
215
189
|
// Content was migrated into users/chris.md.
|
|
216
|
-
const destPath = join(workspaceDir, "users", "chris.md");
|
|
190
|
+
const destPath = join(workspaceDir(), "users", "chris.md");
|
|
217
191
|
expect(existsSync(destPath)).toBe(true);
|
|
218
192
|
expect(readFileSync(destPath, "utf-8")).toBe(content);
|
|
219
193
|
|
|
220
194
|
// Legacy USER.md was deleted.
|
|
221
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
195
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
222
196
|
});
|
|
223
197
|
|
|
224
198
|
test("post-017 users/<slug>.md already populated, USER.md still on disk as template — does not overwrite dest, deletes USER.md", () => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
channel: { type: "vellum" },
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// Pre-populated persona file (post-017 state).
|
|
236
|
-
const usersDir = join(workspaceDir, "users");
|
|
199
|
+
seedGuardian({
|
|
200
|
+
id: "guardian-2",
|
|
201
|
+
displayName: "Chris",
|
|
202
|
+
userFile: "chris.md",
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const usersDir = join(workspaceDir(), "users");
|
|
237
206
|
mkdirSync(usersDir, { recursive: true });
|
|
238
207
|
const destPath = join(usersDir, "chris.md");
|
|
239
208
|
const existingPersona = "# Chris's Profile\n\n- Loves kayaking\n";
|
|
240
209
|
writeFileSync(destPath, existingPersona, "utf-8");
|
|
241
210
|
|
|
242
|
-
|
|
243
|
-
const userMdPath = join(workspaceDir, "USER.md");
|
|
244
|
-
writeFileSync(userMdPath, templateContent(), "utf-8");
|
|
211
|
+
writeFileSync(userMdPath(), templateContent(), "utf-8");
|
|
245
212
|
|
|
246
|
-
dropUserMdMigration.run(workspaceDir);
|
|
213
|
+
dropUserMdMigration.run(workspaceDir());
|
|
247
214
|
|
|
248
215
|
// users/chris.md is untouched.
|
|
249
216
|
expect(readFileSync(destPath, "utf-8")).toBe(existingPersona);
|
|
250
|
-
|
|
251
217
|
// USER.md is gone.
|
|
252
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
253
|
-
|
|
218
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
254
219
|
// No slug backfill necessary.
|
|
255
|
-
expect(
|
|
220
|
+
expect(guardianUserFile("guardian-2")).toBe("chris.md");
|
|
256
221
|
});
|
|
257
222
|
|
|
258
223
|
test("idempotent: second run is a no-op after the first run deleted USER.md", () => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// First run: migrates content and deletes USER.md.
|
|
272
|
-
dropUserMdMigration.run(workspaceDir);
|
|
273
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
274
|
-
const destPath = join(workspaceDir, "users", "alice.md");
|
|
224
|
+
seedGuardian({
|
|
225
|
+
id: "guardian-3",
|
|
226
|
+
displayName: "Alice",
|
|
227
|
+
userFile: "alice.md",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
writeFileSync(userMdPath(), customizedContent(), "utf-8");
|
|
231
|
+
|
|
232
|
+
dropUserMdMigration.run(workspaceDir());
|
|
233
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
234
|
+
const destPath = join(workspaceDir(), "users", "alice.md");
|
|
275
235
|
expect(existsSync(destPath)).toBe(true);
|
|
276
236
|
const afterFirst = readFileSync(destPath, "utf-8");
|
|
277
237
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
dropUserMdMigration.run(workspaceDir);
|
|
281
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
238
|
+
dropUserMdMigration.run(workspaceDir());
|
|
239
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
282
240
|
expect(existsSync(destPath)).toBe(true);
|
|
283
241
|
expect(readFileSync(destPath, "utf-8")).toBe(afterFirst);
|
|
284
242
|
});
|
|
285
243
|
|
|
286
244
|
test("guardian exists but users/ directory is missing — migration creates the directory", () => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// USER.md present but no users/ dir yet.
|
|
297
|
-
const userMdPath = join(workspaceDir, "USER.md");
|
|
298
|
-
writeFileSync(userMdPath, customizedContent(), "utf-8");
|
|
299
|
-
expect(existsSync(join(workspaceDir, "users"))).toBe(false);
|
|
300
|
-
|
|
301
|
-
dropUserMdMigration.run(workspaceDir);
|
|
302
|
-
|
|
303
|
-
expect(existsSync(join(workspaceDir, "users"))).toBe(true);
|
|
304
|
-
const destPath = join(workspaceDir, "users", "bob.md");
|
|
245
|
+
seedGuardian({ id: "guardian-4", displayName: "Bob", userFile: "bob.md" });
|
|
246
|
+
|
|
247
|
+
writeFileSync(userMdPath(), customizedContent(), "utf-8");
|
|
248
|
+
expect(existsSync(join(workspaceDir(), "users"))).toBe(false);
|
|
249
|
+
|
|
250
|
+
dropUserMdMigration.run(workspaceDir());
|
|
251
|
+
|
|
252
|
+
expect(existsSync(join(workspaceDir(), "users"))).toBe(true);
|
|
253
|
+
const destPath = join(workspaceDir(), "users", "bob.md");
|
|
305
254
|
expect(existsSync(destPath)).toBe(true);
|
|
306
255
|
expect(readFileSync(destPath, "utf-8")).toBe(customizedContent());
|
|
307
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
256
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
308
257
|
});
|
|
309
258
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
userFile: "carol.md",
|
|
319
|
-
},
|
|
320
|
-
channels: [{ type: "telegram" }],
|
|
321
|
-
};
|
|
259
|
+
test("falls back to any guardian when no vellum-channel guardian exists", () => {
|
|
260
|
+
seedGuardian({
|
|
261
|
+
id: "guardian-5",
|
|
262
|
+
displayName: "Carol",
|
|
263
|
+
userFile: "carol.md",
|
|
264
|
+
channelType: "telegram",
|
|
265
|
+
address: "carol-tg",
|
|
266
|
+
});
|
|
322
267
|
|
|
323
|
-
|
|
324
|
-
writeFileSync(userMdPath, customizedContent(), "utf-8");
|
|
268
|
+
writeFileSync(userMdPath(), customizedContent(), "utf-8");
|
|
325
269
|
|
|
326
|
-
dropUserMdMigration.run(workspaceDir);
|
|
270
|
+
dropUserMdMigration.run(workspaceDir());
|
|
327
271
|
|
|
328
|
-
const destPath = join(workspaceDir, "users", "carol.md");
|
|
272
|
+
const destPath = join(workspaceDir(), "users", "carol.md");
|
|
329
273
|
expect(existsSync(destPath)).toBe(true);
|
|
330
274
|
expect(readFileSync(destPath, "utf-8")).toBe(customizedContent());
|
|
331
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
275
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("prefers vellum-channel guardian over a more-recently-verified other guardian", () => {
|
|
279
|
+
// Older vellum guardian vs newer telegram guardian: vellum wins.
|
|
280
|
+
seedGuardian({
|
|
281
|
+
id: "guardian-vellum",
|
|
282
|
+
displayName: "Vee",
|
|
283
|
+
userFile: "vee.md",
|
|
284
|
+
channelType: "vellum",
|
|
285
|
+
address: "vellum:self",
|
|
286
|
+
verifiedAt: 1_000,
|
|
287
|
+
});
|
|
288
|
+
seedGuardian({
|
|
289
|
+
id: "guardian-tg",
|
|
290
|
+
displayName: "Tom",
|
|
291
|
+
userFile: "tom.md",
|
|
292
|
+
channelType: "telegram",
|
|
293
|
+
address: "tom-tg",
|
|
294
|
+
verifiedAt: 2_000,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
writeFileSync(userMdPath(), customizedContent(), "utf-8");
|
|
298
|
+
|
|
299
|
+
dropUserMdMigration.run(workspaceDir());
|
|
300
|
+
|
|
301
|
+
expect(existsSync(join(workspaceDir(), "users", "vee.md"))).toBe(true);
|
|
302
|
+
expect(existsSync(join(workspaceDir(), "users", "tom.md"))).toBe(false);
|
|
332
303
|
});
|
|
333
304
|
|
|
334
305
|
test("template-shaped USER.md with no destination file — seeds scaffold and deletes USER.md", () => {
|
|
335
|
-
|
|
336
|
-
contact: {
|
|
337
|
-
id: "guardian-6",
|
|
338
|
-
displayName: "Dana",
|
|
339
|
-
userFile: "dana.md",
|
|
340
|
-
},
|
|
341
|
-
channel: { type: "vellum" },
|
|
342
|
-
};
|
|
306
|
+
seedGuardian({ id: "guardian-6", displayName: "Dana", userFile: "dana.md" });
|
|
343
307
|
|
|
344
|
-
|
|
345
|
-
writeFileSync(userMdPath, templateContent(), "utf-8");
|
|
308
|
+
writeFileSync(userMdPath(), templateContent(), "utf-8");
|
|
346
309
|
|
|
347
|
-
dropUserMdMigration.run(workspaceDir);
|
|
310
|
+
dropUserMdMigration.run(workspaceDir());
|
|
348
311
|
|
|
349
|
-
|
|
350
|
-
expect(existsSync(userMdPath)).toBe(false);
|
|
312
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
351
313
|
|
|
352
|
-
|
|
353
|
-
// (parity with ensureGuardianPersonaFile for new installs).
|
|
354
|
-
const destPath = join(workspaceDir, "users", "dana.md");
|
|
314
|
+
const destPath = join(workspaceDir(), "users", "dana.md");
|
|
355
315
|
expect(existsSync(destPath)).toBe(true);
|
|
356
316
|
const content = readFileSync(destPath, "utf-8");
|
|
357
317
|
expect(content).toContain("# User Profile");
|
|
358
318
|
expect(content).toContain("Preferred name/reference:");
|
|
359
|
-
// Not the legacy template header.
|
|
360
319
|
expect(content).not.toContain("# USER.md");
|
|
361
320
|
});
|
|
362
321
|
|
|
363
322
|
test("down() is a no-op (deletion is irreversible)", () => {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
expect(existsSync(join(workspaceDir, "USER.md"))).toBe(false);
|
|
323
|
+
dropUserMdMigration.down(workspaceDir());
|
|
324
|
+
expect(existsSync(userMdPath())).toBe(false);
|
|
367
325
|
});
|
|
368
326
|
});
|