@vellumai/assistant 0.6.0 → 0.6.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.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/openapi.yaml +538 -3
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/checker.test.ts +38 -6
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +72 -105
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +62 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/agent/loop.ts +6 -0
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/cli/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/connect.ts +14 -5
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
- package/src/config/bundled-skills/gmail/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +158 -65
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +300 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +18 -0
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +63 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +199 -157
- package/src/daemon/message-types/conversations.ts +25 -6
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +89 -9
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +136 -107
- package/src/memory/conversation-group-migration.ts +1 -1
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -15
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/permissions/checker.ts +12 -1
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -0
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -13,18 +13,18 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
|
|
15
15
|
import { and, asc, ne, sql } from "drizzle-orm";
|
|
16
|
+
import { v4 as uuid } from "uuid";
|
|
16
17
|
|
|
17
18
|
import { getConfig } from "../../config/loader.js";
|
|
18
19
|
import { getLogger } from "../../util/logger.js";
|
|
19
20
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
20
21
|
import { getMemoryCheckpoint, setMemoryCheckpoint } from "../checkpoints.js";
|
|
21
|
-
import { getDb, rawAll } from "../db.js";
|
|
22
|
+
import { getDb, rawAll, rawGet, rawRun } from "../db.js";
|
|
22
23
|
import { enqueueMemoryJob, hasActiveJobOfType } from "../jobs-store.js";
|
|
23
24
|
import { initQdrantClient } from "../qdrant-client.js";
|
|
24
25
|
import { conversations, memoryGraphNodes, memorySegments } from "../schema.js";
|
|
25
26
|
import { runGraphExtraction } from "./extraction.js";
|
|
26
|
-
import { countNodes
|
|
27
|
-
import type { NewNode } from "./types.js";
|
|
27
|
+
import { countNodes } from "./store.js";
|
|
28
28
|
|
|
29
29
|
const log = getLogger("graph-bootstrap");
|
|
30
30
|
|
|
@@ -61,7 +61,7 @@ export interface BootstrapResult {
|
|
|
61
61
|
* if interrupted, re-run and it picks up where it left off.
|
|
62
62
|
*/
|
|
63
63
|
export async function bootstrapFromHistory(
|
|
64
|
-
options?: BootstrapOptions
|
|
64
|
+
options?: BootstrapOptions,
|
|
65
65
|
): Promise<BootstrapResult> {
|
|
66
66
|
const start = Date.now();
|
|
67
67
|
const scopeId = options?.scopeId ?? "default";
|
|
@@ -106,7 +106,7 @@ export async function bootstrapFromHistory(
|
|
|
106
106
|
|
|
107
107
|
log.info(
|
|
108
108
|
{ total: allConversations.length },
|
|
109
|
-
"Starting graph bootstrap from historical conversations"
|
|
109
|
+
"Starting graph bootstrap from historical conversations",
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
// Resume from checkpoint
|
|
@@ -148,7 +148,7 @@ export async function bootstrapFromHistory(
|
|
|
148
148
|
skipQdrant: true, // Use DB query for candidates (no Qdrant dependency)
|
|
149
149
|
conversationTimestamp: conv.createdAt, // Use actual conversation time
|
|
150
150
|
embedInline: true, // Embed synchronously so nodes are searchable immediately
|
|
151
|
-
}
|
|
151
|
+
},
|
|
152
152
|
);
|
|
153
153
|
|
|
154
154
|
result.totalNodesCreated += extractionResult.nodesCreated;
|
|
@@ -171,14 +171,14 @@ export async function bootstrapFromHistory(
|
|
|
171
171
|
nodes: nodeCount,
|
|
172
172
|
elapsed: `${((Date.now() - start) / 1000).toFixed(1)}s`,
|
|
173
173
|
},
|
|
174
|
-
"Bootstrap progress"
|
|
174
|
+
"Bootstrap progress",
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
} catch (err) {
|
|
178
178
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
179
179
|
log.warn(
|
|
180
180
|
{ conversationId: conv.id, err: errMsg },
|
|
181
|
-
"Failed to extract conversation, continuing"
|
|
181
|
+
"Failed to extract conversation, continuing",
|
|
182
182
|
);
|
|
183
183
|
result.errors.push({ conversationId: conv.id, error: errMsg });
|
|
184
184
|
|
|
@@ -199,7 +199,7 @@ export async function bootstrapFromHistory(
|
|
|
199
199
|
errors: result.errors.length,
|
|
200
200
|
elapsedMs: result.elapsedMs,
|
|
201
201
|
},
|
|
202
|
-
"Graph bootstrap complete"
|
|
202
|
+
"Graph bootstrap complete",
|
|
203
203
|
);
|
|
204
204
|
|
|
205
205
|
return result;
|
|
@@ -209,7 +209,7 @@ export async function bootstrapFromHistory(
|
|
|
209
209
|
* Also extract from journal files on disk.
|
|
210
210
|
*/
|
|
211
211
|
export async function bootstrapFromJournal(
|
|
212
|
-
scopeId: string = "default"
|
|
212
|
+
scopeId: string = "default",
|
|
213
213
|
): Promise<{ extracted: number; errors: number }> {
|
|
214
214
|
const config = getConfig();
|
|
215
215
|
const journalDir = join(getWorkspaceDir(), "journal");
|
|
@@ -227,7 +227,7 @@ export async function bootstrapFromJournal(
|
|
|
227
227
|
(f) =>
|
|
228
228
|
f.endsWith(".md") &&
|
|
229
229
|
!f.startsWith(".") &&
|
|
230
|
-
f.toLowerCase() !== "readme.md"
|
|
230
|
+
f.toLowerCase() !== "readme.md",
|
|
231
231
|
);
|
|
232
232
|
} catch {
|
|
233
233
|
continue;
|
|
@@ -248,7 +248,7 @@ export async function bootstrapFromJournal(
|
|
|
248
248
|
} catch (err) {
|
|
249
249
|
log.warn(
|
|
250
250
|
{ file, slug, err: err instanceof Error ? err.message : String(err) },
|
|
251
|
-
"Failed to extract journal entry"
|
|
251
|
+
"Failed to extract journal entry",
|
|
252
252
|
);
|
|
253
253
|
errors++;
|
|
254
254
|
}
|
|
@@ -289,8 +289,8 @@ function parseJournalDate(filename: string): number {
|
|
|
289
289
|
|
|
290
290
|
return new Date(
|
|
291
291
|
`${year}-${month}-${day}T${String(hours).padStart(2, "0")}:${String(
|
|
292
|
-
minutes
|
|
293
|
-
).padStart(2, "0")}:00
|
|
292
|
+
minutes,
|
|
293
|
+
).padStart(2, "0")}:00`,
|
|
294
294
|
).getTime();
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -320,8 +320,8 @@ export function maybeEnqueueGraphBootstrap(): void {
|
|
|
320
320
|
.where(
|
|
321
321
|
and(
|
|
322
322
|
ne(memoryGraphNodes.type, "procedural"),
|
|
323
|
-
sql`${memoryGraphNodes.fidelity} != 'gone'
|
|
324
|
-
)
|
|
323
|
+
sql`${memoryGraphNodes.fidelity} != 'gone'`,
|
|
324
|
+
),
|
|
325
325
|
)
|
|
326
326
|
.get()?.count ?? 0;
|
|
327
327
|
|
|
@@ -343,7 +343,7 @@ export function maybeEnqueueGraphBootstrap(): void {
|
|
|
343
343
|
|
|
344
344
|
log.info(
|
|
345
345
|
{ segmentCount, hasJournalFiles },
|
|
346
|
-
"Graph empty with historical data — enqueueing bootstrap"
|
|
346
|
+
"Graph empty with historical data — enqueueing bootstrap",
|
|
347
347
|
);
|
|
348
348
|
enqueueMemoryJob("graph_bootstrap", {});
|
|
349
349
|
}
|
|
@@ -379,6 +379,11 @@ const KIND_TO_PREFIX: Record<string, string> = {
|
|
|
379
379
|
*
|
|
380
380
|
* Idempotent: uses a checkpoint to run only once. Skips items whose
|
|
381
381
|
* sourceKey already exists in the graph.
|
|
382
|
+
*
|
|
383
|
+
* Uses raw SQL for the INSERT to avoid coupling to the evolving Drizzle
|
|
384
|
+
* schema. ORM-based inserts include every column in the schema definition,
|
|
385
|
+
* so adding a column in a later migration would cause this migration to
|
|
386
|
+
* fail with "table has no column named …" on upgrade paths.
|
|
382
387
|
*/
|
|
383
388
|
export function migrateToolCreatedItems(): void {
|
|
384
389
|
if (getMemoryCheckpoint(MIGRATE_ITEMS_CHECKPOINT)) return;
|
|
@@ -392,12 +397,15 @@ export function migrateToolCreatedItems(): void {
|
|
|
392
397
|
`SELECT id, kind, subject, statement, confidence, importance, scope_id, first_seen_at
|
|
393
398
|
FROM memory_items
|
|
394
399
|
WHERE kind IN (${placeholders}) AND status = 'active'`,
|
|
395
|
-
...kinds
|
|
400
|
+
...kinds,
|
|
396
401
|
);
|
|
397
|
-
} catch {
|
|
402
|
+
} catch (err) {
|
|
398
403
|
// Table may not exist (fresh install) — nothing to migrate
|
|
399
|
-
|
|
400
|
-
|
|
404
|
+
if (err instanceof Error && err.message.includes("no such table")) {
|
|
405
|
+
setMemoryCheckpoint(MIGRATE_ITEMS_CHECKPOINT, "done");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
throw err;
|
|
401
409
|
}
|
|
402
410
|
|
|
403
411
|
if (rows.length === 0) {
|
|
@@ -405,7 +413,6 @@ export function migrateToolCreatedItems(): void {
|
|
|
405
413
|
return;
|
|
406
414
|
}
|
|
407
415
|
|
|
408
|
-
const db = getDb();
|
|
409
416
|
let migrated = 0;
|
|
410
417
|
|
|
411
418
|
for (const row of rows) {
|
|
@@ -413,55 +420,57 @@ export function migrateToolCreatedItems(): void {
|
|
|
413
420
|
if (!prefix) continue;
|
|
414
421
|
|
|
415
422
|
// Build content in the format the new tools expect
|
|
416
|
-
const content =
|
|
417
|
-
row.kind === "playbook"
|
|
418
|
-
? `${row.subject}\n${row.statement}`
|
|
419
|
-
: `${row.subject}: ${row.statement}`;
|
|
423
|
+
const content = `${row.subject}\n${row.statement}`;
|
|
420
424
|
|
|
421
425
|
// Check if already migrated (sourceKey exists in graph)
|
|
422
426
|
const sourceKey = `${prefix}${row.id}`;
|
|
423
|
-
const existing =
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
sql`${memoryGraphNodes.sourceConversations} LIKE ${
|
|
428
|
-
"%" + sourceKey + "%"
|
|
429
|
-
}`
|
|
430
|
-
)
|
|
431
|
-
.get();
|
|
427
|
+
const existing = rawGet<{ id: string }>(
|
|
428
|
+
`SELECT id FROM memory_graph_nodes WHERE source_conversations LIKE ?`,
|
|
429
|
+
`%${sourceKey}%`,
|
|
430
|
+
);
|
|
432
431
|
if (existing) continue;
|
|
433
432
|
|
|
434
433
|
const now = Date.now();
|
|
435
|
-
const
|
|
434
|
+
const id = uuid();
|
|
435
|
+
const emotionalCharge = JSON.stringify({
|
|
436
|
+
valence: 0,
|
|
437
|
+
intensity: 0.1,
|
|
438
|
+
decayCurve: "linear",
|
|
439
|
+
decayRate: 0.05,
|
|
440
|
+
originalIntensity: 0.1,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
rawRun(
|
|
444
|
+
`INSERT INTO memory_graph_nodes (
|
|
445
|
+
id, content, type, created, last_accessed, last_consolidated,
|
|
446
|
+
event_date, emotional_charge, fidelity, confidence, significance,
|
|
447
|
+
stability, reinforcement_count, last_reinforced,
|
|
448
|
+
source_conversations, source_type, narrative_role, part_of_story,
|
|
449
|
+
image_refs, scope_id
|
|
450
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
451
|
+
id,
|
|
436
452
|
content,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
emotionalCharge
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
partOfStory: null,
|
|
459
|
-
imageRefs: null,
|
|
460
|
-
scopeId: row.scope_id || "default",
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
const created = createNode(node);
|
|
464
|
-
enqueueMemoryJob("embed_graph_node", { nodeId: created.id });
|
|
453
|
+
"semantic",
|
|
454
|
+
row.first_seen_at || now,
|
|
455
|
+
now,
|
|
456
|
+
now,
|
|
457
|
+
null,
|
|
458
|
+
emotionalCharge,
|
|
459
|
+
"vivid",
|
|
460
|
+
row.confidence,
|
|
461
|
+
row.importance,
|
|
462
|
+
14,
|
|
463
|
+
0,
|
|
464
|
+
now,
|
|
465
|
+
JSON.stringify([sourceKey]),
|
|
466
|
+
"direct",
|
|
467
|
+
null,
|
|
468
|
+
null,
|
|
469
|
+
null,
|
|
470
|
+
row.scope_id || "default",
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: id });
|
|
465
474
|
migrated++;
|
|
466
475
|
}
|
|
467
476
|
|
|
@@ -470,7 +479,7 @@ export function migrateToolCreatedItems(): void {
|
|
|
470
479
|
if (migrated > 0) {
|
|
471
480
|
log.info(
|
|
472
481
|
{ migrated, total: rows.length },
|
|
473
|
-
"Migrated tool-created items to graph nodes"
|
|
482
|
+
"Migrated tool-created items to graph nodes",
|
|
474
483
|
);
|
|
475
484
|
}
|
|
476
485
|
}
|
|
@@ -506,7 +515,7 @@ export async function cleanupStaleItemVectors(): Promise<void> {
|
|
|
506
515
|
} catch (err) {
|
|
507
516
|
log.warn(
|
|
508
517
|
{ err: err instanceof Error ? err.message : String(err) },
|
|
509
|
-
"Failed to clean up stale item vectors — will retry on next startup"
|
|
518
|
+
"Failed to clean up stale item vectors — will retry on next startup",
|
|
510
519
|
);
|
|
511
520
|
}
|
|
512
521
|
}
|
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
// Memory Graph — Capability seeding for skills and CLI commands
|
|
3
3
|
//
|
|
4
4
|
// Creates graph nodes for skill/CLI capabilities so they participate in
|
|
5
|
-
// semantic retrieval.
|
|
6
|
-
// skill-memory.ts and cli-memory.ts.
|
|
5
|
+
// semantic retrieval.
|
|
7
6
|
// ---------------------------------------------------------------------------
|
|
8
7
|
|
|
9
|
-
import { and, eq, like } from "drizzle-orm";
|
|
8
|
+
import { and, eq, like, sql } from "drizzle-orm";
|
|
10
9
|
|
|
11
10
|
import { buildCliProgram } from "../../cli/program.js";
|
|
11
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
12
12
|
import { getConfig } from "../../config/loader.js";
|
|
13
13
|
import { resolveSkillStates } from "../../config/skill-state.js";
|
|
14
14
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
15
15
|
import {
|
|
16
|
+
getCachedCatalogSync,
|
|
17
|
+
getCatalog,
|
|
18
|
+
} from "../../skills/catalog-cache.js";
|
|
19
|
+
import {
|
|
20
|
+
fromCatalogSkill,
|
|
16
21
|
fromSkillSummary,
|
|
17
22
|
type SkillCapabilityInput,
|
|
18
23
|
} from "../../skills/skill-memory.js";
|
|
@@ -24,6 +29,9 @@ import { createNode } from "./store.js";
|
|
|
24
29
|
|
|
25
30
|
const log = getLogger("graph-capability-seed");
|
|
26
31
|
|
|
32
|
+
/** Default significance for capability nodes. */
|
|
33
|
+
const CAPABILITY_SIGNIFICANCE = 0.6;
|
|
34
|
+
|
|
27
35
|
/** Stable prefix for capability node source tracking. */
|
|
28
36
|
const SKILL_SOURCE_PREFIX = "capability:skill:";
|
|
29
37
|
const CLI_SOURCE_PREFIX = "capability:cli:";
|
|
@@ -107,11 +115,40 @@ export function seedSkillGraphNodes(): void {
|
|
|
107
115
|
seenKeys.add(`${SKILL_SOURCE_PREFIX}${summary.id}`);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
118
|
+
// Protect uninstalled catalog skills from pruning — they are seeded
|
|
119
|
+
// asynchronously by seedUninstalledCatalogSkillMemories() and should
|
|
120
|
+
// not be marked as "gone" just because they aren't locally installed.
|
|
121
|
+
// When the catalog cache is cold (empty before the async fetch
|
|
122
|
+
// completes), we can only prune locally managed skills; full
|
|
123
|
+
// catalog-based pruning waits until the cache is populated.
|
|
124
|
+
const cachedCatalog = getCachedCatalogSync();
|
|
125
|
+
if (cachedCatalog.length === 0) {
|
|
126
|
+
// Catalog cache is cold — we can't enumerate remote catalog skills, so
|
|
127
|
+
// skip catalog-based pruning to avoid incorrectly marking valid
|
|
128
|
+
// uninstalled catalog nodes as gone. But still prune locally disabled
|
|
129
|
+
// skills so stale capability nodes don't linger after cold start.
|
|
130
|
+
log.info(
|
|
131
|
+
"Catalog cache is cold — pruning only locally disabled skills",
|
|
132
|
+
);
|
|
133
|
+
const disabled = resolved.filter((r) => r.state !== "enabled");
|
|
134
|
+
for (const { summary } of disabled) {
|
|
135
|
+
deleteSkillCapabilityNode(summary.id);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
for (const entry of cachedCatalog) {
|
|
139
|
+
const flagKey = entry.metadata?.vellum?.["feature-flag"];
|
|
140
|
+
if (flagKey && !isAssistantFeatureFlagEnabled(flagKey, config))
|
|
141
|
+
continue;
|
|
142
|
+
seenKeys.add(`${SKILL_SOURCE_PREFIX}${entry.id}`);
|
|
143
|
+
}
|
|
144
|
+
pruneStaleCapabilities(SKILL_SOURCE_PREFIX, seenKeys);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Clean up old-format capability nodes (skill:* and cli:*) that use the
|
|
148
|
+
// legacy "{prefix}:{id}\n..." content format. Mark them as gone so they
|
|
149
|
+
// stop appearing as duplicates. Idempotent — once cleaned, subsequent
|
|
150
|
+
// runs find nothing.
|
|
151
|
+
cleanupOldFormatCapabilityNodes();
|
|
115
152
|
} catch (err) {
|
|
116
153
|
log.warn({ err }, "Failed to seed skill graph nodes");
|
|
117
154
|
}
|
|
@@ -137,6 +174,34 @@ export function seedCliGraphNodes(): void {
|
|
|
137
174
|
}
|
|
138
175
|
}
|
|
139
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Seed capability graph nodes for catalog skills that are not yet installed.
|
|
179
|
+
* This makes uninstalled skills discoverable via memory injection so the LLM
|
|
180
|
+
* can auto-install them via skill_load when relevant.
|
|
181
|
+
* Best-effort: errors are logged but never thrown.
|
|
182
|
+
*/
|
|
183
|
+
export async function seedUninstalledCatalogSkillMemories(): Promise<void> {
|
|
184
|
+
try {
|
|
185
|
+
const fullCatalog = await getCatalog();
|
|
186
|
+
if (fullCatalog.length === 0) return;
|
|
187
|
+
|
|
188
|
+
const installedCatalog = loadSkillCatalog();
|
|
189
|
+
const installedIds = new Set(installedCatalog.map((s) => s.id));
|
|
190
|
+
|
|
191
|
+
const config = getConfig();
|
|
192
|
+
for (const entry of fullCatalog) {
|
|
193
|
+
if (installedIds.has(entry.id)) continue;
|
|
194
|
+
|
|
195
|
+
const flagKey = entry.metadata?.vellum?.["feature-flag"];
|
|
196
|
+
if (flagKey && !isAssistantFeatureFlagEnabled(flagKey, config)) continue;
|
|
197
|
+
|
|
198
|
+
upsertSkillCapabilityNode(entry.id, fromCatalogSkill(entry));
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
log.warn({ err }, "Failed to seed uninstalled catalog skill memories");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
140
205
|
// ---------------------------------------------------------------------------
|
|
141
206
|
// Internal helpers
|
|
142
207
|
// ---------------------------------------------------------------------------
|
|
@@ -172,7 +237,7 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
|
|
|
172
237
|
.where(
|
|
173
238
|
and(
|
|
174
239
|
eq(memoryGraphNodes.scopeId, "default"),
|
|
175
|
-
|
|
240
|
+
eq(memoryGraphNodes.sourceConversations, JSON.stringify([sourceKey])),
|
|
176
241
|
),
|
|
177
242
|
)
|
|
178
243
|
.get();
|
|
@@ -181,9 +246,15 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
|
|
|
181
246
|
|
|
182
247
|
if (existing) {
|
|
183
248
|
if (existing.content === content && existing.fidelity !== "gone") {
|
|
184
|
-
// Same content — just touch lastAccessed
|
|
249
|
+
// Same content — just touch lastAccessed (and backfill lastConsolidated
|
|
250
|
+
// for nodes created before the fix so they don't decay immediately,
|
|
251
|
+
// and backfill significance for nodes created before the raise to 0.6)
|
|
252
|
+
const updates: Record<string, number> = { lastAccessed: now };
|
|
253
|
+
if (existing.lastConsolidated === 0) updates.lastConsolidated = now;
|
|
254
|
+
if (existing.significance < CAPABILITY_SIGNIFICANCE)
|
|
255
|
+
updates.significance = CAPABILITY_SIGNIFICANCE;
|
|
185
256
|
db.update(memoryGraphNodes)
|
|
186
|
-
.set(
|
|
257
|
+
.set(updates)
|
|
187
258
|
.where(eq(memoryGraphNodes.id, existing.id))
|
|
188
259
|
.run();
|
|
189
260
|
return;
|
|
@@ -195,6 +266,7 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
|
|
|
195
266
|
content,
|
|
196
267
|
fidelity: "vivid",
|
|
197
268
|
lastAccessed: now,
|
|
269
|
+
...(existing.lastConsolidated === 0 ? { lastConsolidated: now } : {}),
|
|
198
270
|
})
|
|
199
271
|
.where(eq(memoryGraphNodes.id, existing.id))
|
|
200
272
|
.run();
|
|
@@ -208,7 +280,7 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
|
|
|
208
280
|
type: "procedural" as const,
|
|
209
281
|
created: now,
|
|
210
282
|
lastAccessed: now,
|
|
211
|
-
lastConsolidated:
|
|
283
|
+
lastConsolidated: now,
|
|
212
284
|
eventDate: null,
|
|
213
285
|
emotionalCharge: {
|
|
214
286
|
valence: 0,
|
|
@@ -219,7 +291,7 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
|
|
|
219
291
|
},
|
|
220
292
|
fidelity: "vivid" as const,
|
|
221
293
|
confidence: 1.0,
|
|
222
|
-
significance:
|
|
294
|
+
significance: CAPABILITY_SIGNIFICANCE,
|
|
223
295
|
stability: 1000, // Effectively permanent — never decays
|
|
224
296
|
reinforcementCount: 0,
|
|
225
297
|
lastReinforced: now,
|
|
@@ -246,7 +318,7 @@ function deleteCapabilityNode(sourceKey: string): void {
|
|
|
246
318
|
.where(
|
|
247
319
|
and(
|
|
248
320
|
eq(memoryGraphNodes.scopeId, "default"),
|
|
249
|
-
|
|
321
|
+
eq(memoryGraphNodes.sourceConversations, JSON.stringify([sourceKey])),
|
|
250
322
|
),
|
|
251
323
|
)
|
|
252
324
|
.get();
|
|
@@ -256,6 +328,79 @@ function deleteCapabilityNode(sourceKey: string): void {
|
|
|
256
328
|
.set({ fidelity: "gone", lastAccessed: Date.now() })
|
|
257
329
|
.where(eq(memoryGraphNodes.id, existing.id))
|
|
258
330
|
.run();
|
|
331
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
332
|
+
targetType: "graph_node",
|
|
333
|
+
targetId: existing.id,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Find and soft-delete old-format capability memory nodes (skill:* and cli:*).
|
|
340
|
+
*
|
|
341
|
+
* The legacy system stored content as "skill:{id}\n{statement}" or
|
|
342
|
+
* "cli:{command}\n{statement}". The current system uses prose format.
|
|
343
|
+
* This marks any remaining old-format nodes as gone so they no longer
|
|
344
|
+
* surface in retrieval.
|
|
345
|
+
*/
|
|
346
|
+
function cleanupOldFormatCapabilityNodes(): void {
|
|
347
|
+
const db = getDb();
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
|
|
350
|
+
// --- skill:* old-format nodes ---
|
|
351
|
+
const oldFormatNodes = db
|
|
352
|
+
.select()
|
|
353
|
+
.from(memoryGraphNodes)
|
|
354
|
+
.where(
|
|
355
|
+
and(
|
|
356
|
+
eq(memoryGraphNodes.type, "procedural"),
|
|
357
|
+
eq(memoryGraphNodes.scopeId, "default"),
|
|
358
|
+
sql`${memoryGraphNodes.fidelity} != 'gone'`,
|
|
359
|
+
sql`${memoryGraphNodes.content} LIKE 'skill:%'`,
|
|
360
|
+
),
|
|
361
|
+
)
|
|
362
|
+
.all();
|
|
363
|
+
|
|
364
|
+
for (const node of oldFormatNodes) {
|
|
365
|
+
// Verify this is truly old-format: "skill:{id}\n..."
|
|
366
|
+
if (!/^skill:\S+\n/.test(node.content)) continue;
|
|
367
|
+
|
|
368
|
+
db.update(memoryGraphNodes)
|
|
369
|
+
.set({ fidelity: "gone", lastAccessed: now })
|
|
370
|
+
.where(eq(memoryGraphNodes.id, node.id))
|
|
371
|
+
.run();
|
|
372
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
373
|
+
targetType: "graph_node",
|
|
374
|
+
targetId: node.id,
|
|
375
|
+
});
|
|
376
|
+
log.info({ nodeId: node.id }, "Cleaned up old-format skill memory node");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// --- cli:* old-format nodes ---
|
|
380
|
+
const oldCliNodes = db
|
|
381
|
+
.select()
|
|
382
|
+
.from(memoryGraphNodes)
|
|
383
|
+
.where(
|
|
384
|
+
and(
|
|
385
|
+
eq(memoryGraphNodes.type, "procedural"),
|
|
386
|
+
eq(memoryGraphNodes.scopeId, "default"),
|
|
387
|
+
sql`${memoryGraphNodes.fidelity} != 'gone'`,
|
|
388
|
+
sql`${memoryGraphNodes.content} LIKE 'cli:%'`,
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
.all();
|
|
392
|
+
|
|
393
|
+
for (const node of oldCliNodes) {
|
|
394
|
+
if (!/^cli:\S+\n/.test(node.content)) continue;
|
|
395
|
+
db.update(memoryGraphNodes)
|
|
396
|
+
.set({ fidelity: "gone", lastAccessed: now })
|
|
397
|
+
.where(eq(memoryGraphNodes.id, node.id))
|
|
398
|
+
.run();
|
|
399
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
400
|
+
targetType: "graph_node",
|
|
401
|
+
targetId: node.id,
|
|
402
|
+
});
|
|
403
|
+
log.info({ nodeId: node.id }, "Cleaned up old-format CLI memory node");
|
|
259
404
|
}
|
|
260
405
|
}
|
|
261
406
|
|
|
@@ -284,11 +429,18 @@ function pruneStaleCapabilities(prefix: string, activeKeys: Set<string>): void {
|
|
|
284
429
|
const sources = JSON.parse(row.sourceConversations as string);
|
|
285
430
|
const key = Array.isArray(sources) ? sources[0] : null;
|
|
286
431
|
if (key && typeof key === "string" && !activeKeys.has(key)) {
|
|
287
|
-
log.info(
|
|
432
|
+
log.info(
|
|
433
|
+
{ sourceKey: key, nodeId: row.id },
|
|
434
|
+
"Pruning stale capability graph node",
|
|
435
|
+
);
|
|
288
436
|
db.update(memoryGraphNodes)
|
|
289
437
|
.set({ fidelity: "gone", lastAccessed: now })
|
|
290
438
|
.where(eq(memoryGraphNodes.id, row.id))
|
|
291
439
|
.run();
|
|
440
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
441
|
+
targetType: "graph_node",
|
|
442
|
+
targetId: row.id,
|
|
443
|
+
});
|
|
292
444
|
}
|
|
293
445
|
} catch {
|
|
294
446
|
// Skip malformed JSON
|
|
@@ -19,13 +19,16 @@ import {
|
|
|
19
19
|
} from "../../providers/provider-send-message.js";
|
|
20
20
|
import { BackendUnavailableError } from "../../util/errors.js";
|
|
21
21
|
import { getLogger } from "../../util/logger.js";
|
|
22
|
+
import { getDb } from "../db.js";
|
|
22
23
|
import { parseEpochMs } from "./extraction.js";
|
|
23
24
|
import {
|
|
24
25
|
createTrigger,
|
|
26
|
+
deduplicateParagraphs,
|
|
25
27
|
deleteNode,
|
|
26
28
|
getEdgesForNode,
|
|
27
29
|
getTriggersForNode,
|
|
28
30
|
queryNodes,
|
|
31
|
+
recordNodeEdit,
|
|
29
32
|
updateNode,
|
|
30
33
|
} from "./store.js";
|
|
31
34
|
import type { MemoryNode } from "./types.js";
|
|
@@ -60,7 +63,11 @@ function buildConsolidationPrompt(
|
|
|
60
63
|
? ` eventDate=${new Date(n.eventDate).toISOString().split("T")[0]}`
|
|
61
64
|
: "";
|
|
62
65
|
const imageStr = n.hasImage ? " [has_image]" : "";
|
|
63
|
-
return ` [${n.id}] type=${n.type} sig=${n.significance.toFixed(
|
|
66
|
+
return ` [${n.id}] type=${n.type} sig=${n.significance.toFixed(
|
|
67
|
+
2,
|
|
68
|
+
)} fidelity=${n.fidelity} reinforced=${
|
|
69
|
+
n.reinforcementCount
|
|
70
|
+
}x age=${age}d${eventStr}${imageStr}\n ${n.content}`;
|
|
64
71
|
})
|
|
65
72
|
.join("\n\n");
|
|
66
73
|
|
|
@@ -194,7 +201,9 @@ function getTopSignificanceNodes(
|
|
|
194
201
|
scopeId,
|
|
195
202
|
fidelityNot: ["gone"],
|
|
196
203
|
minSignificance: 0.6,
|
|
197
|
-
})
|
|
204
|
+
})
|
|
205
|
+
.filter((n) => !isCapabilityNode(n))
|
|
206
|
+
.slice(0, n);
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
function getDecayedNodes(scopeId: string): MemoryNode[] {
|
|
@@ -202,7 +211,10 @@ function getDecayedNodes(scopeId: string): MemoryNode[] {
|
|
|
202
211
|
scopeId,
|
|
203
212
|
limit: 10000,
|
|
204
213
|
});
|
|
205
|
-
return all.filter(
|
|
214
|
+
return all.filter(
|
|
215
|
+
(n) =>
|
|
216
|
+
(n.fidelity === "faded" || n.fidelity === "gist") && !isCapabilityNode(n),
|
|
217
|
+
);
|
|
206
218
|
}
|
|
207
219
|
|
|
208
220
|
function getRandomSample(scopeId: string, n: number = 30): MemoryNode[] {
|
|
@@ -506,8 +518,30 @@ async function consolidateChunk(
|
|
|
506
518
|
|
|
507
519
|
if (Object.keys(changes).length > 1) {
|
|
508
520
|
// more than just lastConsolidated
|
|
509
|
-
|
|
521
|
+
|
|
522
|
+
// Wrap edit recording + node update in a transaction so they are atomic:
|
|
523
|
+
// if updateNode fails, the edit record is rolled back.
|
|
524
|
+
getDb().transaction(() => {
|
|
525
|
+
if (changes.content) {
|
|
526
|
+
const cleanContent = deduplicateParagraphs(changes.content);
|
|
527
|
+
const node = nodeMap.get(update.id);
|
|
528
|
+
if (node && node.content !== cleanContent) {
|
|
529
|
+
recordNodeEdit({
|
|
530
|
+
nodeId: update.id,
|
|
531
|
+
previousContent: node.content,
|
|
532
|
+
newContent: cleanContent,
|
|
533
|
+
source: "consolidation",
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
updateNode(update.id, changes);
|
|
539
|
+
});
|
|
510
540
|
result.nodesUpdated++;
|
|
541
|
+
// Sync in-memory state with what updateNode actually wrote to the DB
|
|
542
|
+
// (updateNode deduplicates content before persisting)
|
|
543
|
+
if (changes.content)
|
|
544
|
+
changes.content = deduplicateParagraphs(changes.content);
|
|
511
545
|
const node = nodeMap.get(update.id);
|
|
512
546
|
if (node) Object.assign(node, changes);
|
|
513
547
|
}
|