@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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create the append-only `conversation_compaction_events` ledger and backfill
|
|
5
|
+
* one event per already-compacted conversation from its current cache columns.
|
|
6
|
+
*
|
|
7
|
+
* The `conversations` row keeps the latest compaction
|
|
8
|
+
* (`context_summary` / `context_compacted_message_count` /
|
|
9
|
+
* `context_compacted_at`) as the hot-path cache the load path reads; this table
|
|
10
|
+
* preserves the full history so a fork can inherit the most recent compaction
|
|
11
|
+
* whose event time predates the message it forks from. Pre-feature history
|
|
12
|
+
* beyond the latest compaction is unrecoverable, so existing rows seed exactly
|
|
13
|
+
* one event.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: table creation is guarded on sqlite_master; the backfill is
|
|
16
|
+
* guarded by a memory_checkpoints key and a per-conversation NOT EXISTS so a
|
|
17
|
+
* lost checkpoint cannot duplicate rows.
|
|
18
|
+
*/
|
|
19
|
+
export function migrateCreateCompactionEvents(database: DrizzleDb): void {
|
|
20
|
+
const raw = getSqliteFrom(database);
|
|
21
|
+
|
|
22
|
+
const tableExists = raw
|
|
23
|
+
.query(
|
|
24
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'conversation_compaction_events'`,
|
|
25
|
+
)
|
|
26
|
+
.get();
|
|
27
|
+
|
|
28
|
+
if (!tableExists) {
|
|
29
|
+
try {
|
|
30
|
+
raw.exec("BEGIN");
|
|
31
|
+
|
|
32
|
+
raw.exec(/*sql*/ `
|
|
33
|
+
CREATE TABLE conversation_compaction_events (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
36
|
+
compacted_at INTEGER NOT NULL,
|
|
37
|
+
summary TEXT NOT NULL,
|
|
38
|
+
compacted_message_count INTEGER NOT NULL,
|
|
39
|
+
created_at INTEGER NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
raw.exec(/*sql*/ `
|
|
44
|
+
CREATE INDEX idx_compaction_events_conv_at
|
|
45
|
+
ON conversation_compaction_events(conversation_id, compacted_at)
|
|
46
|
+
`);
|
|
47
|
+
|
|
48
|
+
raw.exec("COMMIT");
|
|
49
|
+
} catch (e) {
|
|
50
|
+
try {
|
|
51
|
+
raw.exec("ROLLBACK");
|
|
52
|
+
} catch {
|
|
53
|
+
/* no active transaction */
|
|
54
|
+
}
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const checkpointKey = "backfill_conversation_compaction_events_v1";
|
|
60
|
+
const checkpoint = raw
|
|
61
|
+
.query(`SELECT 1 FROM memory_checkpoints WHERE key = ?`)
|
|
62
|
+
.get(checkpointKey);
|
|
63
|
+
if (checkpoint) return;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
raw.exec("BEGIN");
|
|
67
|
+
|
|
68
|
+
raw
|
|
69
|
+
.query(
|
|
70
|
+
/*sql*/ `
|
|
71
|
+
INSERT INTO conversation_compaction_events
|
|
72
|
+
(id, conversation_id, compacted_at, summary, compacted_message_count, created_at)
|
|
73
|
+
SELECT
|
|
74
|
+
lower(hex(randomblob(16))),
|
|
75
|
+
c.id,
|
|
76
|
+
c.context_compacted_at,
|
|
77
|
+
c.context_summary,
|
|
78
|
+
c.context_compacted_message_count,
|
|
79
|
+
?
|
|
80
|
+
FROM conversations c
|
|
81
|
+
WHERE c.context_compacted_message_count > 0
|
|
82
|
+
AND c.context_compacted_at IS NOT NULL
|
|
83
|
+
AND c.context_summary IS NOT NULL
|
|
84
|
+
AND NOT EXISTS (
|
|
85
|
+
SELECT 1 FROM conversation_compaction_events e
|
|
86
|
+
WHERE e.conversation_id = c.id
|
|
87
|
+
)
|
|
88
|
+
`,
|
|
89
|
+
)
|
|
90
|
+
.run(Date.now());
|
|
91
|
+
|
|
92
|
+
raw
|
|
93
|
+
.query(
|
|
94
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
95
|
+
)
|
|
96
|
+
.run(checkpointKey, Date.now());
|
|
97
|
+
|
|
98
|
+
raw.exec("COMMIT");
|
|
99
|
+
} catch (e) {
|
|
100
|
+
try {
|
|
101
|
+
raw.exec("ROLLBACK");
|
|
102
|
+
} catch {
|
|
103
|
+
/* no active transaction */
|
|
104
|
+
}
|
|
105
|
+
throw e;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
3
|
+
|
|
4
|
+
const COLUMN_NAME = "seq";
|
|
5
|
+
const COLUMN_DEFINITION = "seq INTEGER";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add the `seq` column to the `conversations` table.
|
|
9
|
+
*
|
|
10
|
+
* The column holds the highest stream `seq` whose content is durably persisted
|
|
11
|
+
* to the conversation's message rows: seeded with the global high-water seq
|
|
12
|
+
* when the row is inserted and advanced on each persistence flush. It is the
|
|
13
|
+
* durable snapshot↔stream alignment baseline returned by `/messages` — a
|
|
14
|
+
* client aligns its snapshot with the `/events` stream by applying only events
|
|
15
|
+
* with a higher `seq`.
|
|
16
|
+
*
|
|
17
|
+
* Nullable: NULL means the conversation was created before any stream activity
|
|
18
|
+
* (global seq was 0) or predates this column, in which case the client
|
|
19
|
+
* cold-starts.
|
|
20
|
+
*
|
|
21
|
+
* No backfill is needed — existing rows default to NULL, which correctly maps
|
|
22
|
+
* to "no recorded baseline, cold-start".
|
|
23
|
+
*
|
|
24
|
+
* Idempotent: guarded with `tableHasColumn` so a crash between the `ALTER
|
|
25
|
+
* TABLE` and the checkpoint write doesn't cause a duplicate-column error on
|
|
26
|
+
* the next boot.
|
|
27
|
+
*/
|
|
28
|
+
export function migrateAddConversationCreationSeq(database: DrizzleDb): void {
|
|
29
|
+
if (tableHasColumn(database, "conversations", COLUMN_NAME)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
database.run(`ALTER TABLE conversations ADD COLUMN ${COLUMN_DEFINITION}`);
|
|
33
|
+
}
|
|
@@ -12,8 +12,11 @@ import { describe, expect, test } from "bun:test";
|
|
|
12
12
|
|
|
13
13
|
const { getDb, getSqlite } = await import("../../db-connection.js");
|
|
14
14
|
const { initializeDb } = await import("../../db-init.js");
|
|
15
|
-
const {
|
|
16
|
-
|
|
15
|
+
const {
|
|
16
|
+
migrateStripThinkingFromConsolidated,
|
|
17
|
+
ROWID_WINDOW,
|
|
18
|
+
WINDOW_TIMEOUT_MS,
|
|
19
|
+
} = await import("../209-strip-thinking-from-consolidated.js");
|
|
17
20
|
|
|
18
21
|
await initializeDb();
|
|
19
22
|
|
|
@@ -40,6 +43,22 @@ function insert(role: string, content: string): { id: string; rowid: number } {
|
|
|
40
43
|
return { id, rowid };
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
/** Insert at an explicit rowid so a test can place rows on either side of a
|
|
47
|
+
* window boundary without materializing every intervening row. */
|
|
48
|
+
function insertAt(
|
|
49
|
+
rowid: number,
|
|
50
|
+
role: string,
|
|
51
|
+
content: string,
|
|
52
|
+
): { id: string; rowid: number } {
|
|
53
|
+
const id = `m209-at-${rowid}`;
|
|
54
|
+
getSqlite()
|
|
55
|
+
.query(
|
|
56
|
+
`INSERT INTO messages (rowid, id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
57
|
+
)
|
|
58
|
+
.run(rowid, id, CONV, role, content, Date.now());
|
|
59
|
+
return { id, rowid };
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
function content(id: string): string {
|
|
44
63
|
return (
|
|
45
64
|
getSqlite().query(`SELECT content FROM messages WHERE id = ?`).get(id) as {
|
|
@@ -52,6 +71,13 @@ function blocks(id: string): Array<Record<string, unknown>> {
|
|
|
52
71
|
return JSON.parse(content(id));
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
/** The persisted resume watermark, or null once the sweep has cleared it. */
|
|
75
|
+
function watermark(): unknown {
|
|
76
|
+
return getSqlite()
|
|
77
|
+
.query(`SELECT value FROM memory_checkpoints WHERE key = ?`)
|
|
78
|
+
.get("migration_209_strip_thinking_watermark");
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
describe("migration 209 — strip thinking from consolidated assistant messages", () => {
|
|
56
82
|
test("strips thinking blocks but keeps text and tool_use, preserving order", async () => {
|
|
57
83
|
const { id } = insert(
|
|
@@ -216,9 +242,56 @@ describe("migration 209 — strip thinking from consolidated assistant messages"
|
|
|
216
242
|
expect(blocks(above.id)).toEqual([{ type: "text", text: "above" }]);
|
|
217
243
|
|
|
218
244
|
// The watermark is cleared once the sweep reaches the end of the table.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
245
|
+
expect(watermark()).toBeNull();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("sweeps a rowid span wider than one window across multiple windows", async () => {
|
|
249
|
+
// Place rows on either side of a window boundary so the sweep must iterate
|
|
250
|
+
// the window loop more than once to reach them. The original 100k window
|
|
251
|
+
// collapsed any table smaller than itself into a single subprocess sweep —
|
|
252
|
+
// this exercises the bounded-window loop that replaces it.
|
|
253
|
+
const base =
|
|
254
|
+
(
|
|
255
|
+
getSqlite().query(`SELECT MAX(rowid) AS m FROM messages`).get() as {
|
|
256
|
+
m: number | null;
|
|
257
|
+
}
|
|
258
|
+
).m ?? 0;
|
|
259
|
+
const first = insertAt(
|
|
260
|
+
base + ROWID_WINDOW,
|
|
261
|
+
"assistant",
|
|
262
|
+
JSON.stringify([
|
|
263
|
+
{ type: "thinking", thinking: "a", signature: "s" },
|
|
264
|
+
{ type: "text", text: "first" },
|
|
265
|
+
]),
|
|
266
|
+
);
|
|
267
|
+
const second = insertAt(
|
|
268
|
+
base + 2 * ROWID_WINDOW + 1,
|
|
269
|
+
"assistant",
|
|
270
|
+
JSON.stringify([
|
|
271
|
+
{ type: "thinking", thinking: "b", signature: "s" },
|
|
272
|
+
{ type: "text", text: "second" },
|
|
273
|
+
]),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
await migrateStripThinkingFromConsolidated(getDb());
|
|
277
|
+
|
|
278
|
+
// Both rows are cleaned even though they sit in different windows, and the
|
|
279
|
+
// sweep runs to completion (watermark cleared) rather than stalling on the
|
|
280
|
+
// first window.
|
|
281
|
+
expect(blocks(first.id)).toEqual([{ type: "text", text: "first" }]);
|
|
282
|
+
expect(blocks(second.id)).toEqual([{ type: "text", text: "second" }]);
|
|
283
|
+
expect(watermark()).toBeNull();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("keeps the sweep window and timeout bounded so it cannot overrun a whole table", () => {
|
|
287
|
+
// Regression guard: ROWID_WINDOW was 100_000, larger than a typical
|
|
288
|
+
// messages table, so the entire table swept in one subprocess that overran
|
|
289
|
+
// runAsyncSqlite's 1h cap, never advanced the watermark, and re-ran the
|
|
290
|
+
// identical doomed window every boot. The window must stay small enough that
|
|
291
|
+
// a single window finishes well within its timeout, and the per-window cap
|
|
292
|
+
// must stay below the 1h whole-process default so a stuck window surfaces in
|
|
293
|
+
// minutes instead of blocking startup for an hour.
|
|
294
|
+
expect(ROWID_WINDOW).toBeLessThanOrEqual(10_000);
|
|
295
|
+
expect(WINDOW_TIMEOUT_MS).toBeLessThan(60 * 60 * 1000);
|
|
223
296
|
});
|
|
224
297
|
});
|
|
@@ -8,10 +8,14 @@ export const contacts = sqliteTable("contacts", {
|
|
|
8
8
|
notes: text("notes"),
|
|
9
9
|
createdAt: integer("created_at").notNull(),
|
|
10
10
|
updatedAt: integer("updated_at").notNull(),
|
|
11
|
-
role: text("role"
|
|
11
|
+
role: text("role", { enum: ["guardian", "contact"] })
|
|
12
|
+
.notNull()
|
|
13
|
+
.default("contact"),
|
|
12
14
|
principalId: text("principal_id"), // internal auth principal (nullable)
|
|
13
15
|
userFile: text("user_file"), // workspace-relative path to per-user persona file
|
|
14
|
-
contactType: text("contact_type"
|
|
16
|
+
contactType: text("contact_type", { enum: ["human", "assistant"] })
|
|
17
|
+
.notNull()
|
|
18
|
+
.default("human"),
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
export const contactChannels = sqliteTable(
|
|
@@ -61,6 +61,17 @@ export const conversations = sqliteTable(
|
|
|
61
61
|
* callers read this column directly.
|
|
62
62
|
*/
|
|
63
63
|
processingStartedAt: integer("processing_started_at"),
|
|
64
|
+
/**
|
|
65
|
+
* Highest stream `seq` whose content is durably persisted to this
|
|
66
|
+
* conversation's message rows. Seeded with the global high-water seq when
|
|
67
|
+
* the row is inserted and advanced on each persistence flush
|
|
68
|
+
* (`recordConversationPersistedSeq`). Returned by `/messages` as the
|
|
69
|
+
* snapshot↔stream alignment baseline so a client applies only stream
|
|
70
|
+
* events with a higher `seq`. NULL means the conversation was created
|
|
71
|
+
* before any stream activity (global seq 0) or predates this column — the
|
|
72
|
+
* client cold-starts in that case.
|
|
73
|
+
*/
|
|
74
|
+
seq: integer("seq"),
|
|
64
75
|
},
|
|
65
76
|
(table) => [
|
|
66
77
|
index("idx_conversations_updated_at").on(table.updatedAt),
|
|
@@ -170,6 +181,34 @@ export const conversationGraphMemoryState = sqliteTable(
|
|
|
170
181
|
},
|
|
171
182
|
);
|
|
172
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Append-only ledger of every compaction event for a conversation. The
|
|
186
|
+
* `conversations` row keeps only the latest compaction (`context_summary` /
|
|
187
|
+
* `context_compacted_message_count` / `context_compacted_at`) as the hot-path
|
|
188
|
+
* cache the load path reads; this table preserves the full history so a fork
|
|
189
|
+
* can inherit the most recent compaction whose event time (`compacted_at`)
|
|
190
|
+
* is at-or-before the boundary message it forks from.
|
|
191
|
+
*/
|
|
192
|
+
export const conversationCompactionEvents = sqliteTable(
|
|
193
|
+
"conversation_compaction_events",
|
|
194
|
+
{
|
|
195
|
+
id: text("id").primaryKey(),
|
|
196
|
+
conversationId: text("conversation_id")
|
|
197
|
+
.notNull()
|
|
198
|
+
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
199
|
+
compactedAt: integer("compacted_at").notNull(),
|
|
200
|
+
summary: text("summary").notNull(),
|
|
201
|
+
compactedMessageCount: integer("compacted_message_count").notNull(),
|
|
202
|
+
createdAt: integer("created_at").notNull(),
|
|
203
|
+
},
|
|
204
|
+
(table) => [
|
|
205
|
+
index("idx_compaction_events_conv_at").on(
|
|
206
|
+
table.conversationId,
|
|
207
|
+
table.compactedAt,
|
|
208
|
+
),
|
|
209
|
+
],
|
|
210
|
+
);
|
|
211
|
+
|
|
173
212
|
export const channelInboundEvents = sqliteTable("channel_inbound_events", {
|
|
174
213
|
id: text("id").primaryKey(),
|
|
175
214
|
sourceChannel: text("source_channel").notNull(),
|