agent-working-memory 0.7.16 → 0.8.5
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/README.md +156 -15
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +2 -16
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +2 -11
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/common.d.ts +18 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +254 -141
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/cursor.js +2 -15
- package/dist/adapters/cursor.js.map +1 -1
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +28 -34
- package/dist/adapters/http.js.map +1 -1
- package/dist/api/routes.d.ts +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +280 -113
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.d.ts +42 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +268 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli.js +45 -1
- package/dist/cli.js.map +1 -1
- package/dist/coordination/circuit-breaker.d.ts +24 -0
- package/dist/coordination/circuit-breaker.d.ts.map +1 -0
- package/dist/coordination/circuit-breaker.js +70 -0
- package/dist/coordination/circuit-breaker.js.map +1 -0
- package/dist/coordination/failure-modes.d.ts +21 -0
- package/dist/coordination/failure-modes.d.ts.map +1 -0
- package/dist/coordination/failure-modes.js +49 -0
- package/dist/coordination/failure-modes.js.map +1 -0
- package/dist/coordination/peer-decisions.js +7 -7
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +44 -1
- package/dist/coordination/routes.js.map +1 -1
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +18 -0
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/schemas.d.ts +9 -9
- package/dist/coordination/stale.d.ts +9 -1
- package/dist/coordination/stale.d.ts.map +1 -1
- package/dist/coordination/stale.js +53 -9
- package/dist/coordination/stale.js.map +1 -1
- package/dist/core/embeddings.d.ts +16 -10
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +54 -38
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/ml-worker-entry.d.ts +17 -0
- package/dist/core/ml-worker-entry.d.ts.map +1 -0
- package/dist/core/ml-worker-entry.js +193 -0
- package/dist/core/ml-worker-entry.js.map +1 -0
- package/dist/core/ml-worker.d.ts +59 -0
- package/dist/core/ml-worker.d.ts.map +1 -0
- package/dist/core/ml-worker.js +253 -0
- package/dist/core/ml-worker.js.map +1 -0
- package/dist/core/query-expander.d.ts +13 -15
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +48 -54
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.d.ts +9 -19
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +33 -35
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.d.ts +32 -5
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +201 -28
- package/dist/core/salience.js.map +1 -1
- package/dist/core/write-pipeline.d.ts +128 -0
- package/dist/core/write-pipeline.d.ts.map +1 -0
- package/dist/core/write-pipeline.js +405 -0
- package/dist/core/write-pipeline.js.map +1 -0
- package/dist/engine/activation.d.ts +2 -2
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +238 -115
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/confidence.d.ts +62 -0
- package/dist/engine/confidence.d.ts.map +1 -0
- package/dist/engine/confidence.js +100 -0
- package/dist/engine/confidence.js.map +1 -0
- package/dist/engine/connections.d.ts +47 -8
- package/dist/engine/connections.d.ts.map +1 -1
- package/dist/engine/connections.js +83 -22
- package/dist/engine/connections.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +46 -10
- package/dist/engine/consolidation-scheduler.d.ts.map +1 -1
- package/dist/engine/consolidation-scheduler.js +170 -50
- package/dist/engine/consolidation-scheduler.js.map +1 -1
- package/dist/engine/consolidation.d.ts +16 -2
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +143 -44
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts +2 -2
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +9 -9
- package/dist/engine/eval.js.map +1 -1
- package/dist/engine/eviction.d.ts +4 -4
- package/dist/engine/eviction.d.ts.map +1 -1
- package/dist/engine/eviction.js +15 -15
- package/dist/engine/eviction.js.map +1 -1
- package/dist/engine/retraction.d.ts +93 -5
- package/dist/engine/retraction.d.ts.map +1 -1
- package/dist/engine/retraction.js +238 -19
- package/dist/engine/retraction.js.map +1 -1
- package/dist/engine/staging.d.ts +1 -1
- package/dist/engine/staging.d.ts.map +1 -1
- package/dist/engine/staging.js +3 -3
- package/dist/engine/staging.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +1 -1
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +4 -4
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +48 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +110 -155
- package/dist/mcp.js.map +1 -1
- package/dist/storage/factory.d.ts +55 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +135 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts +20 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -0
- package/dist/storage/pglite-schema.js +165 -0
- package/dist/storage/pglite-schema.js.map +1 -0
- package/dist/storage/pglite.d.ts +187 -0
- package/dist/storage/pglite.d.ts.map +1 -0
- package/dist/storage/pglite.js +1114 -0
- package/dist/storage/pglite.js.map +1 -0
- package/dist/storage/sqlite.d.ts +117 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +357 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/store.d.ts +63 -0
- package/dist/storage/store.d.ts.map +1 -0
- package/dist/storage/store.js +27 -0
- package/dist/storage/store.js.map +1 -0
- package/dist/types/engram.d.ts +92 -2
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +3 -1
- package/src/adapters/claude-code.ts +218 -234
- package/src/adapters/codex.ts +252 -262
- package/src/adapters/common.ts +411 -284
- package/src/adapters/cursor.ts +135 -150
- package/src/adapters/http.ts +93 -100
- package/src/adapters/index.ts +31 -31
- package/src/adapters/types.ts +75 -75
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +367 -120
- package/src/cli/migrate.ts +307 -0
- package/src/cli.ts +37 -1
- package/src/coordination/circuit-breaker.ts +83 -0
- package/src/coordination/events.ts +90 -90
- package/src/coordination/failure-modes.ts +50 -0
- package/src/coordination/peer-decisions.ts +105 -105
- package/src/coordination/plugin-loader.ts +60 -60
- package/src/coordination/plugin.ts +44 -44
- package/src/coordination/routes.ts +56 -1
- package/src/coordination/schema.ts +13 -0
- package/src/coordination/stale.ts +80 -11
- package/src/coordination/types.ts +311 -311
- package/src/coordination/write-mutex.ts +69 -69
- package/src/core/auto-tagger.ts +168 -168
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -93
- package/src/core/index.ts +5 -5
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -0
- package/src/core/ml-worker.ts +281 -0
- package/src/core/query-expander.ts +122 -128
- package/src/core/reranker.ts +119 -125
- package/src/core/salience.ts +200 -33
- package/src/core/write-pipeline.ts +521 -0
- package/src/engine/activation.ts +235 -109
- package/src/engine/confidence.ts +120 -0
- package/src/engine/connections.ts +162 -103
- package/src/engine/consolidation-scheduler.ts +242 -125
- package/src/engine/consolidation.ts +138 -45
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -116
- package/src/engine/staging.ts +74 -74
- package/src/hooks/sidecar.ts +5 -5
- package/src/index.ts +42 -28
- package/src/mcp.ts +105 -152
- package/src/storage/factory.ts +147 -0
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -0
- package/src/storage/pglite.ts +1363 -0
- package/src/storage/sqlite.ts +402 -7
- package/src/storage/store.ts +80 -0
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/engram.ts +101 -3
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
package/dist/api/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,IAAI,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AASnF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,EAAE,UAAU,CAAC;IACvB,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,sBAAsB,EAAE,sBAAsB,CAAC;CAChD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CA04B3E"}
|
package/dist/api/routes.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* POST /system/consolidate — run sleep cycle (strengthen, decay, sweep)
|
|
33
33
|
* GET /health — health check
|
|
34
34
|
*/
|
|
35
|
-
import {
|
|
35
|
+
import { performWrite } from '../core/write-pipeline.js';
|
|
36
36
|
import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
|
|
37
37
|
import { embed, embedBatch } from '../core/embeddings.js';
|
|
38
38
|
export function registerRoutes(app, deps) {
|
|
@@ -47,21 +47,6 @@ export function registerRoutes(app, deps) {
|
|
|
47
47
|
!body.content || typeof body.content !== 'string') {
|
|
48
48
|
return reply.status(400).send({ error: 'agentId, concept, and content are required strings' });
|
|
49
49
|
}
|
|
50
|
-
const novelty = computeNovelty(store, body.agentId, body.concept, body.content);
|
|
51
|
-
const salience = evaluateSalience({
|
|
52
|
-
content: body.content,
|
|
53
|
-
eventType: body.eventType,
|
|
54
|
-
surprise: body.surprise,
|
|
55
|
-
decisionMade: body.decisionMade,
|
|
56
|
-
causalDepth: body.causalDepth,
|
|
57
|
-
resolutionEffort: body.resolutionEffort,
|
|
58
|
-
novelty,
|
|
59
|
-
});
|
|
60
|
-
// v0.5.4: No longer discard — store with low confidence for ranking.
|
|
61
|
-
const isLowSalience = salience.disposition === 'discard';
|
|
62
|
-
const confidence = isLowSalience
|
|
63
|
-
? 0.25
|
|
64
|
-
: body.confidence ?? (salience.disposition === 'staging' ? 0.40 : 0.50);
|
|
65
50
|
// Assemble tags: user-provided + agent metadata
|
|
66
51
|
const userTags = body.tags ?? [];
|
|
67
52
|
const metaTags = [];
|
|
@@ -77,59 +62,90 @@ export function registerRoutes(app, deps) {
|
|
|
77
62
|
metaTags.push(`sid=${body.sessionId}`);
|
|
78
63
|
if (body.intent)
|
|
79
64
|
metaTags.push(`intent=${body.intent}`);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
65
|
+
// Resolve references (0.8 Cluster D): if matchConcept is given without
|
|
66
|
+
// matchEngramId, look up the most recent active engram with that concept
|
|
67
|
+
// (+ optional matchTags) and store both. Stable link survives concept
|
|
68
|
+
// edits. No match found → store just matchConcept so the intent
|
|
69
|
+
// (link-to-future or link-to-deleted) is preserved.
|
|
70
|
+
const resolvedRefs = await Promise.all((body.references ?? []).map(async (ref) => {
|
|
71
|
+
if (!ref.matchEngramId && ref.matchConcept) {
|
|
72
|
+
const matched = await store.findActiveMatchByConcept(body.agentId, ref.matchConcept, ref.matchTags);
|
|
73
|
+
if (matched) {
|
|
74
|
+
return { type: ref.type, matchEngramId: matched.id, matchConcept: ref.matchConcept };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { type: ref.type, matchEngramId: ref.matchEngramId, matchConcept: ref.matchConcept };
|
|
78
|
+
}));
|
|
79
|
+
const result = await performWrite({ store, connectionEngine }, {
|
|
84
80
|
agentId: body.agentId,
|
|
85
81
|
concept: body.concept,
|
|
86
82
|
content: body.content,
|
|
87
|
-
tags:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
tags: [...userTags, ...metaTags],
|
|
84
|
+
memoryClass: body.memory_class,
|
|
85
|
+
eventType: body.eventType,
|
|
86
|
+
surprise: body.surprise,
|
|
87
|
+
decisionMade: body.decisionMade,
|
|
88
|
+
causalDepth: body.causalDepth,
|
|
89
|
+
resolutionEffort: body.resolutionEffort,
|
|
90
|
+
confidence: body.confidence,
|
|
91
|
+
sequence: body.sequence,
|
|
92
|
+
embed: body.embed,
|
|
93
|
+
references: resolvedRefs.length > 0 ? resolvedRefs : undefined,
|
|
93
94
|
});
|
|
94
|
-
|
|
95
|
-
store.updateStage(engram.id, 'staging');
|
|
96
|
-
}
|
|
97
|
-
// Create temporal adjacency edge to previous memory (conversation thread graph)
|
|
98
|
-
// This enables multi-hop graph walk through conversation sequences
|
|
95
|
+
// Auto-checkpoint always (covers create, reinforce, and supersede).
|
|
99
96
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
await store.updateAutoCheckpointWrite(body.agentId, result.engram.id);
|
|
98
|
+
}
|
|
99
|
+
catch { /* non-fatal */ }
|
|
100
|
+
if (result.action === 'reinforce') {
|
|
101
|
+
return reply.code(200).send({
|
|
102
|
+
stored: false,
|
|
103
|
+
action: 'reinforce',
|
|
104
|
+
disposition: 'reinforced',
|
|
105
|
+
engram: result.engram,
|
|
106
|
+
reinforce: result.reinforce,
|
|
107
|
+
novelty: result.noveltyResult.novelty,
|
|
108
|
+
});
|
|
104
109
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
// create / supersede paths follow legacy temporal-edge + episode logic.
|
|
111
|
+
// Structural-class writes (0.8 Cluster A) skip these: they're system-written
|
|
112
|
+
// event-log records, not conversational beats, so keeping them out of the
|
|
113
|
+
// temporal graph + episode index keeps cognitive retrieval clean.
|
|
114
|
+
const isStructural = body.memory_class === 'structural';
|
|
115
|
+
if (!isStructural) {
|
|
109
116
|
try {
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
117
|
+
const prev = await store.getLatestEngram(body.agentId, result.engram.id);
|
|
118
|
+
if (prev) {
|
|
119
|
+
await store.upsertAssociation(prev.id, result.engram.id, 0.3, 'temporal', 0.8);
|
|
113
120
|
}
|
|
114
|
-
store.addEngramToEpisode(engram.id, episode.id);
|
|
115
121
|
}
|
|
116
|
-
catch { /*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
catch { /* Temporal edge creation is non-fatal */ }
|
|
123
|
+
if (result.salience
|
|
124
|
+
&& (result.salience.disposition === 'active' || result.salience.disposition === 'discard')) {
|
|
125
|
+
try {
|
|
126
|
+
let episode = await store.getActiveEpisode(body.agentId, 3600_000);
|
|
127
|
+
if (!episode) {
|
|
128
|
+
episode = await store.createEpisode({ agentId: body.agentId, label: body.concept });
|
|
129
|
+
}
|
|
130
|
+
await store.addEngramToEpisode(result.engram.id, episode.id);
|
|
131
|
+
}
|
|
132
|
+
catch { /* Episode assignment is non-fatal */ }
|
|
133
|
+
}
|
|
125
134
|
}
|
|
126
|
-
|
|
135
|
+
const isLowSalience = result.salience?.disposition === 'discard';
|
|
127
136
|
return reply.code(201).send({
|
|
128
137
|
stored: true,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
action: result.action,
|
|
139
|
+
// disposition: legacy field — 'low-salience' is returned for discard so
|
|
140
|
+
// callers know the engram was kept but marked low-value. The raw inner
|
|
141
|
+
// salience decision is exposed as `salienceDisposition` for callers
|
|
142
|
+
// (and tests) that want the unmapped value.
|
|
143
|
+
disposition: isLowSalience ? 'low-salience' : (result.salience?.disposition ?? 'active'),
|
|
144
|
+
salienceDisposition: result.salience?.disposition ?? null,
|
|
145
|
+
salience: result.salience?.score ?? 0,
|
|
146
|
+
reasonCodes: result.salience?.reasonCodes ?? [],
|
|
147
|
+
engram: result.engram,
|
|
148
|
+
supersedeOf: result.supersedeOf,
|
|
133
149
|
});
|
|
134
150
|
});
|
|
135
151
|
/**
|
|
@@ -149,7 +165,7 @@ export function registerRoutes(app, deps) {
|
|
|
149
165
|
const memTags = [...(mem.tags ?? [])];
|
|
150
166
|
if (sid)
|
|
151
167
|
memTags.push(`sid=${sid}`);
|
|
152
|
-
const engram = store.createEngram({
|
|
168
|
+
const engram = await store.createEngram({
|
|
153
169
|
agentId: body.agentId,
|
|
154
170
|
concept: mem.concept,
|
|
155
171
|
content: mem.content,
|
|
@@ -160,9 +176,9 @@ export function registerRoutes(app, deps) {
|
|
|
160
176
|
});
|
|
161
177
|
// Handle supersession inline — archive superseded memory to remove from active pool
|
|
162
178
|
if (mem.supersedes) {
|
|
163
|
-
store.supersedeEngram(mem.supersedes, engram.id);
|
|
164
|
-
store.updateConfidence(mem.supersedes, 0.1);
|
|
165
|
-
store.updateStage(mem.supersedes, 'archived'); // Remove from active search pool
|
|
179
|
+
await store.supersedeEngram(mem.supersedes, engram.id);
|
|
180
|
+
await store.updateConfidence(mem.supersedes, 0.1);
|
|
181
|
+
await store.updateStage(mem.supersedes, 'archived'); // Remove from active search pool
|
|
166
182
|
}
|
|
167
183
|
results.push({ id: engram.id, concept: mem.concept, disposition: 'active' });
|
|
168
184
|
}
|
|
@@ -172,13 +188,13 @@ export function registerRoutes(app, deps) {
|
|
|
172
188
|
const vecs = await embedBatch(texts);
|
|
173
189
|
for (let i = 0; i < vecs.length; i++) {
|
|
174
190
|
if (results[i]) {
|
|
175
|
-
store.updateEmbedding(results[i].id, vecs[i]);
|
|
191
|
+
await store.updateEmbedding(results[i].id, vecs[i]);
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
}
|
|
179
195
|
catch { /* Embedding failure is non-fatal */ }
|
|
180
196
|
try {
|
|
181
|
-
store.updateAutoCheckpointWrite(body.agentId, results[results.length - 1]?.id ?? '');
|
|
197
|
+
await store.updateAutoCheckpointWrite(body.agentId, results[results.length - 1]?.id ?? '');
|
|
182
198
|
}
|
|
183
199
|
catch { }
|
|
184
200
|
return reply.code(201).send({
|
|
@@ -197,33 +213,39 @@ export function registerRoutes(app, deps) {
|
|
|
197
213
|
useReranker: body.useReranker,
|
|
198
214
|
useExpansion: body.useExpansion,
|
|
199
215
|
abstentionThreshold: body.abstentionThreshold,
|
|
216
|
+
requireConfidence: body.requireConfidence,
|
|
200
217
|
workspace: body.workspace,
|
|
201
218
|
bm25Only: body.bm25Only,
|
|
219
|
+
granularity: body.granularity,
|
|
202
220
|
});
|
|
203
221
|
// Auto-checkpoint: track recall for consolidation scheduling
|
|
204
222
|
try {
|
|
205
223
|
const ids = results.map(r => r.engram.id);
|
|
206
|
-
store.updateAutoCheckpointRecall(body.agentId, body.context, ids);
|
|
224
|
+
await store.updateAutoCheckpointRecall(body.agentId, body.context, ids);
|
|
207
225
|
}
|
|
208
226
|
catch { /* non-fatal */ }
|
|
209
|
-
|
|
227
|
+
// Surface recall confidence as a top-level field too — same value is on
|
|
228
|
+
// every result, but it describes the recall as a whole, so exposing it
|
|
229
|
+
// once is easier for consumers (and lets them inspect 0-result recalls).
|
|
230
|
+
const confidence = results[0]?.confidence ?? 0;
|
|
231
|
+
return reply.send({ results, confidence });
|
|
210
232
|
});
|
|
211
233
|
app.post('/memory/feedback', async (req, reply) => {
|
|
212
234
|
const body = req.body;
|
|
213
|
-
store.logRetrievalFeedback(body.activationEventId ?? null, body.engramId, body.useful, body.context ?? '');
|
|
235
|
+
await store.logRetrievalFeedback(body.activationEventId ?? null, body.engramId, body.useful, body.context ?? '');
|
|
214
236
|
// Update engram confidence based on feedback
|
|
215
|
-
const engram = store.getEngram(body.engramId);
|
|
237
|
+
const engram = await store.getEngram(body.engramId);
|
|
216
238
|
if (engram) {
|
|
217
239
|
const config = DEFAULT_AGENT_CONFIG;
|
|
218
240
|
const delta = body.useful
|
|
219
241
|
? config.feedbackPositiveBoost
|
|
220
242
|
: -config.feedbackNegativePenalty;
|
|
221
|
-
store.updateConfidence(engram.id, engram.confidence + delta);
|
|
243
|
+
await store.updateConfidence(engram.id, engram.confidence + delta);
|
|
222
244
|
}
|
|
223
245
|
// Touch activity for consolidation scheduling
|
|
224
246
|
if (engram) {
|
|
225
247
|
try {
|
|
226
|
-
store.touchActivity(engram.agentId);
|
|
248
|
+
await store.touchActivity(engram.agentId);
|
|
227
249
|
}
|
|
228
250
|
catch { /* non-fatal */ }
|
|
229
251
|
}
|
|
@@ -231,7 +253,7 @@ export function registerRoutes(app, deps) {
|
|
|
231
253
|
});
|
|
232
254
|
app.post('/memory/retract', async (req, reply) => {
|
|
233
255
|
const body = req.body;
|
|
234
|
-
const result = retractionEngine.retract({
|
|
256
|
+
const result = await retractionEngine.retract({
|
|
235
257
|
agentId: body.agentId,
|
|
236
258
|
targetEngramId: body.targetEngramId,
|
|
237
259
|
reason: body.reason,
|
|
@@ -239,33 +261,83 @@ export function registerRoutes(app, deps) {
|
|
|
239
261
|
});
|
|
240
262
|
// Touch activity for consolidation scheduling
|
|
241
263
|
try {
|
|
242
|
-
store.touchActivity(body.agentId);
|
|
264
|
+
await store.touchActivity(body.agentId);
|
|
243
265
|
}
|
|
244
266
|
catch { /* non-fatal */ }
|
|
245
267
|
return reply.send(result);
|
|
246
268
|
});
|
|
247
269
|
app.post('/memory/supersede', async (req, reply) => {
|
|
248
270
|
const body = req.body;
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
return reply.code(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
const isFormA = !!(body.oldEngramId && body.newEngramId);
|
|
272
|
+
const isFormB = !!(body.matchConcept && body.newEngram && body.agentId);
|
|
273
|
+
if (isFormA && isFormB) {
|
|
274
|
+
return reply.code(400).send({
|
|
275
|
+
error: 'Pass either {oldEngramId, newEngramId} (Form A) OR ' +
|
|
276
|
+
'{agentId, matchConcept, newEngram} (Form B), not both.',
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (!isFormA && !isFormB) {
|
|
280
|
+
return reply.code(400).send({
|
|
281
|
+
error: 'Missing required fields. Form A: {oldEngramId, newEngramId}. ' +
|
|
282
|
+
'Form B: {agentId, matchConcept, newEngram}.',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// ── Form A — by IDs ──
|
|
286
|
+
if (isFormA) {
|
|
287
|
+
const oldEngram = await store.getEngram(body.oldEngramId);
|
|
288
|
+
const newEngram = await store.getEngram(body.newEngramId);
|
|
289
|
+
if (!oldEngram)
|
|
290
|
+
return reply.code(404).send({ error: `Old engram ${body.oldEngramId} not found` });
|
|
291
|
+
if (!newEngram)
|
|
292
|
+
return reply.code(404).send({ error: `New engram ${body.newEngramId} not found` });
|
|
293
|
+
await store.upsertAssociation(body.newEngramId, body.oldEngramId, 0.8, 'causal', 1.0);
|
|
294
|
+
await store.updateConfidence(body.oldEngramId, oldEngram.confidence * 0.2);
|
|
295
|
+
await store.supersedeEngram(body.oldEngramId, body.newEngramId);
|
|
296
|
+
try {
|
|
297
|
+
await store.touchActivity(oldEngram.agentId);
|
|
298
|
+
}
|
|
299
|
+
catch { /* non-fatal */ }
|
|
300
|
+
return reply.send({
|
|
301
|
+
superseded: body.oldEngramId,
|
|
302
|
+
supersededBy: body.newEngramId,
|
|
303
|
+
reason: body.reason ?? 'outdated',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
// ── Form B — atomic write-and-supersede by concept match ──
|
|
307
|
+
// Find old (most recent active match by concept + optional tags), write
|
|
308
|
+
// new engram via performWrite, link them — all in one SQL transaction.
|
|
309
|
+
// If no match: write new anyway, return { superseded: null }.
|
|
310
|
+
// AWM 0.8.x P4b follow-up: Form B atomicity via withTransaction.
|
|
311
|
+
// Holds the SQLite/PGlite lock across the async write + supersede pair
|
|
312
|
+
// so callers never observe a half-completed state.
|
|
313
|
+
const result = await store.withTransaction(async () => {
|
|
314
|
+
const matched = await store.findActiveMatchByConcept(body.agentId, body.matchConcept, body.matchTags);
|
|
315
|
+
const writeRes = await performWrite({ store, connectionEngine }, {
|
|
316
|
+
agentId: body.agentId,
|
|
317
|
+
concept: body.newEngram.concept,
|
|
318
|
+
content: body.newEngram.content,
|
|
319
|
+
tags: body.newEngram.tags ?? [],
|
|
320
|
+
memoryClass: body.newEngram.memory_class,
|
|
321
|
+
sequence: body.newEngram.sequence,
|
|
322
|
+
eventType: body.newEngram.eventType,
|
|
323
|
+
enableReinforcement: false,
|
|
324
|
+
});
|
|
325
|
+
if (matched) {
|
|
326
|
+
await store.upsertAssociation(writeRes.engram.id, matched.id, 0.8, 'causal', 1.0);
|
|
327
|
+
await store.updateConfidence(matched.id, matched.confidence * 0.2);
|
|
328
|
+
await store.supersedeEngram(matched.id, writeRes.engram.id);
|
|
329
|
+
}
|
|
330
|
+
return { writeRes, matched };
|
|
331
|
+
});
|
|
261
332
|
try {
|
|
262
|
-
store.touchActivity(
|
|
333
|
+
await store.touchActivity(body.agentId);
|
|
263
334
|
}
|
|
264
335
|
catch { /* non-fatal */ }
|
|
265
|
-
return reply.send({
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
336
|
+
return reply.code(201).send({
|
|
337
|
+
newEngram: result.writeRes.engram,
|
|
338
|
+
superseded: result.matched ? result.matched.id : null,
|
|
339
|
+
supersededBy: result.writeRes.engram.id,
|
|
340
|
+
reason: body.reason ?? 'resolved by concept match',
|
|
269
341
|
});
|
|
270
342
|
});
|
|
271
343
|
// ============================================================
|
|
@@ -273,32 +345,122 @@ export function registerRoutes(app, deps) {
|
|
|
273
345
|
// ============================================================
|
|
274
346
|
app.post('/memory/search', async (req, reply) => {
|
|
275
347
|
const body = req.body;
|
|
276
|
-
const results = store.search({
|
|
348
|
+
const results = await store.search({
|
|
277
349
|
agentId: body.agentId,
|
|
278
350
|
text: body.text,
|
|
279
351
|
concept: body.concept,
|
|
280
352
|
tags: body.tags,
|
|
353
|
+
tagsAll: body.tagsAll,
|
|
354
|
+
tagsAny: body.tagsAny,
|
|
355
|
+
tagsNone: body.tagsNone,
|
|
281
356
|
stage: body.stage,
|
|
282
357
|
retracted: body.retracted,
|
|
283
358
|
limit: body.limit,
|
|
284
359
|
offset: body.offset,
|
|
360
|
+
sortBy: body.sortBy,
|
|
361
|
+
sortOrder: body.sortOrder,
|
|
285
362
|
});
|
|
286
363
|
return reply.send({ results, count: results.length });
|
|
287
364
|
});
|
|
365
|
+
// ============================================================
|
|
366
|
+
// 0.8 Cluster C — materialized-view + atomic-counter endpoints
|
|
367
|
+
// ============================================================
|
|
368
|
+
/**
|
|
369
|
+
* For each distinct value of `tagKey`, return the most recent active
|
|
370
|
+
* engram. Used by NovelForge for "latest emotional state per character",
|
|
371
|
+
* "latest motif phase per motif", etc.
|
|
372
|
+
*/
|
|
373
|
+
app.post('/memory/latest-by-tag', async (req, reply) => {
|
|
374
|
+
const body = req.body;
|
|
375
|
+
if (!body.agentId || !body.tagKey) {
|
|
376
|
+
return reply.code(400).send({ error: 'agentId and tagKey are required' });
|
|
377
|
+
}
|
|
378
|
+
const results = await store.getLatestByTag({
|
|
379
|
+
agentId: body.agentId,
|
|
380
|
+
tagKeyPrefix: body.tagKey,
|
|
381
|
+
scopeTagsAll: body.scopeTagsAll,
|
|
382
|
+
retracted: body.retracted ?? false,
|
|
383
|
+
sortBy: body.sortBy,
|
|
384
|
+
limit: body.limit,
|
|
385
|
+
});
|
|
386
|
+
return reply.send({ results, count: results.length });
|
|
387
|
+
});
|
|
388
|
+
/**
|
|
389
|
+
* Filter by tag-set operators, sort by numeric value extracted from a
|
|
390
|
+
* tag prefix, return top N. Used by NovelForge for "top N active
|
|
391
|
+
* promises by weight".
|
|
392
|
+
*/
|
|
393
|
+
app.post('/memory/top-by', async (req, reply) => {
|
|
394
|
+
const body = req.body;
|
|
395
|
+
if (!body.agentId || !body.sortField) {
|
|
396
|
+
return reply.code(400).send({ error: 'agentId and sortField are required' });
|
|
397
|
+
}
|
|
398
|
+
const results = await store.getTopBy({
|
|
399
|
+
agentId: body.agentId,
|
|
400
|
+
sortField: body.sortField,
|
|
401
|
+
order: body.order ?? 'desc',
|
|
402
|
+
filterTagsAll: body.filterTagsAll,
|
|
403
|
+
filterTagsAny: body.filterTagsAny,
|
|
404
|
+
filterTagsNone: body.filterTagsNone,
|
|
405
|
+
retracted: body.retracted ?? false,
|
|
406
|
+
limit: body.limit,
|
|
407
|
+
});
|
|
408
|
+
return reply.send({ results, count: results.length });
|
|
409
|
+
});
|
|
410
|
+
/**
|
|
411
|
+
* Compute effective state of an engram from referenced events. Two
|
|
412
|
+
* targeting modes: by ID, or by concept match (same semantics as Form B's
|
|
413
|
+
* findActiveMatchByConcept).
|
|
414
|
+
*/
|
|
415
|
+
app.post('/memory/resolve', async (req, reply) => {
|
|
416
|
+
const body = req.body;
|
|
417
|
+
if (!body.agentId) {
|
|
418
|
+
return reply.code(400).send({ error: 'agentId is required' });
|
|
419
|
+
}
|
|
420
|
+
let targetId = body.targetEngramId;
|
|
421
|
+
if (!targetId && body.matchConcept) {
|
|
422
|
+
const matched = await store.findActiveMatchByConcept(body.agentId, body.matchConcept, body.matchTags);
|
|
423
|
+
if (!matched) {
|
|
424
|
+
return reply.code(404).send({
|
|
425
|
+
error: `No active engram matches concept "${body.matchConcept}"`,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
targetId = matched.id;
|
|
429
|
+
}
|
|
430
|
+
if (!targetId) {
|
|
431
|
+
return reply.code(400).send({
|
|
432
|
+
error: 'Provide either targetEngramId or matchConcept',
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const result = await store.resolveEffectiveState(targetId);
|
|
436
|
+
if (!result)
|
|
437
|
+
return reply.code(404).send({ error: `Engram ${targetId} not found` });
|
|
438
|
+
return reply.send(result);
|
|
439
|
+
});
|
|
440
|
+
/**
|
|
441
|
+
* Race-free next-sequence allocator. Caller writes the engram with the
|
|
442
|
+
* returned value in `sequence`. Doesn't reserve — concurrent allocations
|
|
443
|
+
* always serialize via BEGIN IMMEDIATE.
|
|
444
|
+
*/
|
|
445
|
+
app.get('/memory/sequence/:agentId/next', async (req, reply) => {
|
|
446
|
+
const { agentId } = req.params;
|
|
447
|
+
const next = await store.allocateNextSequence(agentId);
|
|
448
|
+
return reply.send({ agentId, next });
|
|
449
|
+
});
|
|
288
450
|
app.get('/memory/:id', async (req, reply) => {
|
|
289
451
|
const { id } = req.params;
|
|
290
|
-
const engram = store.getEngram(id);
|
|
452
|
+
const engram = await store.getEngram(id);
|
|
291
453
|
if (!engram)
|
|
292
454
|
return reply.code(404).send({ error: 'Not found' });
|
|
293
|
-
const associations = store.getAssociationsFor(id);
|
|
455
|
+
const associations = await store.getAssociationsFor(id);
|
|
294
456
|
return reply.send({ engram, associations });
|
|
295
457
|
});
|
|
296
458
|
app.get('/agent/:id/stats', async (req, reply) => {
|
|
297
459
|
const { id } = req.params;
|
|
298
|
-
const active = store.getEngramsByAgent(id, 'active');
|
|
299
|
-
const staging = store.getEngramsByAgent(id, 'staging');
|
|
300
|
-
const retracted = store.getEngramsByAgent(id, undefined, true).filter(e => e.retracted);
|
|
301
|
-
const associations = store.getAllAssociations(id);
|
|
460
|
+
const active = await store.getEngramsByAgent(id, 'active');
|
|
461
|
+
const staging = await store.getEngramsByAgent(id, 'staging');
|
|
462
|
+
const retracted = (await store.getEngramsByAgent(id, undefined, true)).filter(e => e.retracted);
|
|
463
|
+
const associations = await store.getAllAssociations(id);
|
|
302
464
|
return reply.send({
|
|
303
465
|
agentId: id,
|
|
304
466
|
engrams: {
|
|
@@ -316,7 +478,7 @@ export function registerRoutes(app, deps) {
|
|
|
316
478
|
app.get('/agent/:id/metrics', async (req, reply) => {
|
|
317
479
|
const { id } = req.params;
|
|
318
480
|
const windowHours = parseInt(req.query.window ?? '24', 10);
|
|
319
|
-
const metrics = evalEngine.computeMetrics(id, windowHours);
|
|
481
|
+
const metrics = await evalEngine.computeMetrics(id, windowHours);
|
|
320
482
|
return reply.send({ metrics });
|
|
321
483
|
});
|
|
322
484
|
app.post('/agent/register', async (req, reply) => {
|
|
@@ -333,12 +495,12 @@ export function registerRoutes(app, deps) {
|
|
|
333
495
|
// ============================================================
|
|
334
496
|
app.post('/system/evict', async (req, reply) => {
|
|
335
497
|
const body = req.body;
|
|
336
|
-
const result = evictionEngine.enforceCapacity(body.agentId, DEFAULT_AGENT_CONFIG);
|
|
498
|
+
const result = await evictionEngine.enforceCapacity(body.agentId, DEFAULT_AGENT_CONFIG);
|
|
337
499
|
return reply.send(result);
|
|
338
500
|
});
|
|
339
501
|
app.post('/system/decay', async (req, reply) => {
|
|
340
502
|
const body = req.body;
|
|
341
|
-
const decayed = evictionEngine.decayEdges(body.agentId, body.halfLifeDays);
|
|
503
|
+
const decayed = await evictionEngine.decayEdges(body.agentId, body.halfLifeDays);
|
|
342
504
|
return reply.send({ edgesDecayed: decayed });
|
|
343
505
|
});
|
|
344
506
|
app.post('/system/consolidate', async (req, reply) => {
|
|
@@ -365,7 +527,7 @@ export function registerRoutes(app, deps) {
|
|
|
365
527
|
});
|
|
366
528
|
app.get('/memory/restore/:agentId', async (req, reply) => {
|
|
367
529
|
const { agentId } = req.params;
|
|
368
|
-
const checkpoint = store.getCheckpoint(agentId);
|
|
530
|
+
const checkpoint = await store.getCheckpoint(agentId);
|
|
369
531
|
const now = Date.now();
|
|
370
532
|
const idleMs = checkpoint
|
|
371
533
|
? now - checkpoint.auto.lastActivityAt.getTime()
|
|
@@ -373,7 +535,7 @@ export function registerRoutes(app, deps) {
|
|
|
373
535
|
// Get last written engram for context
|
|
374
536
|
let lastWrite = null;
|
|
375
537
|
if (checkpoint?.auto.lastWriteId) {
|
|
376
|
-
const engram = store.getEngram(checkpoint.auto.lastWriteId);
|
|
538
|
+
const engram = await store.getEngram(checkpoint.auto.lastWriteId);
|
|
377
539
|
if (engram) {
|
|
378
540
|
lastWrite = { id: engram.id, concept: engram.concept, content: engram.content };
|
|
379
541
|
}
|
|
@@ -423,7 +585,7 @@ export function registerRoutes(app, deps) {
|
|
|
423
585
|
// ============================================================
|
|
424
586
|
app.post('/task/create', async (req, reply) => {
|
|
425
587
|
const body = req.body;
|
|
426
|
-
const engram = store.createEngram({
|
|
588
|
+
const engram = await store.createEngram({
|
|
427
589
|
agentId: body.agentId,
|
|
428
590
|
concept: body.concept,
|
|
429
591
|
content: body.content,
|
|
@@ -440,32 +602,32 @@ export function registerRoutes(app, deps) {
|
|
|
440
602
|
blockedBy: body.blockedBy,
|
|
441
603
|
});
|
|
442
604
|
connectionEngine.enqueue(engram.id);
|
|
443
|
-
embed(`${body.concept} ${body.content}`).then(vec => {
|
|
444
|
-
store.updateEmbedding(engram.id, vec);
|
|
605
|
+
embed(`${body.concept} ${body.content}`).then(async (vec) => {
|
|
606
|
+
await store.updateEmbedding(engram.id, vec);
|
|
445
607
|
}).catch(() => { });
|
|
446
608
|
return reply.send(engram);
|
|
447
609
|
});
|
|
448
610
|
app.post('/task/update', async (req, reply) => {
|
|
449
611
|
const body = req.body;
|
|
450
|
-
const engram = store.getEngram(body.taskId);
|
|
612
|
+
const engram = await store.getEngram(body.taskId);
|
|
451
613
|
if (!engram || !engram.taskStatus) {
|
|
452
614
|
return reply.code(404).send({ error: 'Task not found' });
|
|
453
615
|
}
|
|
454
616
|
if (body.blockedBy !== undefined) {
|
|
455
|
-
store.updateBlockedBy(body.taskId, body.blockedBy);
|
|
617
|
+
await store.updateBlockedBy(body.taskId, body.blockedBy);
|
|
456
618
|
}
|
|
457
619
|
if (body.status) {
|
|
458
|
-
store.updateTaskStatus(body.taskId, body.status);
|
|
620
|
+
await store.updateTaskStatus(body.taskId, body.status);
|
|
459
621
|
}
|
|
460
622
|
if (body.priority) {
|
|
461
|
-
store.updateTaskPriority(body.taskId, body.priority);
|
|
623
|
+
await store.updateTaskPriority(body.taskId, body.priority);
|
|
462
624
|
}
|
|
463
|
-
return reply.send(store.getEngram(body.taskId));
|
|
625
|
+
return reply.send(await store.getEngram(body.taskId));
|
|
464
626
|
});
|
|
465
627
|
app.get('/task/list/:agentId', async (req, reply) => {
|
|
466
628
|
const { agentId } = req.params;
|
|
467
629
|
const { status, includeDone } = req.query;
|
|
468
|
-
let tasks = store.getTasks(agentId, status);
|
|
630
|
+
let tasks = await store.getTasks(agentId, status);
|
|
469
631
|
if (includeDone !== 'true' && !status) {
|
|
470
632
|
tasks = tasks.filter(t => t.taskStatus !== 'done');
|
|
471
633
|
}
|
|
@@ -473,20 +635,25 @@ export function registerRoutes(app, deps) {
|
|
|
473
635
|
});
|
|
474
636
|
app.get('/task/next/:agentId', async (req, reply) => {
|
|
475
637
|
const { agentId } = req.params;
|
|
476
|
-
const next = store.getNextTask(agentId);
|
|
638
|
+
const next = await store.getNextTask(agentId);
|
|
477
639
|
return reply.send(next ? { task: next } : { task: null, message: 'No actionable tasks' });
|
|
478
640
|
});
|
|
479
641
|
// Time warp — shift all timestamps backward by N days (for testing)
|
|
480
642
|
app.post('/system/time-warp', async (req, reply) => {
|
|
481
643
|
const body = req.body;
|
|
482
644
|
const ms = body.days * 24 * 60 * 60 * 1000;
|
|
483
|
-
const shifted = store.timeWarp(body.agentId, ms);
|
|
645
|
+
const shifted = await store.timeWarp(body.agentId, ms);
|
|
484
646
|
return reply.send({ shifted, days: body.days });
|
|
485
647
|
});
|
|
486
648
|
// ─── Export ─────────────────────────────────────────────────────────────
|
|
487
649
|
app.get('/memory/export', async (req, reply) => {
|
|
488
650
|
const { agentId, all } = req.query;
|
|
489
651
|
const includeAll = all === 'true';
|
|
652
|
+
// /memory/export uses raw SQL — SQLite-only. On PGlite, callers should use
|
|
653
|
+
// the awm CLI export/merge tools instead.
|
|
654
|
+
if (typeof store.getDb !== 'function') {
|
|
655
|
+
return reply.code(501).send({ error: 'export endpoint requires the SQLite backend' });
|
|
656
|
+
}
|
|
490
657
|
const db = store.getDb();
|
|
491
658
|
let engramSql = `SELECT id, agent_id, concept, content, confidence, salience, access_count,
|
|
492
659
|
last_accessed, created_at, salience_features, reason_codes, stage, ttl,
|
|
@@ -527,10 +694,10 @@ export function registerRoutes(app, deps) {
|
|
|
527
694
|
const base = {
|
|
528
695
|
status: 'ok',
|
|
529
696
|
timestamp: new Date().toISOString(),
|
|
530
|
-
version: '0.
|
|
697
|
+
version: '0.8.5',
|
|
531
698
|
coordination: coordEnabled,
|
|
532
699
|
};
|
|
533
|
-
if (coordEnabled) {
|
|
700
|
+
if (coordEnabled && typeof deps.store.getDb === 'function') {
|
|
534
701
|
try {
|
|
535
702
|
const db = deps.store.getDb();
|
|
536
703
|
const stats = db.prepare(`SELECT
|