@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wire contract for the memory v3 selection set surfaced in the inspector's
|
|
3
3
|
* Memory tab. Mirrors the return value of `getMemoryV3SelectionForInspector`
|
|
4
|
-
* in `assistant/src/memory
|
|
4
|
+
* in `assistant/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts`.
|
|
5
5
|
*
|
|
6
6
|
* Canonical wire-contract source. Assistant code imports the types directly
|
|
7
7
|
* from this file via relative paths; external consumers (web client, gateway,
|
|
@@ -12,19 +12,27 @@
|
|
|
12
12
|
* `live` / `shadow` reflect the CURRENT `memory-v3-live` / `memory-v3-shadow`
|
|
13
13
|
* flag state, not the per-turn history (the selection rows don't record which
|
|
14
14
|
* mode was active when they were written). When `live` is true the rendered
|
|
15
|
-
* `injectedText`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* `injectedText` reflects the live selection; when only `shadow` is true it is
|
|
16
|
+
* the block that WOULD have been injected.
|
|
17
|
+
*
|
|
18
|
+
* `injectedText` is APPROXIMATE, not a verbatim record. It is re-rendered from
|
|
19
|
+
* the persisted selection rows, which store slug/source/pinned but NOT the
|
|
20
|
+
* matched-section identity. A page that live injection sent as a single matched
|
|
21
|
+
* section is re-rendered here as its FULL page (the section ordinal isn't
|
|
22
|
+
* persisted), so the text can differ from what was actually sent to the model.
|
|
23
|
+
* The selected slug set is exact — only the rendered text is full-page
|
|
24
|
+
* approximate. Persisting the per-turn injected section is a documented
|
|
25
|
+
* follow-up.
|
|
19
26
|
*/
|
|
20
27
|
|
|
21
28
|
import { z } from "zod";
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
31
|
* One selected page in the v3 set. `source` is the lane that surfaced it —
|
|
25
|
-
* the daemon emits `
|
|
26
|
-
* schema stays a permissive string so a new lane label
|
|
27
|
-
* on the FE. `pinned` marks a slug carried
|
|
32
|
+
* the daemon emits `needle`, `dense`, `edge`, or `carry-forward` — but the
|
|
33
|
+
* schema stays a permissive string so a new lane label (or a historical
|
|
34
|
+
* pre-lane row) doesn't break parsing on the FE. `pinned` marks a slug carried
|
|
35
|
+
* forward across turns.
|
|
28
36
|
*/
|
|
29
37
|
export const MemoryV3SelectionRowSchema = z.object({
|
|
30
38
|
slug: z.string(),
|
|
@@ -36,8 +44,9 @@ export type MemoryV3SelectionRow = z.infer<typeof MemoryV3SelectionRowSchema>;
|
|
|
36
44
|
|
|
37
45
|
/**
|
|
38
46
|
* Memory v3 selection log shape. `injectedText` is the rendered
|
|
39
|
-
* `<memory>…</memory>` block for the selection
|
|
40
|
-
*
|
|
47
|
+
* `<memory>…</memory>` block for the selection — APPROXIMATE: re-rendered from
|
|
48
|
+
* the persisted rows, with full pages where live injection used a matched
|
|
49
|
+
* section (the section identity isn't persisted). See the file header.
|
|
41
50
|
*/
|
|
42
51
|
export const MemoryV3SelectionLogSchema = z.object({
|
|
43
52
|
turn: z.number(),
|
|
@@ -1,245 +1,226 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the `memory v3` CLI
|
|
2
|
+
* Tests for the `assistant memory v3` CLI subgroup (section-lane model).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* concept page's `leaves:` frontmatter (via the page index) and union it over
|
|
12
|
-
* `assignments.json`, matching the consolidation-injected health block. The
|
|
13
|
-
* fixture below writes concept pages with `leaves:` frontmatter as the
|
|
14
|
-
* authoritative membership and keeps a *stale* `assignments.json` to prove the
|
|
15
|
-
* handlers read the frontmatter, not the stale assignments. The seeded
|
|
16
|
-
* skill/CLI-command catalogs are mocked empty so `allSlugs` (derived from the
|
|
17
|
-
* page index) is deterministic.
|
|
4
|
+
* Validates:
|
|
5
|
+
* - Subcommand registration (rebuild-index, backfill-sections) under
|
|
6
|
+
* `memory v3`.
|
|
7
|
+
* - Each subcommand maps to the right `cliIpcCall` method.
|
|
8
|
+
* - `backfill-sections` passes a long IPC timeout (the one-time full-corpus
|
|
9
|
+
* embed easily outlasts the default 60s), while `rebuild-index` does not.
|
|
10
|
+
* - IPC error paths return a non-zero exit code without throwing.
|
|
18
11
|
*/
|
|
19
12
|
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
13
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { Command } from "commander";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Mock state
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/** The last `cliIpcCall` invocation captured for assertions. */
|
|
22
|
+
let lastIpcCall: {
|
|
23
|
+
method: string;
|
|
24
|
+
|
|
25
|
+
params?: any;
|
|
26
|
+
options?: { timeoutMs?: number; signal?: AbortSignal };
|
|
27
|
+
} | null = null;
|
|
28
|
+
|
|
29
|
+
/** The result that cliIpcCall will return. */
|
|
30
|
+
let mockIpcResult: {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
result?: unknown;
|
|
33
|
+
error?: string;
|
|
34
|
+
} = { ok: true, result: { articles: 3, sections: 12, failures: 0 } };
|
|
35
|
+
|
|
36
|
+
/** Captured log output for assertion. */
|
|
37
|
+
let logOutput: string[] = [];
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Mocks
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
mock.module("../../../ipc/cli-client.js", () => ({
|
|
44
|
+
cliIpcCall: async (
|
|
45
|
+
method: string,
|
|
46
|
+
params?: any,
|
|
47
|
+
options?: { timeoutMs?: number; signal?: AbortSignal },
|
|
48
|
+
) => {
|
|
49
|
+
lastIpcCall = { method, params, options };
|
|
50
|
+
return mockIpcResult;
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const capture = (...args: unknown[]) => {
|
|
55
|
+
logOutput.push(args.map(String).join(" "));
|
|
56
|
+
};
|
|
57
|
+
const fakeLogger = {
|
|
58
|
+
info: capture,
|
|
59
|
+
warn: capture,
|
|
60
|
+
error: capture,
|
|
61
|
+
debug: () => {},
|
|
62
|
+
};
|
|
63
|
+
mock.module("../../../util/logger.js", () => ({
|
|
64
|
+
getLogger: () => fakeLogger,
|
|
65
|
+
getCliLogger: () => fakeLogger,
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Import modules under test (after mocks)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
const { registerMemoryV3Command } = await import("../memory-v3.js");
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Test helpers
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
function buildProgram(): Command {
|
|
79
|
+
const program = new Command();
|
|
80
|
+
program.exitOverride();
|
|
81
|
+
program.configureOutput({
|
|
82
|
+
writeErr: () => {},
|
|
83
|
+
writeOut: () => {},
|
|
84
|
+
});
|
|
85
|
+
registerMemoryV3Command(program);
|
|
86
|
+
return program;
|
|
60
87
|
}
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
async function runCommand(
|
|
90
|
+
args: string[],
|
|
91
|
+
): Promise<{ stdout: string; exitCode: number }> {
|
|
92
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
93
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
94
|
+
const stdoutChunks: string[] = [];
|
|
95
|
+
|
|
96
|
+
process.stdout.write = ((chunk: unknown) => {
|
|
97
|
+
stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
98
|
+
return true;
|
|
99
|
+
}) as typeof process.stdout.write;
|
|
100
|
+
|
|
101
|
+
process.stderr.write = (() => true) as typeof process.stderr.write;
|
|
102
|
+
|
|
103
|
+
process.exitCode = 0;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const program = buildProgram();
|
|
107
|
+
await program.parseAsync(["node", "assistant", ...args]);
|
|
108
|
+
} catch {
|
|
109
|
+
if (process.exitCode === 0) process.exitCode = 1;
|
|
110
|
+
} finally {
|
|
111
|
+
process.stdout.write = originalStdoutWrite;
|
|
112
|
+
process.stderr.write = originalStderrWrite;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const exitCode = process.exitCode ?? 0;
|
|
116
|
+
process.exitCode = 0;
|
|
117
|
+
|
|
118
|
+
return { exitCode, stdout: stdoutChunks.join("") };
|
|
76
119
|
}
|
|
77
120
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
await mkdir(dataDir, { recursive: true });
|
|
82
|
-
|
|
83
|
-
// Keep the page index deterministic regardless of the on-disk seeded
|
|
84
|
-
// catalogs, and clear any module-cached index from a prior test.
|
|
85
|
-
spyOn(skillStore, "listSkillEntries").mockReturnValue([]);
|
|
86
|
-
spyOn(cliCommandStore, "listCliCommandEntries").mockReturnValue([]);
|
|
87
|
-
invalidatePageIndex();
|
|
88
|
-
|
|
89
|
-
// A small generic tree: two leaves under domain-a, one under domain-b.
|
|
90
|
-
await writeLeaf("domain-a/topic-x", {
|
|
91
|
-
path: "domain-a/topic-x",
|
|
92
|
-
in_core: true,
|
|
93
|
-
id: "leaf-x",
|
|
94
|
-
});
|
|
95
|
-
await writeLeaf("domain-a/topic-y", {
|
|
96
|
-
path: "domain-a/topic-y",
|
|
97
|
-
in_core: false,
|
|
98
|
-
id: "leaf-y",
|
|
99
|
-
});
|
|
100
|
-
await writeLeaf("domain-b/topic-z", {
|
|
101
|
-
path: "domain-b/topic-z",
|
|
102
|
-
in_core: false,
|
|
103
|
-
id: "leaf-z",
|
|
104
|
-
});
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Setup
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// when it is non-empty, so for these slugs the frontmatter wins and
|
|
115
|
-
// assignments.json contributes no membership — proving the handlers read the
|
|
116
|
-
// page index, not these stale assignments (the regression Defect A guards
|
|
117
|
-
// against). We deliberately avoid a slug that exists ONLY in assignments.json:
|
|
118
|
-
// such a slug has no frontmatter, falls back to its assignments entry, and
|
|
119
|
-
// would inflate the cost preview below with phantom membership.
|
|
120
|
-
await writeFile(
|
|
121
|
-
join(dataDir, "assignments.json"),
|
|
122
|
-
JSON.stringify({
|
|
123
|
-
"page-1": ["domain-a/bogus-leaf"],
|
|
124
|
-
"page-2": ["domain-b/topic-z"],
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
127
|
-
await writeFile(
|
|
128
|
-
join(dataDir, "core.json"),
|
|
129
|
-
JSON.stringify({ alwaysOn: ["domain-a/topic-x"] }),
|
|
130
|
-
);
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
lastIpcCall = null;
|
|
127
|
+
mockIpcResult = {
|
|
128
|
+
ok: true,
|
|
129
|
+
result: { articles: 3, sections: 12, failures: 0 },
|
|
130
|
+
};
|
|
131
|
+
logOutput = [];
|
|
132
|
+
process.exitCode = 0;
|
|
131
133
|
});
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Subcommand registration
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
describe("subcommand registration", () => {
|
|
140
|
+
test("registers v3 under memory with the expected subcommands", () => {
|
|
141
|
+
const program = buildProgram();
|
|
142
|
+
const memory = program.commands.find((c) => c.name() === "memory");
|
|
143
|
+
expect(memory).toBeDefined();
|
|
144
|
+
const v3 = memory!.commands.find((c) => c.name() === "v3");
|
|
145
|
+
expect(v3).toBeDefined();
|
|
146
|
+
const subcommandNames = v3!.commands.map((c) => c.name()).sort();
|
|
147
|
+
expect(subcommandNames).toEqual(["backfill-sections", "rebuild-index"]);
|
|
148
|
+
});
|
|
136
149
|
});
|
|
137
150
|
|
|
138
151
|
// ---------------------------------------------------------------------------
|
|
139
|
-
//
|
|
152
|
+
// rebuild-index
|
|
140
153
|
// ---------------------------------------------------------------------------
|
|
141
154
|
|
|
142
|
-
describe("
|
|
143
|
-
test("
|
|
144
|
-
|
|
145
|
-
{ add: ["domain-a/does-not-exist"], write: true },
|
|
146
|
-
{ dataDir, workspaceDir },
|
|
147
|
-
);
|
|
148
|
-
await expect(call).rejects.toThrow(/domain-a\/does-not-exist/);
|
|
149
|
-
await expect(
|
|
150
|
-
handleMemoryV3SetCore(
|
|
151
|
-
{ add: ["domain-a/does-not-exist"], write: true },
|
|
152
|
-
{ dataDir, workspaceDir },
|
|
153
|
-
),
|
|
154
|
-
).rejects.toBeInstanceOf(UnknownLeafError);
|
|
155
|
+
describe("memory v3 rebuild-index", () => {
|
|
156
|
+
test("sends memory_v3_rebuild_index with the default (no) timeout", async () => {
|
|
157
|
+
mockIpcResult = { ok: true, result: { invalidated: true } };
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
const core = JSON.parse(await readFile(join(dataDir, "core.json"), "utf8"));
|
|
158
|
-
expect(core.alwaysOn).toEqual(["domain-a/topic-x"]);
|
|
159
|
-
});
|
|
159
|
+
const { exitCode } = await runCommand(["memory", "v3", "rebuild-index"]);
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
// topic-z), topic-x/topic-y would have no members and this would be 0.
|
|
167
|
-
const result = await handleMemoryV3SetCore(
|
|
168
|
-
{ add: ["domain-a/topic-y"] },
|
|
169
|
-
{ dataDir, workspaceDir },
|
|
170
|
-
);
|
|
171
|
-
expect(result.written).toBe(false);
|
|
172
|
-
expect(result.nextCore).toEqual(["domain-a/topic-x", "domain-a/topic-y"]);
|
|
173
|
-
expect(result.alwaysOnPageCount).toBe(2);
|
|
174
|
-
|
|
175
|
-
// No write happened — core.json still holds only the original entry.
|
|
176
|
-
const core = JSON.parse(await readFile(join(dataDir, "core.json"), "utf8"));
|
|
177
|
-
expect(core.alwaysOn).toEqual(["domain-a/topic-x"]);
|
|
161
|
+
expect(exitCode).toBe(0);
|
|
162
|
+
expect(lastIpcCall!.method).toBe("memory_v3_rebuild_index");
|
|
163
|
+
expect(lastIpcCall!.params!.body).toEqual({});
|
|
164
|
+
// rebuild-index is fast; it relies on the default call timeout.
|
|
165
|
+
expect(lastIpcCall!.options).toBeUndefined();
|
|
178
166
|
});
|
|
179
167
|
|
|
180
|
-
test("
|
|
181
|
-
|
|
182
|
-
{ add: ["domain-b/topic-z"], write: true },
|
|
183
|
-
{ dataDir, workspaceDir },
|
|
184
|
-
);
|
|
185
|
-
expect(result.written).toBe(true);
|
|
186
|
-
expect(result.nextCore).toEqual(["domain-a/topic-x", "domain-b/topic-z"]);
|
|
168
|
+
test("exits with code 1 on IPC failure", async () => {
|
|
169
|
+
mockIpcResult = { ok: false, error: "Daemon down" };
|
|
187
170
|
|
|
188
|
-
const
|
|
189
|
-
expect(core.alwaysOn).toEqual(["domain-a/topic-x", "domain-b/topic-z"]);
|
|
190
|
-
});
|
|
171
|
+
const { exitCode } = await runCommand(["memory", "v3", "rebuild-index"]);
|
|
191
172
|
|
|
192
|
-
|
|
193
|
-
const result = await handleMemoryV3SetCore(
|
|
194
|
-
{ remove: ["domain-a/topic-y"], write: true },
|
|
195
|
-
{ dataDir, workspaceDir },
|
|
196
|
-
);
|
|
197
|
-
// topic-y was never in core; result is the original core unchanged.
|
|
198
|
-
expect(result.nextCore).toEqual(["domain-a/topic-x"]);
|
|
173
|
+
expect(exitCode).toBe(1);
|
|
199
174
|
});
|
|
200
175
|
});
|
|
201
176
|
|
|
202
177
|
// ---------------------------------------------------------------------------
|
|
203
|
-
//
|
|
178
|
+
// backfill-sections
|
|
204
179
|
// ---------------------------------------------------------------------------
|
|
205
180
|
|
|
206
|
-
describe("
|
|
207
|
-
test("
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
expect(
|
|
181
|
+
describe("memory v3 backfill-sections", () => {
|
|
182
|
+
test("sends memory_v3_backfill_sections with a long (30-min) IPC timeout", async () => {
|
|
183
|
+
mockIpcResult = {
|
|
184
|
+
ok: true,
|
|
185
|
+
result: { articles: 50, sections: 400, failures: 0 },
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const { exitCode } = await runCommand([
|
|
189
|
+
"memory",
|
|
190
|
+
"v3",
|
|
191
|
+
"backfill-sections",
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
expect(exitCode).toBe(0);
|
|
195
|
+
expect(lastIpcCall!.method).toBe("memory_v3_backfill_sections");
|
|
196
|
+
expect(lastIpcCall!.params!.body).toEqual({});
|
|
197
|
+
// The one-time full-corpus embed is long-running, so the CLI must override
|
|
198
|
+
// the default 60s timeout with a generous ceiling (30 min).
|
|
199
|
+
expect(lastIpcCall!.options!.timeoutMs).toBe(30 * 60 * 1000);
|
|
220
200
|
});
|
|
221
201
|
|
|
222
|
-
test("
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
202
|
+
test("summarizes embedded sections on success", async () => {
|
|
203
|
+
mockIpcResult = {
|
|
204
|
+
ok: true,
|
|
205
|
+
result: { articles: 7, sections: 42, failures: 0 },
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
await runCommand(["memory", "v3", "backfill-sections"]);
|
|
209
|
+
|
|
210
|
+
expect(
|
|
211
|
+
logOutput.some((line) => line.includes("42") && line.includes("7")),
|
|
212
|
+
).toBe(true);
|
|
227
213
|
});
|
|
228
|
-
});
|
|
229
214
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
215
|
+
test("exits with code 1 on IPC failure", async () => {
|
|
216
|
+
mockIpcResult = { ok: false, error: "Memory v3 not enabled" };
|
|
217
|
+
|
|
218
|
+
const { exitCode } = await runCommand([
|
|
219
|
+
"memory",
|
|
220
|
+
"v3",
|
|
221
|
+
"backfill-sections",
|
|
222
|
+
]);
|
|
233
223
|
|
|
234
|
-
|
|
235
|
-
test("no-op reconcile against an unchanged tree returns empty diffs", async () => {
|
|
236
|
-
// v1: prevLeaves is derived from the current tree, so diffing
|
|
237
|
-
// current-vs-current yields no renames/deletes — reconcile runs as a
|
|
238
|
-
// convergence/prune pass. This still drives the full reconcileTree path
|
|
239
|
-
// (snapshot → apply → validate → invalidate).
|
|
240
|
-
const result = await handleMemoryV3Reconcile({ dataDir, workspaceDir });
|
|
241
|
-
expect(result.renames).toEqual([]);
|
|
242
|
-
expect(result.deleted).toEqual([]);
|
|
243
|
-
expect(result.prunedCore).toEqual([]);
|
|
224
|
+
expect(exitCode).toBe(1);
|
|
244
225
|
});
|
|
245
226
|
});
|