@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
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* SCOPE / ALTITUDE. A full daemon-assembly integration (plugin registry → flag
|
|
5
5
|
* read → runtime assembly → provider call) is too heavy and too mock-fragile
|
|
6
6
|
* for a unit test. Instead this composes the REAL v3 live-path units with a
|
|
7
|
-
* mocked
|
|
7
|
+
* mocked select provider + stubbed dense lane and synthetic fixtures:
|
|
8
8
|
*
|
|
9
|
-
* orchestrate (
|
|
9
|
+
* orchestrate (needle ∪ dense ∪ edge → selectPool over a shared WorkingSet)
|
|
10
10
|
* → renderMemoryBlock (the rendered `<memory>` working-set block)
|
|
11
11
|
* → stripAllMemoryInjections (all-turns history strip)
|
|
12
12
|
*
|
|
@@ -16,35 +16,26 @@
|
|
|
16
16
|
* message so exactly one block exists. Driving these real units across turns
|
|
17
17
|
* exercises carry-forward, eviction, single-source, and strip-all end-to-end at
|
|
18
18
|
* the v3 layer without the daemon. The provider is stubbed (no network).
|
|
19
|
-
*
|
|
20
|
-
* Mock-leak safety: the only `mock.module` here stub the provider + logger,
|
|
21
|
-
* which are pure inputs to orchestrate and carry no real behavior siblings
|
|
22
|
-
* depend on (orchestrate.test.ts installs the identical stubs). No
|
|
23
|
-
* snapshot-real-modules dance is needed because nothing is partially stubbed.
|
|
24
19
|
*/
|
|
25
20
|
|
|
26
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
21
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
27
22
|
|
|
28
23
|
import { stripAllMemoryInjections } from "../../../../memory/graph/conversation-graph-memory.js";
|
|
24
|
+
import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
|
|
29
25
|
import type {
|
|
30
26
|
ContentBlock,
|
|
31
27
|
Message,
|
|
32
28
|
Provider,
|
|
33
29
|
ProviderResponse,
|
|
34
30
|
} from "../../../../providers/types.js";
|
|
35
|
-
import type {
|
|
36
|
-
import
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
MemoryRoutingTurn,
|
|
41
|
-
Slug,
|
|
42
|
-
} from "../types.js";
|
|
43
|
-
import liveTurns from "./fixtures/live-turns.json" with { type: "json" };
|
|
31
|
+
import type { EdgeGraph } from "../edge.js";
|
|
32
|
+
import { buildEdgeGraph } from "../edge.js";
|
|
33
|
+
import { buildSectionNeedle } from "../section-needle.js";
|
|
34
|
+
import { buildSectionIndex } from "../sections.js";
|
|
35
|
+
import type { MemoryRoutingTurn, SectionIndex, Slug } from "../types.js";
|
|
44
36
|
|
|
45
37
|
// ---------------------------------------------------------------------------
|
|
46
|
-
//
|
|
47
|
-
// selector.ts observe it at load time. Mirrors orchestrate.test.ts.
|
|
38
|
+
// Module stubs installed BEFORE the orchestrator import.
|
|
48
39
|
// ---------------------------------------------------------------------------
|
|
49
40
|
|
|
50
41
|
let providerStub: Provider | null = null;
|
|
@@ -62,60 +53,61 @@ mock.module("../../../../util/logger.js", () => ({
|
|
|
62
53
|
}),
|
|
63
54
|
}));
|
|
64
55
|
|
|
56
|
+
// The dense lane never hits in this fixture — the needle drives the pool. The
|
|
57
|
+
// stub DELEGATES to the real `denseLane` unless this file's tests are running
|
|
58
|
+
// (`denseMockActive`), so the process-global `mock.module` cannot leak fake
|
|
59
|
+
// behavior into dense.test.ts (which exercises the real lane).
|
|
60
|
+
const realDense = { ...(await import("../dense.js")) };
|
|
61
|
+
let denseMockActive = false;
|
|
62
|
+
mock.module("../dense.js", () => ({
|
|
63
|
+
...realDense,
|
|
64
|
+
denseLane: async (...args: Parameters<typeof realDense.denseLane>) =>
|
|
65
|
+
denseMockActive ? [] : realDense.denseLane(...args),
|
|
66
|
+
}));
|
|
67
|
+
|
|
65
68
|
const { orchestrate } = await import("../orchestrate.js");
|
|
66
69
|
const { WorkingSet } = await import("../working-set.js");
|
|
67
70
|
const { renderMemoryBlock } = await import("../render-injection.js");
|
|
68
71
|
|
|
69
72
|
// ---------------------------------------------------------------------------
|
|
70
|
-
//
|
|
73
|
+
// Fixtures: a tiny corpus whose section text contains a distinctive term per
|
|
74
|
+
// page so a query selects exactly that page via the needle.
|
|
71
75
|
// ---------------------------------------------------------------------------
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
description: `description for ${path}`,
|
|
90
|
-
members,
|
|
91
|
-
domain: path.split("/")[0],
|
|
92
|
-
};
|
|
77
|
+
const PAGES: Record<Slug, string> = {
|
|
78
|
+
"page-a": "lead a\n## Body\napple content for page a",
|
|
79
|
+
"page-b": "lead b\n## Body\nbanana content for page b",
|
|
80
|
+
"page-c": "lead c\n## Body\ncherry content for page c",
|
|
81
|
+
};
|
|
82
|
+
const SLUGS = Object.keys(PAGES);
|
|
83
|
+
|
|
84
|
+
function makeEntries(): PageIndexEntry[] {
|
|
85
|
+
return SLUGS.map((slug, i) => ({
|
|
86
|
+
id: i + 1,
|
|
87
|
+
slug,
|
|
88
|
+
summary: `summary of ${slug}`,
|
|
89
|
+
edges: [],
|
|
90
|
+
leaves: [],
|
|
91
|
+
modifiedAt: 0,
|
|
92
|
+
}));
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
* Synthetic tree. Sorted leaf order is [domain-a/topic-x, domain-a/topic-y],
|
|
97
|
-
* so L1 id 1 → topic-x, id 2 → topic-y.
|
|
98
|
-
*/
|
|
99
|
-
function makeTree(): LeafTree {
|
|
100
|
-
const leaves: LeafNode[] = [
|
|
101
|
-
makeLeaf("domain-a/topic-x", ["page-a", "page-b"]),
|
|
102
|
-
makeLeaf("domain-a/topic-y", ["page-c"]),
|
|
103
|
-
];
|
|
104
|
-
const byPage = new Map<Slug, LeafPath[]>();
|
|
105
|
-
for (const leaf of leaves) {
|
|
106
|
-
for (const slug of leaf.members) {
|
|
107
|
-
byPage.set(slug, [...(byPage.get(slug) ?? []), leaf.path]);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { leaves: new Map(leaves.map((n) => [n.path, n])), byPage };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const summaryOf = async (slug: Slug): Promise<string> => `summary of ${slug}`;
|
|
114
|
-
/** Deterministic per-slug body so the rendered `<memory>` block is assertable. */
|
|
95
|
+
const config = {} as never;
|
|
115
96
|
const contentOf = async (slug: Slug): Promise<string> => `body for ${slug}`;
|
|
116
97
|
|
|
117
|
-
|
|
118
|
-
|
|
98
|
+
async function buildLanes(): Promise<{
|
|
99
|
+
sectionIndex: SectionIndex;
|
|
100
|
+
needle: ReturnType<typeof buildSectionNeedle>;
|
|
101
|
+
edgeGraph: EdgeGraph;
|
|
102
|
+
}> {
|
|
103
|
+
const sectionIndex = await buildSectionIndex(SLUGS, async (s) => PAGES[s]!);
|
|
104
|
+
const needle = buildSectionNeedle(sectionIndex);
|
|
105
|
+
const edgeGraph = await buildEdgeGraph(
|
|
106
|
+
makeEntries(),
|
|
107
|
+
async (s) => PAGES[s]!, // no `links:` frontmatter → edge graph is empty
|
|
108
|
+
);
|
|
109
|
+
return { sectionIndex, needle, edgeGraph };
|
|
110
|
+
}
|
|
119
111
|
|
|
120
112
|
function makeTurn(
|
|
121
113
|
turnNumber: number,
|
|
@@ -129,71 +121,72 @@ function makeTurn(
|
|
|
129
121
|
};
|
|
130
122
|
}
|
|
131
123
|
|
|
132
|
-
function toolUseResponse(
|
|
133
|
-
name: string,
|
|
134
|
-
input: Record<string, unknown>,
|
|
135
|
-
): ProviderResponse {
|
|
124
|
+
function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
|
|
136
125
|
return {
|
|
137
126
|
model: "stub-model",
|
|
138
127
|
stopReason: "tool_use",
|
|
139
128
|
usage: { inputTokens: 0, outputTokens: 0 },
|
|
140
|
-
content: [{ type: "tool_use", id: "tu-1", name, input }],
|
|
129
|
+
content: [{ type: "tool_use", id: "tu-1", name: "select_pages", input }],
|
|
141
130
|
};
|
|
142
131
|
}
|
|
143
132
|
|
|
144
|
-
/**
|
|
145
|
-
function
|
|
133
|
+
/** Parse the numbered `<candidates>` block into an ordered slug list. */
|
|
134
|
+
function candidateSlugs(messages: Message[]): Slug[] {
|
|
146
135
|
for (const msg of messages) {
|
|
147
136
|
for (const block of msg.content) {
|
|
148
|
-
if (block.type
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
if (block.type !== "text") continue;
|
|
138
|
+
const m = /<candidates>\n([\s\S]*?)\n<\/candidates>/.exec(block.text);
|
|
139
|
+
if (!m) continue;
|
|
140
|
+
return m[1]
|
|
141
|
+
.split("\n")
|
|
142
|
+
.map((line) => /^\[\d+\] (\S+) —/.exec(line)?.[1])
|
|
143
|
+
.filter((s): s is string => !!s);
|
|
152
144
|
}
|
|
153
145
|
}
|
|
154
|
-
return
|
|
146
|
+
return [];
|
|
155
147
|
}
|
|
156
148
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
* `select_pages` call with the per-leaf selection from the fixture turn.
|
|
160
|
-
*/
|
|
161
|
-
function providerForTurn(turn: LiveTurn): Provider {
|
|
149
|
+
/** Provider that selects (and optionally pins) the pooled candidates in `keep`. */
|
|
150
|
+
function selectProvider(keep: Slug[], pin: Slug[] = []): Provider {
|
|
162
151
|
return {
|
|
163
152
|
name: "stub",
|
|
164
|
-
sendMessage: async (messages
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ids: [],
|
|
172
|
-
pinned_ids: [],
|
|
173
|
-
};
|
|
174
|
-
return toolUseResponse("select_pages", {
|
|
175
|
-
ids: sel.ids,
|
|
176
|
-
pinned_ids: sel.pinned_ids,
|
|
153
|
+
sendMessage: async (messages) => {
|
|
154
|
+
const pool = candidateSlugs(messages);
|
|
155
|
+
const ids: number[] = [];
|
|
156
|
+
const pinned_ids: number[] = [];
|
|
157
|
+
pool.forEach((slug, i) => {
|
|
158
|
+
if (keep.includes(slug)) ids.push(i + 1);
|
|
159
|
+
if (pin.includes(slug)) pinned_ids.push(i + 1);
|
|
177
160
|
});
|
|
161
|
+
return toolUseResponse({ ids, pinned_ids });
|
|
178
162
|
},
|
|
179
163
|
};
|
|
180
164
|
}
|
|
181
165
|
|
|
182
166
|
/** Run one turn through orchestrate over the shared working set + render it. */
|
|
183
167
|
async function runTurn(
|
|
184
|
-
turn: LiveTurn,
|
|
185
168
|
turnNumber: number,
|
|
186
|
-
|
|
169
|
+
query: string,
|
|
170
|
+
keep: Slug[],
|
|
171
|
+
pin: Slug[],
|
|
172
|
+
deps: {
|
|
173
|
+
lanes: Awaited<ReturnType<typeof buildLanes>>;
|
|
174
|
+
workingSet: InstanceType<typeof WorkingSet>;
|
|
175
|
+
},
|
|
187
176
|
) {
|
|
188
|
-
providerStub =
|
|
189
|
-
const result = await orchestrate(makeTurn(turnNumber,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
177
|
+
providerStub = selectProvider(keep, pin);
|
|
178
|
+
const result = await orchestrate(makeTurn(turnNumber, query), {
|
|
179
|
+
sectionIndex: deps.lanes.sectionIndex,
|
|
180
|
+
needle: deps.lanes.needle,
|
|
181
|
+
denseConfig: config,
|
|
182
|
+
edgeGraph: deps.lanes.edgeGraph,
|
|
193
183
|
workingSet: deps.workingSet,
|
|
194
|
-
pageSummary: summaryOf,
|
|
195
184
|
});
|
|
196
|
-
const block = await renderMemoryBlock(
|
|
185
|
+
const block = await renderMemoryBlock(
|
|
186
|
+
result.finalInjection,
|
|
187
|
+
result.sectionBySlug,
|
|
188
|
+
contentOf,
|
|
189
|
+
);
|
|
197
190
|
return { result, block };
|
|
198
191
|
}
|
|
199
192
|
|
|
@@ -203,9 +196,14 @@ function countMemoryBlocks(text: string): number {
|
|
|
203
196
|
}
|
|
204
197
|
|
|
205
198
|
beforeEach(() => {
|
|
199
|
+
denseMockActive = true;
|
|
206
200
|
providerStub = null;
|
|
207
201
|
});
|
|
208
202
|
|
|
203
|
+
afterAll(() => {
|
|
204
|
+
denseMockActive = false;
|
|
205
|
+
});
|
|
206
|
+
|
|
209
207
|
// ---------------------------------------------------------------------------
|
|
210
208
|
// Carry-forward: a page pinned in turn 1 appears in turn 2's injected block
|
|
211
209
|
// WITHOUT being re-selected in turn 2 (same WorkingSet reused across turns).
|
|
@@ -213,20 +211,23 @@ beforeEach(() => {
|
|
|
213
211
|
|
|
214
212
|
describe("memory-v3 live — carry-forward across turns", () => {
|
|
215
213
|
test("a page pinned in turn 1 is injected in turn 2 without re-selection", async () => {
|
|
216
|
-
const
|
|
214
|
+
const lanes = await buildLanes();
|
|
217
215
|
const workingSet = new WorkingSet();
|
|
218
216
|
|
|
219
|
-
const t1 = await runTurn(
|
|
220
|
-
|
|
217
|
+
const t1 = await runTurn(1, "apple", ["page-a"], ["page-a"], {
|
|
218
|
+
lanes,
|
|
219
|
+
workingSet,
|
|
220
|
+
});
|
|
221
221
|
expect(t1.result.currentSelections.map((s) => s.slug)).toContain("page-a");
|
|
222
222
|
expect(t1.block).toContain("body for page-a");
|
|
223
223
|
|
|
224
|
-
const t2 = await runTurn(
|
|
225
|
-
|
|
224
|
+
const t2 = await runTurn(2, "cherry", ["page-c"], [], {
|
|
225
|
+
lanes,
|
|
226
|
+
workingSet,
|
|
227
|
+
});
|
|
226
228
|
expect(t2.result.currentSelections.map((s) => s.slug)).not.toContain(
|
|
227
229
|
"page-a",
|
|
228
230
|
);
|
|
229
|
-
// …yet page-a carries forward into the injected block via the working set.
|
|
230
231
|
expect(t2.result.finalInjection).toContain("page-a");
|
|
231
232
|
expect(t2.block).toContain("body for page-a");
|
|
232
233
|
});
|
|
@@ -239,24 +240,36 @@ describe("memory-v3 live — carry-forward across turns", () => {
|
|
|
239
240
|
|
|
240
241
|
describe("memory-v3 live — eviction reflected in the injected block", () => {
|
|
241
242
|
test("a stale non-pinned page drops out; the pinned page persists", async () => {
|
|
242
|
-
const
|
|
243
|
+
const lanes = await buildLanes();
|
|
243
244
|
// Small window: a non-pinned entry unseen for >2 turns evicts. page-b is
|
|
244
245
|
// selected only in turn 1, so by turn 4 (4-1=3 > 2) it ages out; pinned
|
|
245
246
|
// page-a never evicts; page-c is re-selected every later turn.
|
|
246
247
|
const workingSet = new WorkingSet(150, 2);
|
|
247
248
|
|
|
248
|
-
const t1 = await runTurn(
|
|
249
|
+
const t1 = await runTurn(
|
|
250
|
+
1,
|
|
251
|
+
"apple banana",
|
|
252
|
+
["page-a", "page-b"],
|
|
253
|
+
["page-a"],
|
|
254
|
+
{ lanes, workingSet },
|
|
255
|
+
);
|
|
249
256
|
expect(t1.result.finalInjection).toContain("page-b");
|
|
250
257
|
expect(t1.block).toContain("body for page-b");
|
|
251
258
|
|
|
252
|
-
// Turns 2–3 keep page-b inside the window (3-1=2, not > 2).
|
|
253
|
-
await runTurn(
|
|
254
|
-
const t3 = await runTurn(
|
|
259
|
+
// Turns 2–3 keep page-b inside the window (3-1=2, not > 2); re-select page-c.
|
|
260
|
+
await runTurn(2, "cherry", ["page-c"], [], { lanes, workingSet });
|
|
261
|
+
const t3 = await runTurn(3, "cherry", ["page-c"], [], {
|
|
262
|
+
lanes,
|
|
263
|
+
workingSet,
|
|
264
|
+
});
|
|
255
265
|
expect(t3.result.finalInjection).toContain("page-b");
|
|
256
266
|
|
|
257
267
|
// Turn 4: page-b is now stale (4-1=3 > 2) and must be gone from the block;
|
|
258
268
|
// pinned page-a and freshly-selected page-c remain.
|
|
259
|
-
const t4 = await runTurn(
|
|
269
|
+
const t4 = await runTurn(4, "cherry", ["page-c"], [], {
|
|
270
|
+
lanes,
|
|
271
|
+
workingSet,
|
|
272
|
+
});
|
|
260
273
|
expect(t4.result.finalInjection).not.toContain("page-b");
|
|
261
274
|
expect(t4.block).not.toContain("body for page-b");
|
|
262
275
|
expect(t4.result.finalInjection).toContain("page-a");
|
|
@@ -266,17 +279,20 @@ describe("memory-v3 live — eviction reflected in the injected block", () => {
|
|
|
266
279
|
|
|
267
280
|
// ---------------------------------------------------------------------------
|
|
268
281
|
// Single source: orchestrate → render produces exactly one coherent `<memory>`
|
|
269
|
-
// block per turn.
|
|
270
|
-
// tests; here we assert the v3 producer never emits more than one block.)
|
|
282
|
+
// block per turn.
|
|
271
283
|
// ---------------------------------------------------------------------------
|
|
272
284
|
|
|
273
285
|
describe("memory-v3 live — single memory source", () => {
|
|
274
286
|
test("each turn renders exactly one <memory> block", async () => {
|
|
275
|
-
const
|
|
287
|
+
const lanes = await buildLanes();
|
|
276
288
|
const workingSet = new WorkingSet();
|
|
277
289
|
|
|
278
|
-
|
|
279
|
-
|
|
290
|
+
const queries = ["apple", "banana", "cherry"];
|
|
291
|
+
for (const [i, query] of queries.entries()) {
|
|
292
|
+
const { block } = await runTurn(i + 1, query, [SLUGS[i]!], [], {
|
|
293
|
+
lanes,
|
|
294
|
+
workingSet,
|
|
295
|
+
});
|
|
280
296
|
expect(countMemoryBlocks(block)).toBe(1);
|
|
281
297
|
expect(block.startsWith("<memory>\n")).toBe(true);
|
|
282
298
|
expect(block.endsWith("\n</memory>")).toBe(true);
|
|
@@ -291,10 +307,9 @@ describe("memory-v3 live — single memory source", () => {
|
|
|
291
307
|
|
|
292
308
|
describe("memory-v3 live — all-turns history strip", () => {
|
|
293
309
|
test("historical <memory> blocks strip back to byte-stable user history", async () => {
|
|
294
|
-
const
|
|
310
|
+
const lanes = await buildLanes();
|
|
295
311
|
const workingSet = new WorkingSet();
|
|
296
312
|
|
|
297
|
-
// The canonical (un-injected) user history the strip must converge to.
|
|
298
313
|
const baseHistory: Message[] = [
|
|
299
314
|
{ role: "user", content: [{ type: "text", text: "first user message" }] },
|
|
300
315
|
{ role: "assistant", content: [{ type: "text", text: "ok" }] },
|
|
@@ -306,17 +321,14 @@ describe("memory-v3 live — all-turns history strip", () => {
|
|
|
306
321
|
{ role: "user", content: [{ type: "text", text: "third user message" }] },
|
|
307
322
|
];
|
|
308
323
|
|
|
309
|
-
// Simulate prior turns having injected a v3 `<memory>` block onto EVERY
|
|
310
|
-
// historical user message (prepended, as the live path does).
|
|
311
324
|
const memBlock = (text: string): ContentBlock => ({ type: "text", text });
|
|
325
|
+
const queries = ["apple", "banana", "cherry"];
|
|
312
326
|
let injected: Message[] = baseHistory;
|
|
313
|
-
for (
|
|
314
|
-
const { block } = await runTurn(
|
|
315
|
-
|
|
327
|
+
for (const [i, query] of queries.entries()) {
|
|
328
|
+
const { block } = await runTurn(i + 1, query, [SLUGS[i]!], [], {
|
|
329
|
+
lanes,
|
|
316
330
|
workingSet,
|
|
317
331
|
});
|
|
318
|
-
// Re-inject this turn's freshly-rendered block onto each user message,
|
|
319
|
-
// after stripping the prior turn's block (what the live assembly does).
|
|
320
332
|
const stripped = stripAllMemoryInjections(injected);
|
|
321
333
|
injected = stripped.map((m) =>
|
|
322
334
|
m.role === "user"
|
|
@@ -325,8 +337,6 @@ describe("memory-v3 live — all-turns history strip", () => {
|
|
|
325
337
|
);
|
|
326
338
|
}
|
|
327
339
|
|
|
328
|
-
// After a final all-turns strip, history is byte-identical to the canonical
|
|
329
|
-
// base — every injected block was recognized and removed.
|
|
330
340
|
const finalStripped = stripAllMemoryInjections(injected);
|
|
331
341
|
expect(finalStripped).toEqual(baseHistory);
|
|
332
342
|
});
|