agent-mockingbird 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/btca-cli/SKILL.md +64 -0
- package/.agents/skills/btca-cli/agents/openai.yaml +3 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/frontend-design/agents/openai.yaml +3 -0
- package/.env.example +36 -0
- package/.githooks/pre-commit +33 -0
- package/.github/workflows/ci.yml +309 -0
- package/.opencode/bun.lock +18 -0
- package/.opencode/package.json +5 -0
- package/.opencode/tools/agent_type_manager.ts +100 -0
- package/.opencode/tools/config_manager.ts +87 -0
- package/.opencode/tools/cron_manager.ts +145 -0
- package/.opencode/tools/memory_get.ts +43 -0
- package/.opencode/tools/memory_remember.ts +53 -0
- package/.opencode/tools/memory_search.ts +48 -0
- package/AGENTS.md +126 -0
- package/MEMORY.md +2 -0
- package/README.md +451 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/agent-mockingbird.config.example.json +135 -0
- package/apps/server/package.json +32 -0
- package/apps/server/src/backend/agents/bootstrapContext.ts +362 -0
- package/apps/server/src/backend/agents/openclawImport.test.ts +133 -0
- package/apps/server/src/backend/agents/openclawImport.ts +797 -0
- package/apps/server/src/backend/agents/opencodeConfig.ts +428 -0
- package/apps/server/src/backend/agents/service.ts +10 -0
- package/apps/server/src/backend/config/example-config.test.ts +20 -0
- package/apps/server/src/backend/config/orchestration.ts +243 -0
- package/apps/server/src/backend/config/policy.ts +158 -0
- package/apps/server/src/backend/config/schema.test.ts +15 -0
- package/apps/server/src/backend/config/schema.ts +391 -0
- package/apps/server/src/backend/config/semantic.test.ts +34 -0
- package/apps/server/src/backend/config/semantic.ts +149 -0
- package/apps/server/src/backend/config/service.test.ts +75 -0
- package/apps/server/src/backend/config/service.ts +207 -0
- package/apps/server/src/backend/config/smoke.ts +77 -0
- package/apps/server/src/backend/config/store.test.ts +123 -0
- package/apps/server/src/backend/config/store.ts +581 -0
- package/apps/server/src/backend/config/testFixtures.ts +5 -0
- package/apps/server/src/backend/config/types.ts +56 -0
- package/apps/server/src/backend/contracts/events.ts +320 -0
- package/apps/server/src/backend/contracts/runtime.ts +111 -0
- package/apps/server/src/backend/cron/executor.ts +435 -0
- package/apps/server/src/backend/cron/repository.ts +170 -0
- package/apps/server/src/backend/cron/service.ts +660 -0
- package/apps/server/src/backend/cron/storage.ts +92 -0
- package/apps/server/src/backend/cron/types.ts +138 -0
- package/apps/server/src/backend/cron/utils.ts +351 -0
- package/apps/server/src/backend/db/client.ts +20 -0
- package/apps/server/src/backend/db/migrate.ts +40 -0
- package/apps/server/src/backend/db/repository.ts +1762 -0
- package/apps/server/src/backend/db/schema.ts +113 -0
- package/apps/server/src/backend/db/usageDashboard.test.ts +102 -0
- package/apps/server/src/backend/db/wipe.ts +13 -0
- package/apps/server/src/backend/defaults.ts +32 -0
- package/apps/server/src/backend/env.ts +48 -0
- package/apps/server/src/backend/heartbeat/activeHours.ts +45 -0
- package/apps/server/src/backend/heartbeat/defaultJob.ts +88 -0
- package/apps/server/src/backend/heartbeat/heartbeat.test.ts +110 -0
- package/apps/server/src/backend/heartbeat/runtimeService.ts +190 -0
- package/apps/server/src/backend/heartbeat/service.ts +176 -0
- package/apps/server/src/backend/heartbeat/state.test.ts +63 -0
- package/apps/server/src/backend/heartbeat/state.ts +167 -0
- package/apps/server/src/backend/heartbeat/types.ts +54 -0
- package/apps/server/src/backend/http/boundedQueue.test.ts +49 -0
- package/apps/server/src/backend/http/boundedQueue.ts +92 -0
- package/apps/server/src/backend/http/parsers.ts +40 -0
- package/apps/server/src/backend/http/router.ts +61 -0
- package/apps/server/src/backend/http/routes/agentRoutes.ts +67 -0
- package/apps/server/src/backend/http/routes/backgroundRoutes.ts +203 -0
- package/apps/server/src/backend/http/routes/chatRoutes.ts +107 -0
- package/apps/server/src/backend/http/routes/configRoutes.ts +602 -0
- package/apps/server/src/backend/http/routes/cronRoutes.ts +221 -0
- package/apps/server/src/backend/http/routes/dashboardRoutes.ts +308 -0
- package/apps/server/src/backend/http/routes/eventRoutes.ts +7 -0
- package/apps/server/src/backend/http/routes/heartbeatRoutes.test.ts +41 -0
- package/apps/server/src/backend/http/routes/heartbeatRoutes.ts +28 -0
- package/apps/server/src/backend/http/routes/index.ts +101 -0
- package/apps/server/src/backend/http/routes/mcpRoutes.ts +213 -0
- package/apps/server/src/backend/http/routes/memoryRoutes.ts +154 -0
- package/apps/server/src/backend/http/routes/runRoutes.ts +310 -0
- package/apps/server/src/backend/http/routes/runtimeRoutes.ts +197 -0
- package/apps/server/src/backend/http/routes/skillRoutes.ts +112 -0
- package/apps/server/src/backend/http/routes/uiRoutes.test.ts +161 -0
- package/apps/server/src/backend/http/routes/uiRoutes.ts +177 -0
- package/apps/server/src/backend/http/routes/usageRoutes.test.ts +104 -0
- package/apps/server/src/backend/http/routes/usageRoutes.ts +767 -0
- package/apps/server/src/backend/http/schemas.ts +64 -0
- package/apps/server/src/backend/http/sse.ts +144 -0
- package/apps/server/src/backend/integration/backend-core.test.ts +2316 -0
- package/apps/server/src/backend/logging/logger.ts +64 -0
- package/apps/server/src/backend/mcp/service.ts +326 -0
- package/apps/server/src/backend/memory/cli.ts +170 -0
- package/apps/server/src/backend/memory/conceptExpansion.test.ts +28 -0
- package/apps/server/src/backend/memory/conceptExpansion.ts +80 -0
- package/apps/server/src/backend/memory/qmdPort.test.ts +54 -0
- package/apps/server/src/backend/memory/qmdPort.ts +61 -0
- package/apps/server/src/backend/memory/records.test.ts +66 -0
- package/apps/server/src/backend/memory/records.ts +229 -0
- package/apps/server/src/backend/memory/service.ts +2012 -0
- package/apps/server/src/backend/memory/sqliteVec.ts +58 -0
- package/apps/server/src/backend/memory/types.ts +104 -0
- package/apps/server/src/backend/opencode/agentMockingbirdPlugin.test.ts +396 -0
- package/apps/server/src/backend/opencode/client.ts +98 -0
- package/apps/server/src/backend/opencode/models.ts +41 -0
- package/apps/server/src/backend/opencode/systemPrompt.test.ts +146 -0
- package/apps/server/src/backend/opencode/systemPrompt.ts +284 -0
- package/apps/server/src/backend/paths.ts +57 -0
- package/apps/server/src/backend/prompts/service.ts +100 -0
- package/apps/server/src/backend/queue/queue.test.ts +189 -0
- package/apps/server/src/backend/queue/service.ts +177 -0
- package/apps/server/src/backend/queue/types.ts +39 -0
- package/apps/server/src/backend/run/service.ts +576 -0
- package/apps/server/src/backend/run/storage.ts +47 -0
- package/apps/server/src/backend/run/types.ts +44 -0
- package/apps/server/src/backend/runtime/errors.ts +61 -0
- package/apps/server/src/backend/runtime/index.ts +72 -0
- package/apps/server/src/backend/runtime/memoryPromptDedup.test.ts +153 -0
- package/apps/server/src/backend/runtime/memoryPromptDedup.ts +76 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/backgroundMethods.ts +765 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/coreMethods.ts +705 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/eventMethods.ts +503 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/memoryMethods.ts +462 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/promptMethods.ts +1167 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/shared.ts +254 -0
- package/apps/server/src/backend/runtime/opencodeRuntime.test.ts +2899 -0
- package/apps/server/src/backend/runtime/opencodeRuntime.ts +135 -0
- package/apps/server/src/backend/runtime/sessionScope.ts +45 -0
- package/apps/server/src/backend/skills/service.ts +442 -0
- package/apps/server/src/backend/workspace/resolve.ts +27 -0
- package/apps/server/src/cli/agent-mockingbird.mjs +2522 -0
- package/apps/server/src/cli/agent-mockingbird.test.ts +68 -0
- package/apps/server/src/cli/runtime-assets.mjs +269 -0
- package/apps/server/src/cli/runtime-assets.test.ts +52 -0
- package/apps/server/src/cli/runtime-layout.mjs +75 -0
- package/apps/server/src/cli/standaloneBuild.test.ts +19 -0
- package/apps/server/src/cli/standaloneBuild.ts +19 -0
- package/apps/server/src/cli/standaloneCronBinary.test.ts +187 -0
- package/apps/server/src/index.ts +178 -0
- package/apps/server/tsconfig.json +12 -0
- package/backlog.md +5 -0
- package/bin/agent-mockingbird +2522 -0
- package/bin/runtime-layout.mjs +75 -0
- package/build-bin.ts +34 -0
- package/build-cli.mjs +37 -0
- package/build.ts +40 -0
- package/bun-env.d.ts +11 -0
- package/bun.lock +888 -0
- package/bunfig.toml +2 -0
- package/components.json +21 -0
- package/config.json +130 -0
- package/deploy/RELEASE_INSTALL.md +112 -0
- package/deploy/docker-compose.yml +42 -0
- package/deploy/systemd/README.md +46 -0
- package/deploy/systemd/agent-mockingbird.service +28 -0
- package/deploy/systemd/opencode.service +25 -0
- package/docs/legacy-config-ui-reference.md +51 -0
- package/docs/memory-e2e-trace-2026-03-04.md +63 -0
- package/docs/memory-ops.md +96 -0
- package/docs/memory-runtime-contract.md +42 -0
- package/docs/memory-tuning-remote-2026-03-04.md +59 -0
- package/docs/opencode-rebase-workflow-plan.md +614 -0
- package/docs/opencode-startup-sync-plan.md +94 -0
- package/docs/vendor-opencode.md +41 -0
- package/drizzle/0000_famous_turbo.sql +49 -0
- package/drizzle/0001_cron_memory_aux.sql +160 -0
- package/drizzle/0002_runtime_session_bindings.sql +28 -0
- package/drizzle/0003_background_runs.sql +27 -0
- package/drizzle/0004_memory_open_write.sql +63 -0
- package/drizzle/0005_signal_channel.sql +47 -0
- package/drizzle/0006_usage_event_dimensions.sql +7 -0
- package/drizzle/meta/0000_snapshot.json +341 -0
- package/drizzle/meta/_journal.json +55 -0
- package/drizzle.config.ts +14 -0
- package/eslint.config.mjs +77 -0
- package/knip.json +18 -0
- package/memory/2026-03-04.md +4 -0
- package/opencode.lock.json +16 -0
- package/package.json +67 -0
- package/packages/agent-mockingbird-installer/README.md +31 -0
- package/packages/agent-mockingbird-installer/bin/agent-mockingbird-installer.mjs +44 -0
- package/packages/agent-mockingbird-installer/opencode.lock.json +16 -0
- package/packages/agent-mockingbird-installer/package.json +23 -0
- package/packages/contracts/package.json +19 -0
- package/packages/contracts/src/agentTypes.ts +122 -0
- package/packages/contracts/src/cron.ts +146 -0
- package/packages/contracts/src/dashboard.ts +378 -0
- package/packages/contracts/src/index.ts +3 -0
- package/packages/contracts/tsconfig.json +4 -0
- package/patches/opencode/0001-Wafflebot-OpenCode-baseline.patch +2341 -0
- package/patches/opencode/0002-Fix-OpenCode-web-entry-and-settings-icons.patch +104 -0
- package/patches/opencode/0003-fix-app-remove-duplicate-sidebar-mount.patch +32 -0
- package/patches/opencode/0004-Add-heartbeat-settings-and-usage-nav.patch +506 -0
- package/patches/opencode/0005-Use-chart-icon-for-usage-nav.patch +38 -0
- package/patches/opencode/0006-Modernize-cron-settings.patch +399 -0
- package/patches/opencode/0007-Rename-waffle-namespaces-to-mockingbird.patch +1110 -0
- package/patches/opencode/0008-Remove-cron-contract-section.patch +178 -0
- package/patches/opencode/0009-Rework-cron-tab-as-operations-console.patch +414 -0
- package/patches/opencode/0010-Refine-heartbeat-settings-controls.patch +208 -0
- package/runtime-assets/opencode-config/opencode.jsonc +25 -0
- package/runtime-assets/opencode-config/package.json +5 -0
- package/runtime-assets/opencode-config/plugins/agent-mockingbird.ts +715 -0
- package/runtime-assets/workspace/.agents/skills/config-auditor/SKILL.md +25 -0
- package/runtime-assets/workspace/.agents/skills/config-editor/SKILL.md +24 -0
- package/runtime-assets/workspace/.agents/skills/cron-manager/SKILL.md +57 -0
- package/runtime-assets/workspace/.agents/skills/memory-ops/SKILL.md +120 -0
- package/runtime-assets/workspace/.agents/skills/runtime-diagnose/SKILL.md +25 -0
- package/runtime-assets/workspace/AGENTS.md +56 -0
- package/runtime-assets/workspace/MEMORY.md +4 -0
- package/scripts/build-release-bundle.sh +66 -0
- package/scripts/check-ship.ts +383 -0
- package/scripts/dev-opencode.sh +17 -0
- package/scripts/dev-stack-opencode.sh +15 -0
- package/scripts/dev-stack.sh +61 -0
- package/scripts/install-systemd.sh +87 -0
- package/scripts/memory-e2e.sh +76 -0
- package/scripts/memory-trace-e2e.sh +141 -0
- package/scripts/migrate-opencode-env.ts +108 -0
- package/scripts/onboard/bootstrap.sh +32 -0
- package/scripts/opencode-swap.ts +78 -0
- package/scripts/opencode-sync.ts +715 -0
- package/scripts/runtime-assets-sync.mjs +83 -0
- package/scripts/setup-git-hooks.ts +39 -0
- package/tsconfig.json +45 -0
- package/tui.json +98 -0
- package/turbo.json +36 -0
- package/vendor/OPENCODE_VENDOR.md +13 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export class RuntimeSessionNotFoundError extends Error {
|
|
2
|
+
constructor(sessionId: string) {
|
|
3
|
+
super(`Session not found: ${sessionId}`);
|
|
4
|
+
this.name = "RuntimeSessionNotFoundError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class RuntimeSessionBusyError extends Error {
|
|
9
|
+
constructor(sessionId: string) {
|
|
10
|
+
super(`Session is already processing: ${sessionId}`);
|
|
11
|
+
this.name = "RuntimeSessionBusyError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class RuntimeSessionQueuedError extends Error {
|
|
16
|
+
constructor(
|
|
17
|
+
public sessionId: string,
|
|
18
|
+
public depth: number,
|
|
19
|
+
) {
|
|
20
|
+
super(`Session is busy; message queued (${depth} pending): ${sessionId}`);
|
|
21
|
+
this.name = "RuntimeSessionQueuedError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class RuntimeContinuationDetachedError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
public sessionId: string,
|
|
28
|
+
public childRunCount: number,
|
|
29
|
+
) {
|
|
30
|
+
super(`Session is still running via ${childRunCount} child run(s): ${sessionId}`);
|
|
31
|
+
this.name = "RuntimeContinuationDetachedError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class RuntimeProviderQuotaError extends Error {
|
|
36
|
+
constructor(message = "Provider quota exceeded. Add credits or switch provider/model.") {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "RuntimeProviderQuotaError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class RuntimeProviderAuthError extends Error {
|
|
43
|
+
constructor(message = "Provider authentication failed. Check API key or provider credentials.") {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "RuntimeProviderAuthError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class RuntimeProviderRateLimitError extends Error {
|
|
50
|
+
constructor(message = "Provider rate limited this request. Please retry in a moment.") {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "RuntimeProviderRateLimitError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class RuntimeTurnTimeoutError extends Error {
|
|
57
|
+
constructor(sessionId: string, timeoutMs: number) {
|
|
58
|
+
super(`Session response timed out after ${timeoutMs}ms: ${sessionId}`);
|
|
59
|
+
this.name = "RuntimeTurnTimeoutError";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { OpencodeRuntime } from "./opencodeRuntime";
|
|
2
|
+
import { getConfigSnapshot } from "../config/service";
|
|
3
|
+
import type { RuntimeEngine } from "../contracts/runtime";
|
|
4
|
+
import { readConfiguredMcpServersFromWorkspaceConfig } from "../mcp/service";
|
|
5
|
+
import { getOpencodeConnectionInfo } from "../opencode/client";
|
|
6
|
+
import { listManagedSkillCatalog } from "../skills/service";
|
|
7
|
+
|
|
8
|
+
let runtimeInstance: RuntimeEngine | null = null;
|
|
9
|
+
|
|
10
|
+
interface RuntimeStartupInfo {
|
|
11
|
+
opencode: {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
providerId: string;
|
|
14
|
+
modelId: string;
|
|
15
|
+
fallbackModels: Array<string>;
|
|
16
|
+
smallModel: string;
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
promptTimeoutMs: number;
|
|
19
|
+
directoryConfigured: boolean;
|
|
20
|
+
authConfigured: boolean;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createRuntime(): RuntimeEngine {
|
|
25
|
+
const config = getConfigSnapshot().config;
|
|
26
|
+
const runtime = new OpencodeRuntime({
|
|
27
|
+
defaultProviderId: config.runtime.opencode.providerId,
|
|
28
|
+
defaultModelId: config.runtime.opencode.modelId,
|
|
29
|
+
fallbackModelRefs: config.runtime.opencode.fallbackModels,
|
|
30
|
+
getRuntimeConfig: () => getConfigSnapshot().config.runtime.opencode,
|
|
31
|
+
getEnabledSkills: () => listManagedSkillCatalog(getConfigSnapshot().config.runtime.opencode.directory).enabled,
|
|
32
|
+
getEnabledMcps: () =>
|
|
33
|
+
readConfiguredMcpServersFromWorkspaceConfig(getConfigSnapshot().config)
|
|
34
|
+
.filter(server => server.enabled)
|
|
35
|
+
.map(server => server.id),
|
|
36
|
+
getConfiguredMcpServers: () => readConfiguredMcpServersFromWorkspaceConfig(getConfigSnapshot().config),
|
|
37
|
+
});
|
|
38
|
+
runtimeInstance = runtime;
|
|
39
|
+
return runtime;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getRuntime(): RuntimeEngine | null {
|
|
43
|
+
return runtimeInstance;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getRuntimeStartupInfo(): RuntimeStartupInfo {
|
|
47
|
+
const config = getConfigSnapshot().config;
|
|
48
|
+
const connection = getOpencodeConnectionInfo({
|
|
49
|
+
baseUrl: config.runtime.opencode.baseUrl,
|
|
50
|
+
timeoutMs: config.runtime.opencode.timeoutMs,
|
|
51
|
+
directory: config.runtime.opencode.directory,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
opencode: {
|
|
55
|
+
baseUrl: connection.baseUrl,
|
|
56
|
+
providerId: config.runtime.opencode.providerId,
|
|
57
|
+
modelId: config.runtime.opencode.modelId,
|
|
58
|
+
fallbackModels: config.runtime.opencode.fallbackModels,
|
|
59
|
+
smallModel: config.runtime.opencode.smallModel,
|
|
60
|
+
timeoutMs: connection.timeoutMs,
|
|
61
|
+
promptTimeoutMs: config.runtime.opencode.promptTimeoutMs,
|
|
62
|
+
directoryConfigured: connection.directoryConfigured,
|
|
63
|
+
authConfigured: connection.authConfigured,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
RuntimeSessionBusyError,
|
|
70
|
+
RuntimeSessionNotFoundError,
|
|
71
|
+
RuntimeTurnTimeoutError,
|
|
72
|
+
} from "./errors";
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
analyzeMemoryInjectionResults,
|
|
5
|
+
buildMemoryContextFingerprint,
|
|
6
|
+
isMemoryRecallIntentQuery,
|
|
7
|
+
isWriteIntentMemoryQuery,
|
|
8
|
+
memoryInjectionResultKey,
|
|
9
|
+
prepareMemoryInjectionResults,
|
|
10
|
+
} from "./memoryPromptDedup";
|
|
11
|
+
import type { MemorySearchResult } from "../memory/types";
|
|
12
|
+
|
|
13
|
+
function result(input: {
|
|
14
|
+
id: string;
|
|
15
|
+
score: number;
|
|
16
|
+
path?: string;
|
|
17
|
+
startLine?: number;
|
|
18
|
+
endLine?: number;
|
|
19
|
+
snippet?: string;
|
|
20
|
+
citation?: string;
|
|
21
|
+
}): MemorySearchResult {
|
|
22
|
+
return {
|
|
23
|
+
id: input.id,
|
|
24
|
+
path: input.path ?? "memory/2026-03-02.md",
|
|
25
|
+
startLine: input.startLine ?? 1,
|
|
26
|
+
endLine: input.endLine ?? 3,
|
|
27
|
+
source: "memory",
|
|
28
|
+
score: input.score,
|
|
29
|
+
snippet: input.snippet ?? "snippet",
|
|
30
|
+
citation: input.citation ?? "memory/2026-03-02.md#L1",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("buildMemoryContextFingerprint", () => {
|
|
35
|
+
test("ignores score and ordering jitter for same chunk set", () => {
|
|
36
|
+
const first = buildMemoryContextFingerprint([result({ id: "chunk-b", score: 0.93 }), result({ id: "chunk-a", score: 0.71 })]);
|
|
37
|
+
const second = buildMemoryContextFingerprint([result({ id: "chunk-a", score: 0.12 }), result({ id: "chunk-b", score: 0.44 })]);
|
|
38
|
+
expect(first).toBe(second);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("changes when chunk identity set changes", () => {
|
|
42
|
+
const baseline = buildMemoryContextFingerprint([result({ id: "chunk-a", score: 0.9 }), result({ id: "chunk-b", score: 0.5 })]);
|
|
43
|
+
const changed = buildMemoryContextFingerprint([result({ id: "chunk-a", score: 0.2 }), result({ id: "chunk-c", score: 0.1 })]);
|
|
44
|
+
expect(changed).not.toBe(baseline);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("returns stable empty fingerprint", () => {
|
|
48
|
+
expect(buildMemoryContextFingerprint([])).toBe("[]");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("prepareMemoryInjectionResults", () => {
|
|
53
|
+
test("drops low-signal boilerplate with no query overlap", () => {
|
|
54
|
+
const results = prepareMemoryInjectionResults("favorite pokemon", [
|
|
55
|
+
result({
|
|
56
|
+
id: "index",
|
|
57
|
+
score: 0.8,
|
|
58
|
+
path: "MEMORY.md",
|
|
59
|
+
snippet: "# Memory Index\nStore durable notes in memory/*.md",
|
|
60
|
+
citation: "MEMORY.md#L1",
|
|
61
|
+
}),
|
|
62
|
+
result({
|
|
63
|
+
id: "pokemon",
|
|
64
|
+
score: 0.7,
|
|
65
|
+
snippet: "User's favorite Pokemon is Vulpix.",
|
|
66
|
+
citation: "memory/2026-03-02.md#L10",
|
|
67
|
+
}),
|
|
68
|
+
]);
|
|
69
|
+
expect(results.length).toBe(1);
|
|
70
|
+
expect(results[0]?.id).toBe("pokemon");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("dedupes structured snippets by memory record id", () => {
|
|
74
|
+
const sharedSnippetA = [
|
|
75
|
+
"### [memory:memory_abc123] 2026-03-02T20:41:01.037Z",
|
|
76
|
+
"```json",
|
|
77
|
+
'{"id":"memory_abc123"}',
|
|
78
|
+
"```",
|
|
79
|
+
"User's favorite Pokemon is Vulpix.",
|
|
80
|
+
].join("\n");
|
|
81
|
+
const sharedSnippetB = `${sharedSnippetA}\n(extra context)`;
|
|
82
|
+
const results = prepareMemoryInjectionResults("pokemon", [
|
|
83
|
+
result({ id: "chunk-1", score: 0.9, snippet: sharedSnippetA }),
|
|
84
|
+
result({ id: "chunk-2", score: 0.7, snippet: sharedSnippetB }),
|
|
85
|
+
]);
|
|
86
|
+
expect(results.length).toBe(1);
|
|
87
|
+
expect(results[0]?.id).toBe("chunk-1");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("filters structured snippets that are irrelevant to non-recall queries", () => {
|
|
91
|
+
const structured = [
|
|
92
|
+
"### [memory:memory_abc123] 2026-03-02T20:41:01.037Z",
|
|
93
|
+
"```json",
|
|
94
|
+
'{"id":"memory_abc123"}',
|
|
95
|
+
"```",
|
|
96
|
+
"User has a daughter named Lucy Lee in the NICU.",
|
|
97
|
+
].join("\n");
|
|
98
|
+
const results = prepareMemoryInjectionResults("pokemon evolution chart", [
|
|
99
|
+
result({ id: "chunk-1", score: 0.9, snippet: structured }),
|
|
100
|
+
]);
|
|
101
|
+
expect(results.length).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("keeps structured snippets for explicit recall-intent queries", () => {
|
|
105
|
+
const structured = [
|
|
106
|
+
"### [memory:memory_abc123] 2026-03-02T20:41:01.037Z",
|
|
107
|
+
"```json",
|
|
108
|
+
'{"id":"memory_abc123"}',
|
|
109
|
+
"```",
|
|
110
|
+
"User has a daughter named Lucy Lee in the NICU.",
|
|
111
|
+
].join("\n");
|
|
112
|
+
const analyzed = analyzeMemoryInjectionResults("what do you remember about me?", [
|
|
113
|
+
result({ id: "chunk-1", score: 0.9, snippet: structured }),
|
|
114
|
+
]);
|
|
115
|
+
expect(analyzed.results.length).toBe(1);
|
|
116
|
+
expect(analyzed.filteredIrrelevantCount).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("memoryInjectionResultKey", () => {
|
|
121
|
+
test("uses memory record id when present", () => {
|
|
122
|
+
const structured = [
|
|
123
|
+
"### [memory:memory_abc123] 2026-03-02T20:41:01.037Z",
|
|
124
|
+
"```json",
|
|
125
|
+
'{"id":"memory_abc123"}',
|
|
126
|
+
"```",
|
|
127
|
+
"User has a daughter named Lucy Lee in the NICU.",
|
|
128
|
+
].join("\n");
|
|
129
|
+
expect(memoryInjectionResultKey(result({ id: "chunk-1", score: 0.8, snippet: structured }))).toBe("record:memory_abc123");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("falls back to chunk id for unstructured snippets", () => {
|
|
133
|
+
expect(memoryInjectionResultKey(result({ id: "chunk-plain", score: 0.8, snippet: "plain snippet" }))).toBe(
|
|
134
|
+
"chunk:chunk-plain",
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("isWriteIntentMemoryQuery", () => {
|
|
140
|
+
test("detects write-intent phrases", () => {
|
|
141
|
+
expect(isWriteIntentMemoryQuery("also remember that I have an android phone")).toBe(true);
|
|
142
|
+
expect(isWriteIntentMemoryQuery("please note that this is important")).toBe(true);
|
|
143
|
+
expect(isWriteIntentMemoryQuery("what is my favorite pokemon?")).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("isMemoryRecallIntentQuery", () => {
|
|
148
|
+
test("detects recall-intent phrases", () => {
|
|
149
|
+
expect(isMemoryRecallIntentQuery("what do you remember about me?")).toBe(true);
|
|
150
|
+
expect(isMemoryRecallIntentQuery("remind me what you know from memory")).toBe(true);
|
|
151
|
+
expect(isMemoryRecallIntentQuery("what is my favorite pokemon?")).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { MemorySearchResult } from "../memory/types";
|
|
2
|
+
|
|
3
|
+
const MEMORY_RECORD_MARKER_RE = /###\s+\[memory:([a-zA-Z0-9_-]+)\]/i;
|
|
4
|
+
const TOKEN_RE = /[a-z0-9]{3,}/g;
|
|
5
|
+
const WRITE_INTENT_RE = /\b(?:remember\s+that|note\s+that|save\s+this)\b/i;
|
|
6
|
+
const RECALL_INTENT_RE = /\b(?:what\s+do\s+you\s+remember|remind\s+me|recall|from\s+memory|what\s+do\s+you\s+know\s+about\s+me)\b/i;
|
|
7
|
+
|
|
8
|
+
function collectTokens(text: string) {
|
|
9
|
+
const matched = text.toLowerCase().match(TOKEN_RE);
|
|
10
|
+
return new Set(matched ?? []);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeSnippetForDedupe(snippet: string) {
|
|
14
|
+
return snippet.toLowerCase().replace(/\s+/g, " ").trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractRecordId(snippet: string): string | null {
|
|
18
|
+
const matched = snippet.match(MEMORY_RECORD_MARKER_RE);
|
|
19
|
+
const recordId = matched?.[1]?.trim();
|
|
20
|
+
return recordId || null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isMemoryRecallIntentQuery(query: string): boolean {
|
|
24
|
+
return RECALL_INTENT_RE.test(query);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildMemoryContextFingerprint(results: MemorySearchResult[]): string {
|
|
28
|
+
const ids = [...new Set(results.map(result => result.id.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
29
|
+
return JSON.stringify(ids);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isWriteIntentMemoryQuery(query: string): boolean {
|
|
33
|
+
return WRITE_INTENT_RE.test(query);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function memoryInjectionResultKey(result: MemorySearchResult): string {
|
|
37
|
+
const recordId = extractRecordId(result.snippet);
|
|
38
|
+
if (recordId) return `record:${recordId}`;
|
|
39
|
+
return `chunk:${result.id.trim() || normalizeSnippetForDedupe(result.snippet)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function analyzeMemoryInjectionResults(query: string, results: MemorySearchResult[]): {
|
|
43
|
+
results: MemorySearchResult[];
|
|
44
|
+
filteredIrrelevantCount: number;
|
|
45
|
+
dedupedCount: number;
|
|
46
|
+
} {
|
|
47
|
+
const queryTokens = collectTokens(query);
|
|
48
|
+
const recallIntent = isMemoryRecallIntentQuery(query);
|
|
49
|
+
const deduped = new Map<string, MemorySearchResult>();
|
|
50
|
+
let filteredIrrelevantCount = 0;
|
|
51
|
+
|
|
52
|
+
for (const result of results) {
|
|
53
|
+
const snippetTokens = collectTokens(`${result.citation}\n${result.snippet}`);
|
|
54
|
+
const hasOverlap = [...snippetTokens].some(token => queryTokens.has(token));
|
|
55
|
+
if (!recallIntent && queryTokens.size > 0 && !hasOverlap) {
|
|
56
|
+
filteredIrrelevantCount += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const key = memoryInjectionResultKey(result);
|
|
61
|
+
if (!deduped.has(key)) {
|
|
62
|
+
deduped.set(key, result);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const dedupedResults = [...deduped.values()];
|
|
67
|
+
return {
|
|
68
|
+
results: dedupedResults,
|
|
69
|
+
filteredIrrelevantCount,
|
|
70
|
+
dedupedCount: Math.max(0, results.length - filteredIrrelevantCount - dedupedResults.length),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function prepareMemoryInjectionResults(query: string, results: MemorySearchResult[]): MemorySearchResult[] {
|
|
75
|
+
return analyzeMemoryInjectionResults(query, results).results;
|
|
76
|
+
}
|