archgraph-argo 0.20.6 → 0.20.8
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.
|
@@ -154,65 +154,83 @@ function createDefaultSemanticRetrieval(dependencies = {}) {
|
|
|
154
154
|
return Object.freeze({
|
|
155
155
|
async retrieve(request = {}) {
|
|
156
156
|
const composition = await resolveRetrievalComposition(dependencies);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
canonicalGraph,
|
|
166
|
-
activeReadinessBoundary,
|
|
167
|
-
);
|
|
168
|
-
if (!evidence.alignment.aligned) {
|
|
169
|
-
await attemptAutomaticAlignment({
|
|
170
|
-
composition,
|
|
171
|
-
request,
|
|
172
|
-
alignment: evidence.alignment,
|
|
173
|
-
});
|
|
174
|
-
evidence = await readAndEvaluatePersistentReadiness(
|
|
157
|
+
try {
|
|
158
|
+
const activeTestComposition = testCompositionStorage.getStore();
|
|
159
|
+
const activeReadinessBoundary = activeTestComposition
|
|
160
|
+
&& activeTestComposition.useReadinessBoundary !== true
|
|
161
|
+
? undefined
|
|
162
|
+
: readinessBoundary;
|
|
163
|
+
let configurationEvidence = await composition.resolveConfiguration();
|
|
164
|
+
let evidence = await readAndEvaluatePersistentReadiness(
|
|
175
165
|
composition,
|
|
176
166
|
canonicalGraph,
|
|
177
167
|
activeReadinessBoundary,
|
|
178
168
|
);
|
|
179
169
|
if (!evidence.alignment.aligned) {
|
|
180
|
-
|
|
170
|
+
await attemptAutomaticAlignment({
|
|
171
|
+
composition,
|
|
172
|
+
request,
|
|
173
|
+
alignment: evidence.alignment,
|
|
174
|
+
});
|
|
175
|
+
evidence = await readAndEvaluatePersistentReadiness(
|
|
176
|
+
composition,
|
|
177
|
+
canonicalGraph,
|
|
178
|
+
activeReadinessBoundary,
|
|
179
|
+
);
|
|
180
|
+
if (!evidence.alignment.aligned) {
|
|
181
|
+
throw semanticAutomaticAlignmentFailed(evidence.alignment);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return await executeWpP2Retrieval({
|
|
185
|
+
composition,
|
|
186
|
+
request,
|
|
187
|
+
canonicalGraph,
|
|
188
|
+
readiness: evidence.readiness,
|
|
189
|
+
configurationEvidence,
|
|
190
|
+
});
|
|
191
|
+
} finally {
|
|
192
|
+
if (typeof composition.dispose === 'function') {
|
|
193
|
+
await composition.dispose();
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
|
-
return executeWpP2Retrieval({
|
|
184
|
-
composition,
|
|
185
|
-
request,
|
|
186
|
-
canonicalGraph,
|
|
187
|
-
readiness: evidence.readiness,
|
|
188
|
-
configurationEvidence,
|
|
189
|
-
});
|
|
190
196
|
},
|
|
191
197
|
async probeQueryability(request = {}, readiness = {}) {
|
|
192
198
|
const composition = await resolveRetrievalComposition(dependencies);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
try {
|
|
200
|
+
const configurationEvidence = await composition.resolveConfiguration();
|
|
201
|
+
return await executeWpP2Retrieval({
|
|
202
|
+
composition,
|
|
203
|
+
request,
|
|
204
|
+
canonicalGraph,
|
|
205
|
+
readiness,
|
|
206
|
+
configurationEvidence,
|
|
207
|
+
});
|
|
208
|
+
} finally {
|
|
209
|
+
if (typeof composition.dispose === 'function') {
|
|
210
|
+
await composition.dispose();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
201
213
|
},
|
|
202
214
|
async readReadiness() {
|
|
203
215
|
const composition = await resolveRetrievalComposition(dependencies);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
try {
|
|
217
|
+
const activeTestComposition = testCompositionStorage.getStore();
|
|
218
|
+
const activeReadinessBoundary = activeTestComposition
|
|
219
|
+
&& activeTestComposition.useReadinessBoundary !== true
|
|
220
|
+
? undefined
|
|
221
|
+
: readinessBoundary;
|
|
222
|
+
await composition.resolveConfiguration();
|
|
223
|
+
const evidence = await readAndEvaluatePersistentReadiness(
|
|
224
|
+
composition,
|
|
225
|
+
canonicalGraph,
|
|
226
|
+
activeReadinessBoundary,
|
|
227
|
+
);
|
|
228
|
+
return publicReadinessOutcome(evidence.alignment);
|
|
229
|
+
} finally {
|
|
230
|
+
if (typeof composition.dispose === 'function') {
|
|
231
|
+
await composition.dispose();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
216
234
|
},
|
|
217
235
|
});
|
|
218
236
|
}
|
|
@@ -236,8 +254,9 @@ async function executeWpP2Retrieval({
|
|
|
236
254
|
configuration: configurationEvidence.configuration,
|
|
237
255
|
transport: composition.transport,
|
|
238
256
|
});
|
|
239
|
-
markPhase('retrieval:embed');
|
|
257
|
+
markPhase('retrieval:embed:start');
|
|
240
258
|
const vector = await provider.embed(request.intent);
|
|
259
|
+
markPhase('retrieval:embed:done');
|
|
241
260
|
requireQualifiedVector(vector);
|
|
242
261
|
const purpose = request && typeof request.purpose === 'string' ? request.purpose : '';
|
|
243
262
|
const strict = AUDIT_PURPOSES.has(purpose);
|
|
@@ -254,6 +273,7 @@ async function executeWpP2Retrieval({
|
|
|
254
273
|
const pool = rerank ? Math.max(topK, rerankOptions.poolSize) : topK;
|
|
255
274
|
const channelSeeds = [];
|
|
256
275
|
for (const channel of CHANNELS) {
|
|
276
|
+
markPhase('retrieval:vector:' + channel.channel);
|
|
257
277
|
const vectorSeeds = await exhaustChannel({
|
|
258
278
|
channel,
|
|
259
279
|
neo4jDriver: composition.neo4jDriver,
|
|
@@ -264,6 +284,7 @@ async function executeWpP2Retrieval({
|
|
|
264
284
|
});
|
|
265
285
|
let seeds = vectorSeeds;
|
|
266
286
|
if (hybrid) {
|
|
287
|
+
markPhase('retrieval:lexical:' + channel.channel);
|
|
267
288
|
const lexicalSeeds = await exhaustLexicalChannel({
|
|
268
289
|
channel,
|
|
269
290
|
neo4jDriver: composition.neo4jDriver,
|
|
@@ -276,7 +297,7 @@ async function executeWpP2Retrieval({
|
|
|
276
297
|
channelSeeds.push({ channel, seeds });
|
|
277
298
|
}
|
|
278
299
|
if (rerank) {
|
|
279
|
-
markPhase('retrieval:rerank');
|
|
300
|
+
markPhase('retrieval:rerank:start');
|
|
280
301
|
// Rerank every channel CONCURRENTLY: the LLM calls dominate latency and are
|
|
281
302
|
// independent, so parallelizing turns the cost from sum(channels) into
|
|
282
303
|
// ~one call. fail-open: a null/empty order keeps the original ordering.
|
|
@@ -292,6 +313,7 @@ async function executeWpP2Retrieval({
|
|
|
292
313
|
: seeds
|
|
293
314
|
)));
|
|
294
315
|
channelSeeds.forEach((entry, index) => { entry.seeds = rerankedSeeds[index]; });
|
|
316
|
+
markPhase('retrieval:rerank:done');
|
|
295
317
|
}
|
|
296
318
|
const seedsByType = {};
|
|
297
319
|
for (const { channel, seeds } of channelSeeds) {
|
|
@@ -329,6 +351,28 @@ async function createProductionComposition(dependencies) {
|
|
|
329
351
|
const repositoryRoot = dependencies.repositoryRoot
|
|
330
352
|
|| getWorkspaceRoot();
|
|
331
353
|
let configurationEvidence;
|
|
354
|
+
let neo4jHandle = null;
|
|
355
|
+
async function disposeNeo4j() {
|
|
356
|
+
const handle = neo4jHandle;
|
|
357
|
+
neo4jHandle = null;
|
|
358
|
+
if (!handle) return;
|
|
359
|
+
try { await handle.session.close(); } catch { /* ignore */ }
|
|
360
|
+
try { await handle.driver.close(); } catch { /* ignore */ }
|
|
361
|
+
}
|
|
362
|
+
function ensureNeo4jSession() {
|
|
363
|
+
if (neo4jHandle) return neo4jHandle.session;
|
|
364
|
+
const configuration = configurationEvidence.configuration;
|
|
365
|
+
const neo4j = require('neo4j-driver');
|
|
366
|
+
const driver = neo4j.driver(
|
|
367
|
+
configuration.neo4jDatabaseUrl,
|
|
368
|
+
neo4j.auth.basic(configuration.neo4jDatabaseUsername, configuration.neo4jDatabasePassword),
|
|
369
|
+
);
|
|
370
|
+
const session = driver.session(configuration.neo4jDatabase === undefined
|
|
371
|
+
? undefined
|
|
372
|
+
: { database: configuration.neo4jDatabase });
|
|
373
|
+
neo4jHandle = { driver, session };
|
|
374
|
+
return session;
|
|
375
|
+
}
|
|
332
376
|
return Object.freeze({
|
|
333
377
|
async resolveConfiguration() {
|
|
334
378
|
configurationEvidence = await resolveApprovedLiveConfiguration({
|
|
@@ -350,59 +394,61 @@ async function createProductionComposition(dependencies) {
|
|
|
350
394
|
if (!configurationEvidence) {
|
|
351
395
|
throw safeError('EXTERNAL_CREDENTIALS_REQUIRED');
|
|
352
396
|
}
|
|
353
|
-
|
|
397
|
+
if (operation && operation.kind === 'semantic-auto-alignment-attempt') {
|
|
398
|
+
return runScriptOwnedSemanticAlignment(operation);
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
return await runOperationOnSession(ensureNeo4jSession(), operation);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
// A failed run may leave the session unusable; drop the handle so the
|
|
404
|
+
// rest of this retrieval (or the next one) reconnects cleanly.
|
|
405
|
+
await disposeNeo4j();
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
354
408
|
},
|
|
355
409
|
}),
|
|
410
|
+
async dispose() {
|
|
411
|
+
await disposeNeo4j();
|
|
412
|
+
},
|
|
356
413
|
});
|
|
357
414
|
}
|
|
358
415
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
416
|
+
// Pure operation runner: runs one Cypher operation on a caller-owned Neo4j
|
|
417
|
+
// session and maps the result. The session/driver lifecycle lives in the
|
|
418
|
+
// production composition (ONE handle per retrieval, reused across every vector
|
|
419
|
+
// window, then closed) -- correctly reused without leaving a lingering handle,
|
|
420
|
+
// which would keep short-lived processes alive. Creating AND closing a driver
|
|
421
|
+
// per operation caused the rapid handle churn that intermittently aborted the
|
|
422
|
+
// MCP with a native libuv assertion on Windows (confirmed crash phase:
|
|
423
|
+
// retrieval:vector-window:ArchitectureRelationship).
|
|
424
|
+
async function runOperationOnSession(session, operation) {
|
|
425
|
+
const result = await session.run(operation.cypher, operation.parameters);
|
|
426
|
+
if (operation.kind === 'semantic-readiness-read') {
|
|
427
|
+
const readiness = result.records[0] && result.records[0].get('readiness');
|
|
428
|
+
return { records: readiness ? [readiness] : [] };
|
|
362
429
|
}
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
),
|
|
370
|
-
);
|
|
371
|
-
const session = driver.session(configuration.neo4jDatabase === undefined
|
|
372
|
-
? undefined
|
|
373
|
-
: { database: configuration.neo4jDatabase });
|
|
374
|
-
try {
|
|
375
|
-
const result = await session.run(operation.cypher, operation.parameters);
|
|
376
|
-
if (operation.kind === 'semantic-readiness-read') {
|
|
377
|
-
const readiness = result.records[0] && result.records[0].get('readiness');
|
|
378
|
-
return { records: readiness ? [readiness] : [] };
|
|
379
|
-
}
|
|
380
|
-
const records = result.records.map(record => ({
|
|
381
|
-
...record.get('record'),
|
|
382
|
-
score: numberValue(record.get('score')),
|
|
383
|
-
}));
|
|
384
|
-
if (operation.kind === 'semantic-lexical-query') {
|
|
385
|
-
return { records };
|
|
386
|
-
}
|
|
387
|
-
const offset = operation.parameters.offset;
|
|
388
|
-
const windowSize = operation.parameters.windowSize;
|
|
389
|
-
const returnedCount = Math.max(0, records.length - offset);
|
|
390
|
-
const hasMore = records.length === operation.parameters.topK;
|
|
391
|
-
return {
|
|
392
|
-
records,
|
|
393
|
-
windowEvidence: {
|
|
394
|
-
offset,
|
|
395
|
-
windowSize,
|
|
396
|
-
returnedCount,
|
|
397
|
-
hasMore,
|
|
398
|
-
nextOffset: hasMore ? operation.parameters.topK : null,
|
|
399
|
-
windowExhausted: !hasMore,
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
} finally {
|
|
403
|
-
await session.close();
|
|
404
|
-
await driver.close();
|
|
430
|
+
const records = result.records.map(record => ({
|
|
431
|
+
...record.get('record'),
|
|
432
|
+
score: numberValue(record.get('score')),
|
|
433
|
+
}));
|
|
434
|
+
if (operation.kind === 'semantic-lexical-query') {
|
|
435
|
+
return { records };
|
|
405
436
|
}
|
|
437
|
+
const offset = operation.parameters.offset;
|
|
438
|
+
const windowSize = operation.parameters.windowSize;
|
|
439
|
+
const returnedCount = Math.max(0, records.length - offset);
|
|
440
|
+
const hasMore = records.length === operation.parameters.topK;
|
|
441
|
+
return {
|
|
442
|
+
records,
|
|
443
|
+
windowEvidence: {
|
|
444
|
+
offset,
|
|
445
|
+
windowSize,
|
|
446
|
+
returnedCount,
|
|
447
|
+
hasMore,
|
|
448
|
+
nextOffset: hasMore ? operation.parameters.topK : null,
|
|
449
|
+
windowExhausted: !hasMore,
|
|
450
|
+
},
|
|
451
|
+
};
|
|
406
452
|
}
|
|
407
453
|
|
|
408
454
|
async function attemptAutomaticAlignment({ composition, request, alignment }) {
|
|
@@ -741,6 +787,7 @@ async function exhaustChannel({
|
|
|
741
787
|
const effectiveMax = Number.isInteger(maxSeeds) && maxSeeds > 0 ? maxSeeds : Number.POSITIVE_INFINITY;
|
|
742
788
|
let offset = 0;
|
|
743
789
|
while (accepted.length < effectiveMax) {
|
|
790
|
+
markPhase('retrieval:vector-window:' + channel.channel);
|
|
744
791
|
const parameters = Object.freeze({
|
|
745
792
|
indexName: channel.indexName,
|
|
746
793
|
channel: channel.channel,
|
|
@@ -46,6 +46,18 @@ function isProjectInitialized(repositoryRoot) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// A readiness record proves the workspace was initialized (argo init) before.
|
|
50
|
+
// The preheat only RECONCILES a previously-initialized-but-now-stale workspace;
|
|
51
|
+
// a never-aligned workspace (brand-new project, or a synthetic test copy) must
|
|
52
|
+
// be initialized explicitly, never rebuilt in the background.
|
|
53
|
+
function hasReadinessRecord(repositoryRoot) {
|
|
54
|
+
try {
|
|
55
|
+
return fs.existsSync(readinessRecordPath(repositoryRoot));
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
function alignmentError() {
|
|
50
62
|
const error = new Error('SEMANTIC_AUTO_ALIGNMENT_FAILED');
|
|
51
63
|
error.category = 'SEMANTIC_AUTO_ALIGNMENT_FAILED';
|
|
@@ -57,7 +69,7 @@ function alignmentError() {
|
|
|
57
69
|
|
|
58
70
|
// Run the alignment (once). Returns a promise resolving to { status: 'aligned' }
|
|
59
71
|
// or rejecting with the SEMANTIC_AUTO_ALIGNMENT_FAILED envelope.
|
|
60
|
-
function runSemanticAlignment(repositoryRoot = getWorkspaceRoot()) {
|
|
72
|
+
function runSemanticAlignment(repositoryRoot = getWorkspaceRoot(), options = {}) {
|
|
61
73
|
if (inFlight) {
|
|
62
74
|
return inFlight;
|
|
63
75
|
}
|
|
@@ -70,6 +82,11 @@ function runSemanticAlignment(repositoryRoot = getWorkspaceRoot()) {
|
|
|
70
82
|
env: process.env,
|
|
71
83
|
stdio: ['ignore', 'ignore', 'inherit'],
|
|
72
84
|
});
|
|
85
|
+
if (options.unref && typeof child.unref === 'function') {
|
|
86
|
+
// Background preheat must NOT keep the MCP process alive (a spawnSync-driven
|
|
87
|
+
// caller waits for the server to exit; an attached child would hang it).
|
|
88
|
+
child.unref();
|
|
89
|
+
}
|
|
73
90
|
const finish = (failed, cause) => {
|
|
74
91
|
inFlight = null;
|
|
75
92
|
const ms = Date.now() - startedAt;
|
|
@@ -94,9 +111,14 @@ function preheatSemanticAlignment(repositoryRoot = getWorkspaceRoot()) {
|
|
|
94
111
|
if (preheated || !repositoryRoot || !isProjectInitialized(repositoryRoot) || isSemanticReady(repositoryRoot)) {
|
|
95
112
|
return;
|
|
96
113
|
}
|
|
114
|
+
// Only reconcile a workspace that was initialized before (a readiness record
|
|
115
|
+
// exists); never rebuild a never-aligned project in the background.
|
|
116
|
+
if (!hasReadinessRecord(repositoryRoot)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
97
119
|
preheated = true;
|
|
98
120
|
console.error('[argo] readiness not aligned at startup; preheating semantic alignment in background…');
|
|
99
|
-
runSemanticAlignment(repositoryRoot).catch(() => {});
|
|
121
|
+
runSemanticAlignment(repositoryRoot, { unref: true }).catch(() => {});
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
module.exports = {
|
|
@@ -104,5 +126,6 @@ module.exports = {
|
|
|
104
126
|
preheatSemanticAlignment,
|
|
105
127
|
isSemanticReady,
|
|
106
128
|
isProjectInitialized,
|
|
129
|
+
hasReadinessRecord,
|
|
107
130
|
readinessRecordPath,
|
|
108
131
|
};
|
package/package.json
CHANGED