agent-planner-mcp 1.5.6 → 1.5.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.
- package/package.json +1 -1
- package/src/tools/bdi/beliefs.js +51 -2
package/package.json
CHANGED
package/src/tools/bdi/beliefs.js
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
const { asOf, formatResponse, errorResponse, safeArray, isV1Unavailable, planUrl } = require('./_shared');
|
|
9
9
|
|
|
10
|
+
// A Graphiti fact is superseded once it has an `expired_at`, or an `invalid_at`
|
|
11
|
+
// that is in the past — the temporal graph has replaced it with a newer truth.
|
|
12
|
+
// recall_knowledge used to return these inline with current facts, undistinguished,
|
|
13
|
+
// so an agent could act on stale knowledge. Tag each fact `status` and sort
|
|
14
|
+
// current-first so the valid facts read first.
|
|
15
|
+
function annotateFacts(facts, nowMs = Date.now()) {
|
|
16
|
+
const list = safeArray(facts).map((f) => {
|
|
17
|
+
const invalidMs = f.invalid_at ? new Date(f.invalid_at).getTime() : null;
|
|
18
|
+
const superseded = Boolean(f.expired_at) || (invalidMs != null && invalidMs <= nowMs);
|
|
19
|
+
return { ...f, status: superseded ? 'superseded' : 'current' };
|
|
20
|
+
});
|
|
21
|
+
list.sort((a, b) => (a.status === b.status ? 0 : a.status === 'current' ? -1 : 1));
|
|
22
|
+
return list;
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
// ─────────────────────────────────────────────────────────────────────────
|
|
11
26
|
// briefing — bundled mission control state. Replaces 4 round trips.
|
|
12
27
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -343,6 +358,8 @@ const recallKnowledgeDefinition = {
|
|
|
343
358
|
description:
|
|
344
359
|
"Universal knowledge graph query. Returns facts, entities, recent episodes, " +
|
|
345
360
|
"and contradictions in one shape. Use result_kind to control payload size. " +
|
|
361
|
+
"Each fact carries status: 'current' or 'superseded' (the graph has since " +
|
|
362
|
+
"replaced it) — facts are sorted current-first; prefer current facts. " +
|
|
346
363
|
"Replaces recall_knowledge legacy + find_entities + get_recent_episodes + check_contradictions.",
|
|
347
364
|
inputSchema: {
|
|
348
365
|
type: 'object',
|
|
@@ -363,6 +380,9 @@ const recallKnowledgeDefinition = {
|
|
|
363
380
|
|
|
364
381
|
async function recallKnowledgeHandler(args, apiClient) {
|
|
365
382
|
const { query, scope = {}, since, entry_type = 'all', result_kind = 'all', max_results = 10, include_contradictions = false } = args;
|
|
383
|
+
if (!query || !String(query).trim()) {
|
|
384
|
+
return errorResponse('invalid_arg', 'recall_knowledge requires a non-empty query string');
|
|
385
|
+
}
|
|
366
386
|
|
|
367
387
|
// v1 facade: one server-side call replaces the 4-endpoint fan-out below.
|
|
368
388
|
if (apiClient.v1) {
|
|
@@ -370,6 +390,11 @@ async function recallKnowledgeHandler(args, apiClient) {
|
|
|
370
390
|
const data = await apiClient.v1.knowledgeSearch({
|
|
371
391
|
query, since, entry_type, result_kind, max_results, include_contradictions, ...scope,
|
|
372
392
|
});
|
|
393
|
+
if (data && Array.isArray(data.facts)) {
|
|
394
|
+
data.facts = annotateFacts(data.facts);
|
|
395
|
+
const superseded = data.facts.filter((f) => f.status === 'superseded').length;
|
|
396
|
+
data.meta = { ...(data.meta || {}), superseded_fact_count: superseded };
|
|
397
|
+
}
|
|
373
398
|
return formatResponse(data);
|
|
374
399
|
} catch (err) {
|
|
375
400
|
if (!isV1Unavailable(err)) {
|
|
@@ -407,7 +432,7 @@ async function recallKnowledgeHandler(args, apiClient) {
|
|
|
407
432
|
return;
|
|
408
433
|
}
|
|
409
434
|
const v = s.value;
|
|
410
|
-
if (key === 'facts') out.facts =
|
|
435
|
+
if (key === 'facts') out.facts = annotateFacts(v.facts || v);
|
|
411
436
|
if (key === 'entities') out.entities = safeArray(v.entities || v);
|
|
412
437
|
if (key === 'episodes') {
|
|
413
438
|
let eps = safeArray(v.episodes?.episodes || v.episodes || v);
|
|
@@ -423,6 +448,7 @@ async function recallKnowledgeHandler(args, apiClient) {
|
|
|
423
448
|
if (key === 'contradictions') out.contradictions = v;
|
|
424
449
|
});
|
|
425
450
|
|
|
451
|
+
out.meta.superseded_fact_count = out.facts.filter((f) => f.status === 'superseded').length;
|
|
426
452
|
return formatResponse(out);
|
|
427
453
|
}
|
|
428
454
|
|
|
@@ -539,6 +565,9 @@ const searchDefinition = {
|
|
|
539
565
|
|
|
540
566
|
async function searchHandler(args, apiClient) {
|
|
541
567
|
const { query, scope = 'global', scope_id, filters = {} } = args;
|
|
568
|
+
if (!query || !String(query).trim()) {
|
|
569
|
+
return errorResponse('invalid_arg', 'search requires a non-empty query string');
|
|
570
|
+
}
|
|
542
571
|
const limit = filters.limit || 20;
|
|
543
572
|
try {
|
|
544
573
|
let result;
|
|
@@ -601,8 +630,17 @@ const planAnalysisDefinition = {
|
|
|
601
630
|
},
|
|
602
631
|
};
|
|
603
632
|
|
|
633
|
+
const PLAN_ANALYSIS_TYPES = ['impact', 'critical_path', 'bottlenecks', 'coherence'];
|
|
634
|
+
|
|
604
635
|
async function planAnalysisHandler(args, apiClient) {
|
|
605
636
|
const { plan_id, type, node_id, scenario } = args;
|
|
637
|
+
// The hosted MCP transport does not enforce `required`, so a missing/unknown
|
|
638
|
+
// `type` would otherwise fall through every branch and return an empty
|
|
639
|
+
// `results: {}` with no explanation. Validate explicitly with a clear error.
|
|
640
|
+
if (!plan_id) return errorResponse('invalid_arg', 'plan_analysis requires plan_id');
|
|
641
|
+
if (!type || !PLAN_ANALYSIS_TYPES.includes(type)) {
|
|
642
|
+
return errorResponse('invalid_arg', `plan_analysis requires type (one of: ${PLAN_ANALYSIS_TYPES.join(', ')})`);
|
|
643
|
+
}
|
|
606
644
|
try {
|
|
607
645
|
let result;
|
|
608
646
|
if (type === 'critical_path') {
|
|
@@ -616,7 +654,18 @@ async function planAnalysisHandler(args, apiClient) {
|
|
|
616
654
|
} else if (type === 'coherence') {
|
|
617
655
|
result = await apiClient.coherence.runCheck(plan_id);
|
|
618
656
|
}
|
|
619
|
-
|
|
657
|
+
const payload = { as_of: asOf(), type, results: result || {} };
|
|
658
|
+
// Critical-path / bottleneck analysis is driven by `blocks` dependency
|
|
659
|
+
// edges. A flat (edgeless) plan returns empty here — without a hint the
|
|
660
|
+
// agent can't tell "nothing blocking" from "this plan has no edges at all".
|
|
661
|
+
const emptyCriticalPath = type === 'critical_path' && !(result?.nodes?.length);
|
|
662
|
+
const emptyBottlenecks = type === 'bottlenecks'
|
|
663
|
+
&& !((Array.isArray(result) ? result : result?.bottlenecks)?.length);
|
|
664
|
+
if (emptyCriticalPath || emptyBottlenecks) {
|
|
665
|
+
payload.note = 'No dependency edges drive this result — the plan may be flat (no blocks edges). '
|
|
666
|
+
+ 'Use task_context (depth 2+) to read its tasks, or add dependencies with link_intentions.';
|
|
667
|
+
}
|
|
668
|
+
return formatResponse(payload);
|
|
620
669
|
} catch (err) {
|
|
621
670
|
return errorResponse('upstream_unavailable', `plan_analysis failed: ${err.response?.data?.error || err.message}`);
|
|
622
671
|
}
|