@vellumai/assistant 0.8.9-staging.2 → 0.8.9-staging.4
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/docs/activation-funnel-telemetry.md +310 -0
- package/openapi.yaml +16 -115
- package/package.json +1 -1
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +13 -13
- package/src/__tests__/anthropic-provider.test.ts +23 -8
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/compaction-direct.test.ts +32 -18
- package/src/__tests__/compaction-events.test.ts +2 -2
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/context-overflow-reducer.test.ts +5 -5
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +40 -0
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -0
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +44 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/history-repair-hook.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +1 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/published-app-updater.test.ts +138 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +5 -7
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- package/src/acp/resolve-agent.test.ts +0 -56
- package/src/acp/resolve-agent.ts +10 -38
- package/src/agent/loop.ts +13 -27
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/lib/__tests__/install-from-github.test.ts +232 -29
- package/src/cli/lib/__tests__/plugin-details.test.ts +28 -19
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +57 -7
- package/src/cli/lib/__tests__/search-plugins.test.ts +17 -10
- package/src/cli/lib/install-from-github.ts +258 -41
- package/src/cli/lib/plugin-details.ts +20 -13
- package/src/cli/lib/plugin-marketplace.ts +23 -5
- package/src/cli/lib/search-plugins.ts +14 -8
- package/src/config/acp-defaults.ts +3 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +4 -17
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/call-site-defaults.ts +0 -1
- package/src/config/feature-flag-registry.json +3 -18
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/llm.ts +17 -1
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +8 -1
- package/src/daemon/conversation-agent-loop.ts +87 -95
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +14 -5
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +37 -1
- package/src/daemon/conversation-surfaces.ts +141 -3
- package/src/daemon/conversation.ts +48 -13
- package/src/daemon/external-plugins-bootstrap.ts +8 -3
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/memory/__tests__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -72
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/adapters/telegram.ts +7 -1
- package/src/plugin-api/constants.ts +2 -2
- package/src/plugin-api/index.ts +2 -2
- package/src/plugin-api/types.ts +19 -5
- package/src/plugins/defaults/compaction/compact.ts +24 -15
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +4 -4
- package/src/plugins/defaults/compaction/manager-store.ts +1 -1
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +68 -12
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +12 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +19 -66
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +139 -129
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +332 -164
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +516 -293
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +51 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +66 -11
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +271 -110
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +0 -22
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +26 -62
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +3 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +415 -181
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +196 -56
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +39 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +15 -7
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +75 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +111 -78
- package/src/plugins/defaults/memory-v3-shadow/types.ts +52 -19
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +4 -1
- package/src/plugins/external-api.ts +114 -0
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +37 -2
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
- package/src/runtime/routes/acp-routes.test.ts +3 -20
- package/src/runtime/routes/app-management-routes.ts +3 -0
- package/src/runtime/routes/conversation-routes.ts +2 -0
- package/src/runtime/routes/memory-v3-routes.ts +64 -314
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/services/published-app-updater.ts +30 -8
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/list-agents.test.ts +2 -18
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +0 -10
- package/src/tools/browser/browser-execution.ts +12 -2
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +7 -0
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +0 -242
- package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +0 -39
- package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +0 -219
- package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +0 -107
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +0 -13
- package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +0 -274
- package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +0 -337
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +0 -470
- package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +0 -168
- package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +0 -192
- package/src/plugins/defaults/memory-v3-shadow/assign.ts +0 -272
- package/src/plugins/defaults/memory-v3-shadow/core.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
- package/src/plugins/defaults/memory-v3-shadow/needle.ts +0 -115
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +0 -527
- package/src/plugins/defaults/memory-v3-shadow/router.ts +0 -190
- package/src/plugins/defaults/memory-v3-shadow/selector.ts +0 -226
- package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +0 -209
- package/src/plugins/defaults/memory-v3-shadow/tree.ts +0 -174
- package/src/util/map-limit.ts +0 -27
|
@@ -155,6 +155,21 @@ registerHook("app_refresh", (_name, input, _result, { ctx }) => {
|
|
|
155
155
|
notifyAppChanged(ctx, appId, { fileChange: true });
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// app_update compiles internally (like app_refresh) but emits no events of its
|
|
159
|
+
// own, so without this hook an updated app leaves open surfaces rendering the
|
|
160
|
+
// stale dist and never re-deploys or invalidates the Library. The executor owns
|
|
161
|
+
// the compile; notifyAppChanged only refreshes surfaces and broadcasts.
|
|
162
|
+
registerHook("app_update", (_name, input, _result, { ctx }) => {
|
|
163
|
+
const appId = input.app_id as string | undefined;
|
|
164
|
+
if (!appId) return;
|
|
165
|
+
try {
|
|
166
|
+
addAppConversationId(appId, ctx.conversationId);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
log.warn({ err, appId }, "Failed to track conversation ID on app_update");
|
|
169
|
+
}
|
|
170
|
+
notifyAppChanged(ctx, appId, { fileChange: true });
|
|
171
|
+
});
|
|
172
|
+
|
|
158
173
|
registerHook("voice_config_update", (_name, input) => {
|
|
159
174
|
const setting = input.setting as string | undefined;
|
|
160
175
|
if (!setting) return;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
mock.module("../../util/logger.js", () => ({
|
|
4
|
+
getLogger: () =>
|
|
5
|
+
new Proxy({} as Record<string, unknown>, {
|
|
6
|
+
get: () => () => {},
|
|
7
|
+
}),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
isActivationSession,
|
|
12
|
+
markActivationSession,
|
|
13
|
+
} from "../activation-session-store.js";
|
|
14
|
+
import { getDb } from "../db-connection.js";
|
|
15
|
+
import { initializeDb } from "../db-init.js";
|
|
16
|
+
import { activationSessions } from "../schema.js";
|
|
17
|
+
|
|
18
|
+
initializeDb();
|
|
19
|
+
|
|
20
|
+
describe("activation-session-store", () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
getDb().delete(activationSessions).run();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("marks a conversation and reports it as an activation session", () => {
|
|
26
|
+
markActivationSession("c1");
|
|
27
|
+
expect(isActivationSession("c1")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("unmarked conversations are not activation sessions", () => {
|
|
31
|
+
expect(isActivationSession("nope")).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("marking the same conversation twice is idempotent", () => {
|
|
35
|
+
markActivationSession("c1");
|
|
36
|
+
markActivationSession("c1");
|
|
37
|
+
expect(isActivationSession("c1")).toBe(true);
|
|
38
|
+
const rows = getDb().select().from(activationSessions).all();
|
|
39
|
+
expect(rows.length).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Silence the logger.
|
|
4
|
+
mock.module("../../util/logger.js", () => ({
|
|
5
|
+
getLogger: () =>
|
|
6
|
+
new Proxy({} as Record<string, unknown>, {
|
|
7
|
+
get: () => () => {},
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
// Mutable usage-data gate, flipped per-test.
|
|
12
|
+
let collectUsageData = true;
|
|
13
|
+
mock.module("../../config/loader.js", () => ({
|
|
14
|
+
getConfig: () => ({ collectUsageData }),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { getDb } from "../db-connection.js";
|
|
18
|
+
import { initializeDb } from "../db-init.js";
|
|
19
|
+
import {
|
|
20
|
+
queryUnreportedOnboardingEvents,
|
|
21
|
+
recordActivationEvent,
|
|
22
|
+
} from "../onboarding-events-store.js";
|
|
23
|
+
import { onboardingEvents } from "../schema.js";
|
|
24
|
+
|
|
25
|
+
initializeDb();
|
|
26
|
+
|
|
27
|
+
function resetTable(): void {
|
|
28
|
+
getDb().delete(onboardingEvents).run();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("onboarding-events-store: recordActivationEvent", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
collectUsageData = true;
|
|
34
|
+
resetTable();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("persists an activation funnel event that round-trips through the query", () => {
|
|
38
|
+
const event = recordActivationEvent({
|
|
39
|
+
stepName: "activation_moment_1_complete",
|
|
40
|
+
sessionId: "conv-1",
|
|
41
|
+
});
|
|
42
|
+
expect(event).not.toBeNull();
|
|
43
|
+
|
|
44
|
+
const rows = queryUnreportedOnboardingEvents(0, undefined, 10);
|
|
45
|
+
expect(rows).toHaveLength(1);
|
|
46
|
+
const row = rows[0]!;
|
|
47
|
+
expect(row.stepName).toBe("activation_moment_1_complete");
|
|
48
|
+
expect(row.stepIndex).toBe(1);
|
|
49
|
+
expect(row.funnelVersion).toBe("activation_v1_2026_06");
|
|
50
|
+
expect(row.screen).toBe("activation_moment_1_complete");
|
|
51
|
+
expect(row.abVariant).toBe("variant-a");
|
|
52
|
+
expect(row.sessionId).toBe("conv-1");
|
|
53
|
+
expect(row.completedAt).toBe(new Date(row.createdAt).toISOString());
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("honors an explicit abVariant override", () => {
|
|
57
|
+
recordActivationEvent({
|
|
58
|
+
stepName: "activation_moment_2_complete",
|
|
59
|
+
sessionId: "conv-2",
|
|
60
|
+
abVariant: "variant-b",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const rows = queryUnreportedOnboardingEvents(0, undefined, 10);
|
|
64
|
+
expect(rows).toHaveLength(1);
|
|
65
|
+
expect(rows[0]!.abVariant).toBe("variant-b");
|
|
66
|
+
expect(rows[0]!.stepIndex).toBe(2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("returns null and writes no row when collectUsageData is disabled", () => {
|
|
70
|
+
collectUsageData = false;
|
|
71
|
+
const event = recordActivationEvent({
|
|
72
|
+
stepName: "activation_moment_1_complete",
|
|
73
|
+
sessionId: "conv-3",
|
|
74
|
+
});
|
|
75
|
+
expect(event).toBeNull();
|
|
76
|
+
|
|
77
|
+
const rows = queryUnreportedOnboardingEvents(0, undefined, 10);
|
|
78
|
+
expect(rows).toHaveLength(0);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
|
|
3
|
+
import { getLogger } from "../util/logger.js";
|
|
4
|
+
import { getDb } from "./db-connection.js";
|
|
5
|
+
import { activationSessions } from "./schema.js";
|
|
6
|
+
|
|
7
|
+
const log = getLogger("activation-session-store");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mark a conversation as an activation-rail session. Idempotent (the row's
|
|
11
|
+
* primary key is the conversation id) and best-effort: a write failure is
|
|
12
|
+
* logged and swallowed so it never blocks the turn that triggered it.
|
|
13
|
+
*/
|
|
14
|
+
export function markActivationSession(conversationId: string): void {
|
|
15
|
+
try {
|
|
16
|
+
getDb()
|
|
17
|
+
.insert(activationSessions)
|
|
18
|
+
.values({ conversationId, createdAt: Date.now() })
|
|
19
|
+
.onConflictDoNothing()
|
|
20
|
+
.run();
|
|
21
|
+
} catch (err) {
|
|
22
|
+
log.warn({ err, conversationId }, "Failed to mark activation session");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether the given conversation was started on the activation rail. Best-effort:
|
|
28
|
+
* a read failure is logged and treated as "not an activation session".
|
|
29
|
+
*/
|
|
30
|
+
export function isActivationSession(conversationId: string): boolean {
|
|
31
|
+
try {
|
|
32
|
+
const row = getDb()
|
|
33
|
+
.select({ conversationId: activationSessions.conversationId })
|
|
34
|
+
.from(activationSessions)
|
|
35
|
+
.where(eq(activationSessions.conversationId, conversationId))
|
|
36
|
+
.limit(1)
|
|
37
|
+
.get();
|
|
38
|
+
return row !== undefined;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
log.warn({ err, conversationId }, "Failed to read activation session");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/memory/db-init.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { getDb, getSqlite } from "./db-connection.js";
|
|
|
16
16
|
import { migrateToolCreatedItems } from "./graph/bootstrap.js";
|
|
17
17
|
import {
|
|
18
18
|
addCoreColumns,
|
|
19
|
+
createActivationSessionsTable,
|
|
19
20
|
createApprovalPromptTsTrackerTable,
|
|
20
21
|
createAssistantInboxTables,
|
|
21
22
|
createAuthFallbackEventsTable,
|
|
@@ -161,6 +162,7 @@ import {
|
|
|
161
162
|
migrateOAuthProvidersScopeSeparator,
|
|
162
163
|
migrateOAuthProvidersTokenAuthMethodDefault,
|
|
163
164
|
migrateOAuthProvidersTokenExchangeBodyFormat,
|
|
165
|
+
migrateOnboardingEventsFunnelColumns,
|
|
164
166
|
migrateOnboardingEventsPriorAssistants,
|
|
165
167
|
migrateProviderConnectionBaseUrlAndModels,
|
|
166
168
|
migrateProviderConnectionStatusLabel,
|
|
@@ -480,6 +482,8 @@ export function initializeDb(): void {
|
|
|
480
482
|
migrateMessagesRoleCreatedAtIndex,
|
|
481
483
|
createAuthFallbackEventsTable,
|
|
482
484
|
migrateAcpSessionHistoryCwd,
|
|
485
|
+
migrateOnboardingEventsFunnelColumns,
|
|
486
|
+
createActivationSessionsTable,
|
|
483
487
|
];
|
|
484
488
|
|
|
485
489
|
// Run each migration step, catching and logging individual failures so one
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
const TABLE = "onboarding_events";
|
|
4
|
+
const COLUMNS: Array<{ name: string; type: string }> = [
|
|
5
|
+
{ name: "session_id", type: "TEXT" },
|
|
6
|
+
{ name: "step_name", type: "TEXT" },
|
|
7
|
+
{ name: "step_index", type: "INTEGER" },
|
|
8
|
+
{ name: "completed_at", type: "TEXT" },
|
|
9
|
+
{ name: "funnel_version", type: "TEXT" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add nullable funnel-tracking columns to `onboarding_events`.
|
|
14
|
+
*
|
|
15
|
+
* The original table (#30733) recorded one row per onboarding screen but had
|
|
16
|
+
* no way to stitch those rows into an ordered, per-attempt funnel: there was
|
|
17
|
+
* no session correlator, no step ordering, and no completion timestamp. These
|
|
18
|
+
* columns let the activation-funnel reporting attribute each event to a single
|
|
19
|
+
* onboarding attempt (`session_id`), order steps within it (`step_name` /
|
|
20
|
+
* `step_index`), record when a step finished (`completed_at`), and version the
|
|
21
|
+
* funnel shape so changes to the step set don't silently corrupt historical
|
|
22
|
+
* conversion math (`funnel_version`).
|
|
23
|
+
*
|
|
24
|
+
* All columns are nullable — `NULL` for rows persisted before this migration
|
|
25
|
+
* ran.
|
|
26
|
+
*
|
|
27
|
+
* Idempotent — re-running is a no-op once the columns exist. Pure DDL with a
|
|
28
|
+
* PRAGMA guard, no registry entry needed (matches the 252 / 261 / 264 / 265 /
|
|
29
|
+
* 267 pattern).
|
|
30
|
+
*/
|
|
31
|
+
export function migrateOnboardingEventsFunnelColumns(
|
|
32
|
+
database: DrizzleDb,
|
|
33
|
+
): void {
|
|
34
|
+
const raw = getSqliteFrom(database);
|
|
35
|
+
|
|
36
|
+
const columns = raw.query(`PRAGMA table_info(${TABLE})`).all() as Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
}>;
|
|
39
|
+
const columnNames = new Set(columns.map((c) => c.name));
|
|
40
|
+
|
|
41
|
+
for (const { name, type } of COLUMNS) {
|
|
42
|
+
if (!columnNames.has(name)) {
|
|
43
|
+
raw.exec(`ALTER TABLE ${TABLE} ADD COLUMN ${name} ${type}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create the activation_sessions table. One row per conversation that was
|
|
5
|
+
* started on the activation-rail bootstrap template, used by the activation
|
|
6
|
+
* funnel telemetry to scope events to activation conversations.
|
|
7
|
+
*/
|
|
8
|
+
export function createActivationSessionsTable(database: DrizzleDb): void {
|
|
9
|
+
database.run(/*sql*/ `
|
|
10
|
+
CREATE TABLE IF NOT EXISTS activation_sessions (
|
|
11
|
+
conversation_id TEXT PRIMARY KEY,
|
|
12
|
+
created_at INTEGER NOT NULL
|
|
13
|
+
)
|
|
14
|
+
`);
|
|
15
|
+
}
|
|
@@ -260,6 +260,8 @@ export { migrateMessagesRoleCreatedAtIndex } from "./270-messages-role-created-a
|
|
|
260
260
|
export { migrateScheduleSourceConversation } from "./270-schedule-source-conversation.js";
|
|
261
261
|
export { createAuthFallbackEventsTable } from "./271-create-auth-fallback-events.js";
|
|
262
262
|
export { migrateAcpSessionHistoryCwd } from "./272-acp-session-history-cwd.js";
|
|
263
|
+
export { migrateOnboardingEventsFunnelColumns } from "./273-onboarding-events-funnel-columns.js";
|
|
264
|
+
export { createActivationSessionsTable } from "./274-create-activation-sessions.js";
|
|
263
265
|
export {
|
|
264
266
|
MIGRATION_REGISTRY,
|
|
265
267
|
type MigrationRegistryEntry,
|
|
@@ -2,6 +2,12 @@ import { and, asc, eq, gt, or } from "drizzle-orm";
|
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
4
|
import { getConfig } from "../config/loader.js";
|
|
5
|
+
import {
|
|
6
|
+
ACTIVATION_AB_VARIANT,
|
|
7
|
+
ACTIVATION_FUNNEL_VERSION,
|
|
8
|
+
activationStepIndex,
|
|
9
|
+
type ActivationStepName,
|
|
10
|
+
} from "../telemetry/activation-funnel.js";
|
|
5
11
|
import { getDb } from "./db-connection.js";
|
|
6
12
|
import { onboardingEvents } from "./schema.js";
|
|
7
13
|
|
|
@@ -16,6 +22,11 @@ export interface OnboardingEvent {
|
|
|
16
22
|
googleScopesJson: string | null;
|
|
17
23
|
priorAssistantsJson: string | null;
|
|
18
24
|
abVariant: string | null;
|
|
25
|
+
sessionId: string | null;
|
|
26
|
+
stepName: string | null;
|
|
27
|
+
stepIndex: number | null;
|
|
28
|
+
completedAt: string | null;
|
|
29
|
+
funnelVersion: string | null;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
export interface RecordOnboardingEventParams {
|
|
@@ -27,6 +38,17 @@ export interface RecordOnboardingEventParams {
|
|
|
27
38
|
googleScopes?: string[];
|
|
28
39
|
priorAssistants?: string[];
|
|
29
40
|
abVariant?: string;
|
|
41
|
+
sessionId?: string | null;
|
|
42
|
+
stepName?: string | null;
|
|
43
|
+
stepIndex?: number | null;
|
|
44
|
+
completedAt?: string | null;
|
|
45
|
+
funnelVersion?: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Insert a fully-built event row. Shared by all record* entry points. */
|
|
49
|
+
function insertOnboardingEvent(event: OnboardingEvent): OnboardingEvent {
|
|
50
|
+
getDb().insert(onboardingEvents).values(event).run();
|
|
51
|
+
return event;
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
/**
|
|
@@ -37,8 +59,7 @@ export function recordOnboardingEvent(
|
|
|
37
59
|
params: RecordOnboardingEventParams,
|
|
38
60
|
): OnboardingEvent | null {
|
|
39
61
|
if (!getConfig().collectUsageData) return null;
|
|
40
|
-
|
|
41
|
-
const event: OnboardingEvent = {
|
|
62
|
+
return insertOnboardingEvent({
|
|
42
63
|
id: uuid(),
|
|
43
64
|
createdAt: Date.now(),
|
|
44
65
|
screen: params.screen,
|
|
@@ -53,22 +74,44 @@ export function recordOnboardingEvent(
|
|
|
53
74
|
? JSON.stringify(params.priorAssistants)
|
|
54
75
|
: null,
|
|
55
76
|
abVariant: params.abVariant ?? null,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
sessionId: params.sessionId ?? null,
|
|
78
|
+
stepName: params.stepName ?? null,
|
|
79
|
+
stepIndex: params.stepIndex ?? null,
|
|
80
|
+
completedAt: params.completedAt ?? null,
|
|
81
|
+
funnelVersion: params.funnelVersion ?? null,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Record an activation-funnel milestone event. Reuses the onboarding telemetry
|
|
87
|
+
* substrate (`screen` carries the step name to satisfy the NOT NULL column).
|
|
88
|
+
* Returns null when usage data collection is disabled.
|
|
89
|
+
*/
|
|
90
|
+
export function recordActivationEvent(params: {
|
|
91
|
+
stepName: ActivationStepName;
|
|
92
|
+
sessionId: string;
|
|
93
|
+
userId?: string | null;
|
|
94
|
+
abVariant?: string;
|
|
95
|
+
}): OnboardingEvent | null {
|
|
96
|
+
if (!getConfig().collectUsageData) return null;
|
|
97
|
+
const createdAt = Date.now();
|
|
98
|
+
return insertOnboardingEvent({
|
|
99
|
+
id: uuid(),
|
|
100
|
+
createdAt,
|
|
101
|
+
screen: params.stepName,
|
|
102
|
+
toolsJson: null,
|
|
103
|
+
tasksJson: null,
|
|
104
|
+
tone: null,
|
|
105
|
+
googleConnected: null,
|
|
106
|
+
googleScopesJson: null,
|
|
107
|
+
priorAssistantsJson: null,
|
|
108
|
+
abVariant: params.abVariant ?? ACTIVATION_AB_VARIANT,
|
|
109
|
+
sessionId: params.sessionId,
|
|
110
|
+
stepName: params.stepName,
|
|
111
|
+
stepIndex: activationStepIndex(params.stepName),
|
|
112
|
+
completedAt: new Date(createdAt).toISOString(),
|
|
113
|
+
funnelVersion: ACTIVATION_FUNNEL_VERSION,
|
|
114
|
+
});
|
|
72
115
|
}
|
|
73
116
|
|
|
74
117
|
/**
|
|
@@ -93,6 +136,11 @@ export function queryUnreportedOnboardingEvents(
|
|
|
93
136
|
googleScopesJson: onboardingEvents.googleScopesJson,
|
|
94
137
|
priorAssistantsJson: onboardingEvents.priorAssistantsJson,
|
|
95
138
|
abVariant: onboardingEvents.abVariant,
|
|
139
|
+
sessionId: onboardingEvents.sessionId,
|
|
140
|
+
stepName: onboardingEvents.stepName,
|
|
141
|
+
stepIndex: onboardingEvents.stepIndex,
|
|
142
|
+
completedAt: onboardingEvents.completedAt,
|
|
143
|
+
funnelVersion: onboardingEvents.funnelVersion,
|
|
96
144
|
})
|
|
97
145
|
.from(onboardingEvents)
|
|
98
146
|
.where(
|
|
@@ -277,6 +277,11 @@ export const onboardingEvents = sqliteTable("onboarding_events", {
|
|
|
277
277
|
googleScopesJson: text("google_scopes_json"),
|
|
278
278
|
priorAssistantsJson: text("prior_assistants_json"),
|
|
279
279
|
abVariant: text("ab_variant"),
|
|
280
|
+
sessionId: text("session_id"),
|
|
281
|
+
stepName: text("step_name"),
|
|
282
|
+
stepIndex: integer("step_index"),
|
|
283
|
+
completedAt: text("completed_at"),
|
|
284
|
+
funnelVersion: text("funnel_version"),
|
|
280
285
|
});
|
|
281
286
|
|
|
282
287
|
// Aggregated legacy-loopback auth-fallback counts forwarded by the gateway.
|
|
@@ -294,6 +299,14 @@ export const authFallbackEvents = sqliteTable("auth_fallback_events", {
|
|
|
294
299
|
windowEnd: integer("window_end").notNull(),
|
|
295
300
|
});
|
|
296
301
|
|
|
302
|
+
// One row per conversation started on the activation-rail bootstrap template.
|
|
303
|
+
// Lets the activation funnel telemetry scope its events to activation
|
|
304
|
+
// conversations without inspecting the bootstrap template at emit time.
|
|
305
|
+
export const activationSessions = sqliteTable("activation_sessions", {
|
|
306
|
+
conversationId: text("conversation_id").primaryKey(),
|
|
307
|
+
createdAt: integer("created_at").notNull(),
|
|
308
|
+
});
|
|
309
|
+
|
|
297
310
|
export const traceEvents = sqliteTable(
|
|
298
311
|
"trace_events",
|
|
299
312
|
{
|
|
@@ -105,43 +105,15 @@ mock.module("../../jobs-store.js", () => ({
|
|
|
105
105
|
isMemoryEnabled: () => true,
|
|
106
106
|
}));
|
|
107
107
|
|
|
108
|
-
// ── v3
|
|
108
|
+
// ── v3 follow-up flag mock ──────────────────────────────────────────
|
|
109
109
|
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// mocks let each test (a) toggle the flag, (b) decide what health block the
|
|
113
|
-
// renderer produces, and (c) force the health computation to throw — without
|
|
114
|
-
// materializing a real v3 data dir. The tree/core/page-index loaders are
|
|
115
|
-
// stubbed to inert values; `computeV3Health` is a pass-through whose result is
|
|
116
|
-
// fed to the toggleable `renderV3Health`.
|
|
110
|
+
// A v3 flag being on appends `memory_v3_maintain` to the post-consolidation
|
|
111
|
+
// follow-up fan-out. This mock lets each test toggle the flag.
|
|
117
112
|
let v3FlagOn = false;
|
|
118
113
|
mock.module("../../../config/assistant-feature-flags.js", () => ({
|
|
119
114
|
isAssistantFeatureFlagEnabled: () => v3FlagOn,
|
|
120
115
|
}));
|
|
121
116
|
|
|
122
|
-
let renderedHealth = "";
|
|
123
|
-
let computeThrows = false;
|
|
124
|
-
mock.module("../../../plugins/defaults/memory-v3-shadow/health.js", () => ({
|
|
125
|
-
computeV3Health: () => {
|
|
126
|
-
if (computeThrows) throw new Error("simulated health compute failure");
|
|
127
|
-
return {};
|
|
128
|
-
},
|
|
129
|
-
renderV3Health: () => renderedHealth,
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
mock.module("../../../plugins/defaults/memory-v3-shadow/tree.js", () => ({
|
|
133
|
-
loadLeafTree: async () => ({ leaves: new Map(), byPage: new Map() }),
|
|
134
|
-
resolveDataDir: () => "/tmp/v3-data-stub",
|
|
135
|
-
}));
|
|
136
|
-
|
|
137
|
-
mock.module("../../../plugins/defaults/memory-v3-shadow/core.js", () => ({
|
|
138
|
-
loadCore: async () => new Set<string>(),
|
|
139
|
-
}));
|
|
140
|
-
|
|
141
|
-
mock.module("../../v2/page-index.js", () => ({
|
|
142
|
-
getPageIndex: async () => ({ entries: [] }),
|
|
143
|
-
}));
|
|
144
|
-
|
|
145
117
|
// ── Workspace pin ───────────────────────────────────────────────────
|
|
146
118
|
let tmpWorkspace: string;
|
|
147
119
|
let previousWorkspaceEnv: string | undefined;
|
|
@@ -211,10 +183,8 @@ beforeEach(() => {
|
|
|
211
183
|
enqueuedJobs.length = 0;
|
|
212
184
|
nextJobIdCounter = 0;
|
|
213
185
|
|
|
214
|
-
// v3
|
|
186
|
+
// v3 follow-up flag default: off.
|
|
215
187
|
v3FlagOn = false;
|
|
216
|
-
renderedHealth = "";
|
|
217
|
-
computeThrows = false;
|
|
218
188
|
});
|
|
219
189
|
|
|
220
190
|
// ---------------------------------------------------------------------------
|
|
@@ -409,69 +379,6 @@ describe("memoryV2ConsolidateJob — non-empty buffer", () => {
|
|
|
409
379
|
});
|
|
410
380
|
});
|
|
411
381
|
|
|
412
|
-
describe("memoryV2ConsolidateJob — v3 health injection", () => {
|
|
413
|
-
beforeEach(() => {
|
|
414
|
-
writeFileSync(
|
|
415
|
-
bufferPath(),
|
|
416
|
-
"- [Apr 27, 9:00 AM] Alice prefers VS Code over Vim.\n",
|
|
417
|
-
);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test("prepends the health block when a v3 flag is on and the report is actionable", async () => {
|
|
421
|
-
v3FlagOn = true;
|
|
422
|
-
renderedHealth = "memory-v3 health:\n- 2 unassigned slug(s): a, b";
|
|
423
|
-
|
|
424
|
-
const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
|
|
425
|
-
|
|
426
|
-
expect(result.kind).toBe("invoked");
|
|
427
|
-
const prompt = runnerLastArgs?.prompt as string;
|
|
428
|
-
// The health block is PREPENDED, separated by a blank line, with the
|
|
429
|
-
// base consolidation prompt still present underneath it.
|
|
430
|
-
expect(prompt.startsWith(`${renderedHealth}\n\n`)).toBe(true);
|
|
431
|
-
expect(prompt).toContain("memory consolidation");
|
|
432
|
-
expect(prompt).not.toContain(CUTOFF_PLACEHOLDER);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
test("leaves the prompt unchanged when the report is all-green (empty render)", async () => {
|
|
436
|
-
v3FlagOn = true;
|
|
437
|
-
renderedHealth = "";
|
|
438
|
-
|
|
439
|
-
const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
|
|
440
|
-
|
|
441
|
-
expect(result.kind).toBe("invoked");
|
|
442
|
-
const prompt = runnerLastArgs?.prompt as string;
|
|
443
|
-
expect(prompt).not.toContain("memory-v3 health:");
|
|
444
|
-
// Prompt is exactly the resolved consolidation body.
|
|
445
|
-
expect(prompt).toContain("memory consolidation");
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
test("leaves the prompt unchanged when v3 flags are off even if a block would render", async () => {
|
|
449
|
-
v3FlagOn = false;
|
|
450
|
-
renderedHealth = "memory-v3 health:\n- 1 unassigned slug(s): a";
|
|
451
|
-
|
|
452
|
-
const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
|
|
453
|
-
|
|
454
|
-
expect(result.kind).toBe("invoked");
|
|
455
|
-
const prompt = runnerLastArgs?.prompt as string;
|
|
456
|
-
expect(prompt).not.toContain("memory-v3 health:");
|
|
457
|
-
expect(prompt).toContain("memory consolidation");
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
test("falls back to the base prompt when health computation throws", async () => {
|
|
461
|
-
v3FlagOn = true;
|
|
462
|
-
computeThrows = true;
|
|
463
|
-
renderedHealth = "memory-v3 health:\n- should not appear";
|
|
464
|
-
|
|
465
|
-
const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
|
|
466
|
-
|
|
467
|
-
// A health-compute failure must NEVER break consolidation.
|
|
468
|
-
expect(result.kind).toBe("invoked");
|
|
469
|
-
const prompt = runnerLastArgs?.prompt as string;
|
|
470
|
-
expect(prompt).not.toContain("memory-v3 health:");
|
|
471
|
-
expect(prompt).toContain("memory consolidation");
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
|
|
475
382
|
describe("memoryV2ConsolidateJob — concurrent invocations", () => {
|
|
476
383
|
beforeEach(() => {
|
|
477
384
|
writeFileSync(bufferPath(), "- [Apr 27, 9:00 AM] Alice prefers VS Code.\n");
|
|
@@ -186,6 +186,28 @@ describe("writePage + readPage round-trip", () => {
|
|
|
186
186
|
expect(read!.body).toBe(page.body);
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
test("round-trips authored links: frontmatter (memory-v3 edge lane)", async () => {
|
|
190
|
+
const page = makePage({
|
|
191
|
+
frontmatter: {
|
|
192
|
+
edges: [],
|
|
193
|
+
ref_files: [],
|
|
194
|
+
ref_urls: [],
|
|
195
|
+
links: ["bob-handoff — the partner this hands off to"],
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
await writePage(workspaceDir, page);
|
|
199
|
+
|
|
200
|
+
// A page using the optional `links:` frontmatter must NOT be rejected by the
|
|
201
|
+
// strict schema (it would otherwise be dropped from memory-v3 entirely), and
|
|
202
|
+
// the curated links must survive the render → read round-trip so the edge
|
|
203
|
+
// graph can parse them back from the rendered frontmatter.
|
|
204
|
+
const read = await readPage(workspaceDir, page.slug);
|
|
205
|
+
expect(read).not.toBeNull();
|
|
206
|
+
expect(read!.frontmatter.links).toEqual([
|
|
207
|
+
"bob-handoff — the partner this hands off to",
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
210
|
+
|
|
189
211
|
test("renders frontmatter at the top with --- delimiters", async () => {
|
|
190
212
|
const page = makePage();
|
|
191
213
|
await writePage(workspaceDir, page);
|