agent-planner-mcp 1.5.8 → 1.5.10
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/_shared.js +17 -1
- package/src/tools/bdi/intentions.js +45 -16
package/package.json
CHANGED
package/src/tools/bdi/_shared.js
CHANGED
|
@@ -26,6 +26,22 @@ function safeArray(value) {
|
|
|
26
26
|
return Array.isArray(value) ? value : [];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Extract the most actionable message from an axios error. The AP backend's
|
|
31
|
+
* validation errors carry a field-level `message` (e.g. "urgency: must be one
|
|
32
|
+
* of ...; options.0: Unrecognized key") plus a `details` array — far more
|
|
33
|
+
* useful than the generic top-line `error` ("Validation failed"), which is all
|
|
34
|
+
* most handlers surfaced. Prefer message → error → err.message.
|
|
35
|
+
*/
|
|
36
|
+
function apiErrorMessage(err) {
|
|
37
|
+
const data = err?.response?.data;
|
|
38
|
+
if (data) {
|
|
39
|
+
if (data.message && data.message !== data.error) return data.message;
|
|
40
|
+
if (typeof data.error === 'string') return data.error;
|
|
41
|
+
}
|
|
42
|
+
return err?.message || 'unknown error';
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
/**
|
|
30
46
|
* The web app origin (where /app/plans/:id lives), for building shareable plan
|
|
31
47
|
* links agents can post (e.g. to Slack). Derived from API_URL — the web app
|
|
@@ -56,4 +72,4 @@ function isV1Unavailable(err) {
|
|
|
56
72
|
return !(body && typeof body === 'object' && body.error);
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
module.exports = { asOf, formatResponse, errorResponse, safeArray, isV1Unavailable, webOrigin, planUrl };
|
|
75
|
+
module.exports = { asOf, formatResponse, errorResponse, safeArray, apiErrorMessage, isV1Unavailable, webOrigin, planUrl };
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See ../../../docs/MCP_v1.0_FULL_SURFACE.md for design rationale.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const { asOf, formatResponse, errorResponse, isV1Unavailable, planUrl } = require('./_shared');
|
|
16
|
+
const { asOf, formatResponse, errorResponse, apiErrorMessage, isV1Unavailable, planUrl } = require('./_shared');
|
|
17
17
|
const { version: PKG_VERSION } = require('../../../package.json');
|
|
18
18
|
|
|
19
19
|
// Provenance tag stamped onto every plan this server creates, so a plan stays
|
|
@@ -108,14 +108,37 @@ async function queueDecisionHandler(args, apiClient) {
|
|
|
108
108
|
return errorResponse('invalid_arg', 'queue_decision requires either plan_id or node_id');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// The agent-facing urgency vocabulary (low/normal/high) differs from the
|
|
112
|
+
// backend decision schema (blocking/can_continue/informational) — map it, or
|
|
113
|
+
// every call fails strict validation.
|
|
114
|
+
const URGENCY_MAP = { low: 'informational', normal: 'can_continue', high: 'blocking' };
|
|
115
|
+
const mappedUrgency = URGENCY_MAP[urgency] || 'can_continue';
|
|
116
|
+
|
|
117
|
+
// The backend decisionOption shape is {option, pros?, cons?, recommendation?}
|
|
118
|
+
// and is .strict() — our {label, description} would be rejected. Fold
|
|
119
|
+
// description into the option text and mark the recommended one.
|
|
120
|
+
const recText = String(recommendation || '').toLowerCase();
|
|
121
|
+
const mappedOptions = Array.isArray(options)
|
|
122
|
+
? options
|
|
123
|
+
.map((o) => {
|
|
124
|
+
const label = o.label || o.option || '';
|
|
125
|
+
const option = o.description ? `${label} — ${o.description}` : label;
|
|
126
|
+
const isRecommended = label && recText.includes(label.toLowerCase());
|
|
127
|
+
return option ? { option, ...(isRecommended ? { recommendation: true } : {}) } : null;
|
|
128
|
+
})
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
: [];
|
|
131
|
+
|
|
111
132
|
const body = {
|
|
112
133
|
title,
|
|
113
134
|
context,
|
|
114
|
-
options:
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
options: mappedOptions,
|
|
136
|
+
urgency: mappedUrgency,
|
|
137
|
+
// Top-level `recommendation` is not in the strict schema — keep the agent's
|
|
138
|
+
// free-text recommendation and ask in metadata instead.
|
|
117
139
|
metadata: {
|
|
118
140
|
smallest_input_needed,
|
|
141
|
+
recommendation: recommendation || null,
|
|
119
142
|
goal_id: goal_id || null,
|
|
120
143
|
source: 'bdi.queue_decision',
|
|
121
144
|
proposed_subtasks: Array.isArray(proposed_subtasks) ? proposed_subtasks : undefined,
|
|
@@ -136,10 +159,7 @@ async function queueDecisionHandler(args, apiClient) {
|
|
|
136
159
|
title: created.title,
|
|
137
160
|
});
|
|
138
161
|
} catch (err) {
|
|
139
|
-
return errorResponse(
|
|
140
|
-
'upstream_unavailable',
|
|
141
|
-
`Failed to queue decision: ${err.response?.data?.error || err.message}`
|
|
142
|
-
);
|
|
162
|
+
return errorResponse('upstream_unavailable', `Failed to queue decision: ${apiErrorMessage(err)}`);
|
|
143
163
|
}
|
|
144
164
|
}
|
|
145
165
|
|
|
@@ -187,20 +207,20 @@ async function resolveDecisionHandler(args, apiClient) {
|
|
|
187
207
|
// Best-effort — if fetch fails, we still try to resolve.
|
|
188
208
|
}
|
|
189
209
|
|
|
210
|
+
// The backend resolve schema is strict {decision, rationale}. Encode the
|
|
211
|
+
// action (+ chosen option) into `decision` and the note into `rationale` —
|
|
212
|
+
// the previous {resolution, message, selected_option} body was rejected.
|
|
213
|
+
const decisionText = selected_option ? `${action} — ${selected_option}` : action;
|
|
190
214
|
let resolved;
|
|
191
215
|
try {
|
|
192
216
|
resolved = await apiClient.axiosInstance
|
|
193
217
|
.post(`/plans/${plan_id}/decisions/${decision_id}/resolve`, {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
selected_option: selected_option || null,
|
|
218
|
+
decision: decisionText,
|
|
219
|
+
rationale: message || undefined,
|
|
197
220
|
})
|
|
198
221
|
.then((r) => r.data);
|
|
199
222
|
} catch (err) {
|
|
200
|
-
return errorResponse(
|
|
201
|
-
'upstream_unavailable',
|
|
202
|
-
`Failed to resolve decision: ${err.response?.data?.error || err.message}`
|
|
203
|
-
);
|
|
223
|
+
return errorResponse('upstream_unavailable', `Failed to resolve decision: ${apiErrorMessage(err)}`);
|
|
204
224
|
}
|
|
205
225
|
|
|
206
226
|
// On approve, materialize any proposed_subtasks atomically (best-effort per task).
|
|
@@ -738,6 +758,15 @@ function validateTreeShape(tree, depth = 0) {
|
|
|
738
758
|
return null;
|
|
739
759
|
}
|
|
740
760
|
|
|
761
|
+
// Count every node in a returned tree, recursing through `children`. The facade
|
|
762
|
+
// response's `tree` is the array of top-level nodes; `tree.length` alone counts
|
|
763
|
+
// only those (e.g. 2 phases) and contradicts structure.task_count, so report
|
|
764
|
+
// the true recursive total.
|
|
765
|
+
function countTreeNodes(tree) {
|
|
766
|
+
if (!Array.isArray(tree)) return 0;
|
|
767
|
+
return tree.reduce((sum, node) => sum + 1 + countTreeNodes(node?.children), 0);
|
|
768
|
+
}
|
|
769
|
+
|
|
741
770
|
const formIntentionDefinition = {
|
|
742
771
|
name: 'form_intention',
|
|
743
772
|
description:
|
|
@@ -862,7 +891,7 @@ async function formIntentionHandler(args, apiClient) {
|
|
|
862
891
|
goal_id,
|
|
863
892
|
status: result.plan?.status || status,
|
|
864
893
|
is_draft: (result.plan?.status || status) === 'draft',
|
|
865
|
-
nodes_created: Array.isArray(result.tree) ? result.tree
|
|
894
|
+
nodes_created: Array.isArray(result.tree) ? countTreeNodes(result.tree) : undefined,
|
|
866
895
|
next_step: (result.plan?.status || status) === 'draft'
|
|
867
896
|
? "Plan created as draft. Will surface in dashboard pending for human review. Auto-promotes to active when first task moves to in_progress."
|
|
868
897
|
: `Plan active. Claim a task with claim_next_task({plan_id}) to begin work. Shareable link: ${planUrl(facadePlanId)} (set visibility:'unlisted' for a rich Slack/social preview).`,
|