agent-planner-mcp 1.0.0 → 1.1.0
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 +22 -0
- package/package.json +2 -1
- package/src/api-client.js +46 -2
- package/src/setup.js +5 -2
- package/src/tools/bdi/beliefs.js +128 -5
- package/src/tools/bdi/intentions.js +69 -1
- package/src/tools/bdi/utility.js +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,28 @@ Beyond title, description, agent_instructions, and acceptance criteria, the gene
|
|
|
123
123
|
|
|
124
124
|
The CLI is intentionally thin: it covers the read context + writeback loop and nothing else. For decomposition, dependency creation, knowledge graph queries, RPI chains, coherence runs, and goal management, use the MCP server (or the API directly).
|
|
125
125
|
|
|
126
|
+
## Agent Loop Facade
|
|
127
|
+
|
|
128
|
+
AgentPlanner API now exposes a narrow `/agent/*` facade for the main autonomous loop. MCP uses this facade when available and falls back to older domain endpoints for self-hosted older APIs.
|
|
129
|
+
|
|
130
|
+
Primary mappings:
|
|
131
|
+
|
|
132
|
+
| MCP tool | Preferred API endpoint |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `briefing` | `GET /agent/briefing` |
|
|
135
|
+
| `claim_next_task` | `POST /agent/work-sessions` |
|
|
136
|
+
| `update_task` with `session_id` + `completed` | `POST /agent/work-sessions/:id/complete` |
|
|
137
|
+
| `update_task` with `session_id` + `blocked` | `POST /agent/work-sessions/:id/block` |
|
|
138
|
+
| `form_intention` | `POST /agent/intentions` when available, with domain-endpoint fallback |
|
|
139
|
+
|
|
140
|
+
Validation:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm run validate:mcp-loop
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This checks that the MCP tools route through the facade for briefing, task claim/start, and session completion/blocking.
|
|
147
|
+
|
|
126
148
|
|
|
127
149
|
### Claude Desktop
|
|
128
150
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-planner-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server for AgentPlanner — AI agent orchestration with planning, dependencies, knowledge graphs, and human oversight",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"dev": "nodemon src/index.js",
|
|
13
13
|
"dev:http": "MCP_TRANSPORT=http nodemon src/index.js",
|
|
14
14
|
"test": "jest",
|
|
15
|
+
"validate:mcp-loop": "jest __tests__/agent-loop-facade.test.js --runInBand",
|
|
15
16
|
"test:tools": "node test-tools.js",
|
|
16
17
|
"setup": "node src/setup.js",
|
|
17
18
|
"setup-claude-code": "node src/setup-claude-code.js",
|
package/src/api-client.js
CHANGED
|
@@ -17,12 +17,29 @@ const getAuthScheme = (token) => {
|
|
|
17
17
|
|
|
18
18
|
const authScheme = getAuthScheme(userApiToken);
|
|
19
19
|
|
|
20
|
+
// Identify this caller in the API server's tool_calls telemetry.
|
|
21
|
+
// Setup wizards (per client) should set MCP_CLIENT_LABEL to one of
|
|
22
|
+
// "Claude Desktop" | "Claude Code" | "Cursor" | "ChatGPT" | "OpenClaw"
|
|
23
|
+
// so the Settings → Integrations dashboard can group calls per client.
|
|
24
|
+
let pkgVersion;
|
|
25
|
+
try { pkgVersion = require('../package.json').version; } catch { pkgVersion = '0.0.0'; }
|
|
26
|
+
const clientLabel = process.env.MCP_CLIENT_LABEL || null;
|
|
27
|
+
const userAgent = clientLabel
|
|
28
|
+
? `agent-planner-mcp/${pkgVersion} (${clientLabel})`
|
|
29
|
+
: `agent-planner-mcp/${pkgVersion}`;
|
|
30
|
+
|
|
20
31
|
// Create API client instance
|
|
21
32
|
const apiClient = axios.create({
|
|
22
33
|
baseURL: process.env.API_URL || 'http://localhost:3000',
|
|
23
34
|
headers: {
|
|
24
35
|
'Content-Type': 'application/json',
|
|
25
|
-
'Authorization': userApiToken ? `${authScheme} ${userApiToken}` : undefined
|
|
36
|
+
'Authorization': userApiToken ? `${authScheme} ${userApiToken}` : undefined,
|
|
37
|
+
'User-Agent': userAgent,
|
|
38
|
+
// X-Client-Label takes precedence on the API side (see
|
|
39
|
+
// toolCallTelemetry.middleware) so MCP setup wizards can label
|
|
40
|
+
// calls precisely without polluting the User-Agent string.
|
|
41
|
+
...(clientLabel ? { 'X-Client-Label': clientLabel } : {}),
|
|
42
|
+
'X-MCP-Client': clientLabel || 'unknown',
|
|
26
43
|
}
|
|
27
44
|
});
|
|
28
45
|
|
|
@@ -738,6 +755,15 @@ const users = {
|
|
|
738
755
|
},
|
|
739
756
|
};
|
|
740
757
|
|
|
758
|
+
// ─── Agent Loop facade ────────────────────────────────────────
|
|
759
|
+
const agentLoop = {
|
|
760
|
+
briefing: async (params = {}) => (await apiClient.get('/agent/briefing', { params })).data,
|
|
761
|
+
startWorkSession: async (data = {}) => (await apiClient.post('/agent/work-sessions', data)).data,
|
|
762
|
+
completeWorkSession: async (sessionId, data = {}) => (await apiClient.post(`/agent/work-sessions/${sessionId}/complete`, data)).data,
|
|
763
|
+
blockWorkSession: async (sessionId, data = {}) => (await apiClient.post(`/agent/work-sessions/${sessionId}/block`, data)).data,
|
|
764
|
+
createIntention: async (data = {}) => (await apiClient.post('/agent/intentions', data)).data,
|
|
765
|
+
};
|
|
766
|
+
|
|
741
767
|
// ─── Dependencies (cross-plan & external) ─────────────────────
|
|
742
768
|
const dependencies = {
|
|
743
769
|
/**
|
|
@@ -778,11 +804,21 @@ const dependencies = {
|
|
|
778
804
|
*/
|
|
779
805
|
function createApiClient(token, options = {}) {
|
|
780
806
|
const scheme = getAuthScheme(token);
|
|
807
|
+
// Telemetry headers — same shape as the default client. options.clientLabel
|
|
808
|
+
// wins over the global env var so HTTP-mode sessions can label themselves
|
|
809
|
+
// per connection (e.g. when the SSE handshake reveals the client).
|
|
810
|
+
const sessionLabel = options.clientLabel || process.env.MCP_CLIENT_LABEL || null;
|
|
811
|
+
const sessionUserAgent = sessionLabel
|
|
812
|
+
? `agent-planner-mcp/${pkgVersion} (${sessionLabel})`
|
|
813
|
+
: `agent-planner-mcp/${pkgVersion}`;
|
|
781
814
|
const client = axios.create({
|
|
782
815
|
baseURL: options.apiUrl || process.env.API_URL || 'http://localhost:3000',
|
|
783
816
|
headers: {
|
|
784
817
|
'Content-Type': 'application/json',
|
|
785
|
-
'Authorization': token ? `${scheme} ${token}` : undefined
|
|
818
|
+
'Authorization': token ? `${scheme} ${token}` : undefined,
|
|
819
|
+
'User-Agent': sessionUserAgent,
|
|
820
|
+
...(sessionLabel ? { 'X-Client-Label': sessionLabel } : {}),
|
|
821
|
+
'X-MCP-Client': sessionLabel || 'unknown',
|
|
786
822
|
}
|
|
787
823
|
});
|
|
788
824
|
|
|
@@ -945,6 +981,13 @@ function createApiClient(token, options = {}) {
|
|
|
945
981
|
return (await client.get(`/users/my-tasks${qs}`)).data;
|
|
946
982
|
},
|
|
947
983
|
},
|
|
984
|
+
agentLoop: {
|
|
985
|
+
briefing: async (params = {}) => (await client.get('/agent/briefing', { params })).data,
|
|
986
|
+
startWorkSession: async (data = {}) => (await client.post('/agent/work-sessions', data)).data,
|
|
987
|
+
completeWorkSession: async (sessionId, data = {}) => (await client.post(`/agent/work-sessions/${sessionId}/complete`, data)).data,
|
|
988
|
+
blockWorkSession: async (sessionId, data = {}) => (await client.post(`/agent/work-sessions/${sessionId}/block`, data)).data,
|
|
989
|
+
createIntention: async (data = {}) => (await client.post('/agent/intentions', data)).data,
|
|
990
|
+
},
|
|
948
991
|
axiosInstance: client,
|
|
949
992
|
};
|
|
950
993
|
}
|
|
@@ -975,6 +1018,7 @@ module.exports = {
|
|
|
975
1018
|
dependencies,
|
|
976
1019
|
coherence,
|
|
977
1020
|
users,
|
|
1021
|
+
agentLoop,
|
|
978
1022
|
axiosInstance, // Export for direct API calls
|
|
979
1023
|
createApiClient // Factory for per-session clients (HTTP mode)
|
|
980
1024
|
};
|
package/src/setup.js
CHANGED
|
@@ -158,13 +158,16 @@ function updateClaudeConfig(configPath, mcpServerPath, apiUrl, token) {
|
|
|
158
158
|
config.mcpServers = {};
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
// Add or update planning-system server
|
|
161
|
+
// Add or update planning-system server.
|
|
162
|
+
// MCP_CLIENT_LABEL identifies this install in the Settings →
|
|
163
|
+
// Integrations dashboard's tool_calls telemetry stream.
|
|
162
164
|
config.mcpServers['planning-system'] = {
|
|
163
165
|
command: 'node',
|
|
164
166
|
args: [path.join(mcpServerPath, 'src', 'index.js')],
|
|
165
167
|
env: {
|
|
166
168
|
API_URL: apiUrl,
|
|
167
|
-
USER_API_TOKEN: token
|
|
169
|
+
USER_API_TOKEN: token,
|
|
170
|
+
MCP_CLIENT_LABEL: 'Claude Desktop'
|
|
168
171
|
}
|
|
169
172
|
};
|
|
170
173
|
|
package/src/tools/bdi/beliefs.js
CHANGED
|
@@ -30,6 +30,20 @@ const briefingDefinition = {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
async function briefingHandler(args, apiClient) {
|
|
33
|
+
try {
|
|
34
|
+
const response = await apiClient.axiosInstance.get('/agent/briefing', {
|
|
35
|
+
params: {
|
|
36
|
+
scope: args.scope,
|
|
37
|
+
goal_id: args.goal_id,
|
|
38
|
+
plan_id: args.plan_id,
|
|
39
|
+
recent_window_hours: args.recent_window_hours,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
return formatResponse(response.data);
|
|
43
|
+
} catch {
|
|
44
|
+
// Fall back to the pre-facade fan-out for self-hosted older APIs.
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
const recentHours = typeof args.recent_window_hours === 'number' ? args.recent_window_hours : 24;
|
|
34
48
|
const recentSinceMs = Date.now() - recentHours * 3600 * 1000;
|
|
35
49
|
|
|
@@ -252,6 +266,19 @@ async function goalStateHandler(args, apiClient) {
|
|
|
252
266
|
direct_downstream_count: t.direct_downstream_count || 0,
|
|
253
267
|
}));
|
|
254
268
|
|
|
269
|
+
// Surface the goal's linked plans + tasks. The underlying GET /goals/:id
|
|
270
|
+
// already returns the `links` array; the previous handler discarded it,
|
|
271
|
+
// so quality.actionability could report "26 plans linked" while the
|
|
272
|
+
// response refused to name a single one. Callers had no read-side way
|
|
273
|
+
// to enumerate the plans served by a goal short of REST.
|
|
274
|
+
const links = safeArray(goal.links);
|
|
275
|
+
const linked_plans = links
|
|
276
|
+
.filter((l) => (l.linkedType || l.linked_type) === 'plan')
|
|
277
|
+
.map((l) => ({ id: l.linkedId || l.linked_id, link_id: l.id }));
|
|
278
|
+
const linked_tasks = links
|
|
279
|
+
.filter((l) => (l.linkedType || l.linked_type) === 'task')
|
|
280
|
+
.map((l) => ({ id: l.linkedId || l.linked_id, link_id: l.id }));
|
|
281
|
+
|
|
255
282
|
return formatResponse({
|
|
256
283
|
as_of: asOf(),
|
|
257
284
|
goal: {
|
|
@@ -261,6 +288,8 @@ async function goalStateHandler(args, apiClient) {
|
|
|
261
288
|
owner_id: goal.ownerId || goal.owner_id, success_criteria: goal.successCriteria || goal.success_criteria,
|
|
262
289
|
promoted_at: goal.promotedAt || goal.promoted_at,
|
|
263
290
|
},
|
|
291
|
+
linked_plans,
|
|
292
|
+
linked_tasks,
|
|
264
293
|
quality: {
|
|
265
294
|
score: quality.score, dimensions: quality.dimensions,
|
|
266
295
|
suggestions: quality.suggestions, last_assessed_at: quality.as_of,
|
|
@@ -348,6 +377,79 @@ async function recallKnowledgeHandler(args, apiClient) {
|
|
|
348
377
|
return formatResponse(out);
|
|
349
378
|
}
|
|
350
379
|
|
|
380
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
381
|
+
// list_plans — list workspace plans with optional filters.
|
|
382
|
+
// Counterpart to list_goals; previously the only ways to find a plan
|
|
383
|
+
// were knowing the UUID a priori, parsing briefing.recent_activity,
|
|
384
|
+
// or calling task_context on a known node — all bad.
|
|
385
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
const listPlansDefinition = {
|
|
388
|
+
name: 'list_plans',
|
|
389
|
+
description:
|
|
390
|
+
'List plans with optional filters by status, visibility, or text query. ' +
|
|
391
|
+
'Returns id, title, status, visibility, last update, and link counts so ' +
|
|
392
|
+
'you can pick a plan to operate on without round-tripping briefing.',
|
|
393
|
+
inputSchema: {
|
|
394
|
+
type: 'object',
|
|
395
|
+
properties: {
|
|
396
|
+
filter: {
|
|
397
|
+
type: 'object',
|
|
398
|
+
properties: {
|
|
399
|
+
status: { type: 'array', items: { type: 'string' } },
|
|
400
|
+
visibility: { type: 'array', items: { type: 'string', enum: ['private', 'unlisted', 'public'] } },
|
|
401
|
+
query: { type: 'string', description: 'Substring match on title (case-insensitive)' },
|
|
402
|
+
limit: { type: 'integer', default: 50 },
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
async function listPlansHandler(args, apiClient) {
|
|
410
|
+
const filter = args.filter || {};
|
|
411
|
+
try {
|
|
412
|
+
const raw = await apiClient.plans.getPlans();
|
|
413
|
+
let plans = Array.isArray(raw) ? raw : safeArray(raw.plans || raw);
|
|
414
|
+
|
|
415
|
+
if (filter.status?.length) plans = plans.filter((p) => filter.status.includes(p.status));
|
|
416
|
+
if (filter.visibility?.length) plans = plans.filter((p) => filter.visibility.includes(p.visibility));
|
|
417
|
+
if (filter.query) {
|
|
418
|
+
const q = filter.query.toLowerCase();
|
|
419
|
+
plans = plans.filter((p) => (p.title || '').toLowerCase().includes(q));
|
|
420
|
+
}
|
|
421
|
+
plans = plans.slice(0, filter.limit || 50);
|
|
422
|
+
|
|
423
|
+
const summary = plans.reduce(
|
|
424
|
+
(acc, p) => {
|
|
425
|
+
acc[p.status] = (acc[p.status] || 0) + 1;
|
|
426
|
+
acc.total += 1;
|
|
427
|
+
return acc;
|
|
428
|
+
},
|
|
429
|
+
{ total: 0 },
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
return formatResponse({
|
|
433
|
+
as_of: asOf(),
|
|
434
|
+
summary,
|
|
435
|
+
plans: plans.map((p) => ({
|
|
436
|
+
id: p.id,
|
|
437
|
+
title: p.title,
|
|
438
|
+
status: p.status,
|
|
439
|
+
visibility: p.visibility,
|
|
440
|
+
owner_id: p.owner_id || p.ownerId,
|
|
441
|
+
updated_at: p.updated_at || p.updatedAt,
|
|
442
|
+
// Surface progress + tether info when the API decorates the rows;
|
|
443
|
+
// listPlans on the v2 API now bulk-loads stats + goal_tethers.
|
|
444
|
+
progress: p.progress ?? p.stats?.percentage,
|
|
445
|
+
goal_tethers: p.goal_tethers,
|
|
446
|
+
})),
|
|
447
|
+
});
|
|
448
|
+
} catch (err) {
|
|
449
|
+
return errorResponse('upstream_unavailable', `list_plans failed: ${err.response?.data?.error || err.message}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
351
453
|
// ─────────────────────────────────────────────────────────────────────────
|
|
352
454
|
// search — universal text search.
|
|
353
455
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -376,13 +478,32 @@ const searchDefinition = {
|
|
|
376
478
|
|
|
377
479
|
async function searchHandler(args, apiClient) {
|
|
378
480
|
const { query, scope = 'global', scope_id, filters = {} } = args;
|
|
481
|
+
const limit = filters.limit || 20;
|
|
379
482
|
try {
|
|
380
483
|
let result;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
484
|
+
// Map MCP scopes onto the actual api-client surface. The handler
|
|
485
|
+
// previously called apiClient.search.{global,plans,inPlan,inNode}
|
|
486
|
+
// — none of those methods exist, so every invocation 500'd. The
|
|
487
|
+
// real api-client exposes globalSearch + searchPlan only; for
|
|
488
|
+
// 'plans' and 'node' we filter the global result client-side
|
|
489
|
+
// instead of hitting a (non-existent) per-bucket endpoint.
|
|
490
|
+
if (scope === 'plan') {
|
|
491
|
+
if (!scope_id) return errorResponse('invalid_arg', 'search scope=plan requires scope_id');
|
|
492
|
+
result = await apiClient.search.searchPlan(scope_id, query);
|
|
493
|
+
} else {
|
|
494
|
+
const global = await apiClient.search.globalSearch(query);
|
|
495
|
+
const all = Array.isArray(global?.results) ? global.results : [];
|
|
496
|
+
const matchScope = (r) => {
|
|
497
|
+
if (scope === 'global') return true;
|
|
498
|
+
if (scope === 'plans') return r.type === 'plan';
|
|
499
|
+
if (scope === 'node') return r.type === 'node' && (!scope_id || r.plan_id === scope_id);
|
|
500
|
+
return true;
|
|
501
|
+
};
|
|
502
|
+
const matchType = (r) => !filters.type || r.type === filters.type;
|
|
503
|
+
const matchStatus = (r) => !filters.status || r.status === filters.status;
|
|
504
|
+
const filtered = all.filter((r) => matchScope(r) && matchType(r) && matchStatus(r)).slice(0, limit);
|
|
505
|
+
result = { results: filtered, count: filtered.length, query, scope };
|
|
506
|
+
}
|
|
386
507
|
return formatResponse({ as_of: asOf(), ...(result || {}) });
|
|
387
508
|
} catch (err) {
|
|
388
509
|
return errorResponse('upstream_unavailable', `Search failed: ${err.response?.data?.error || err.message}`);
|
|
@@ -437,6 +558,7 @@ module.exports = {
|
|
|
437
558
|
taskContextDefinition,
|
|
438
559
|
goalStateDefinition,
|
|
439
560
|
recallKnowledgeDefinition,
|
|
561
|
+
listPlansDefinition,
|
|
440
562
|
searchDefinition,
|
|
441
563
|
planAnalysisDefinition,
|
|
442
564
|
],
|
|
@@ -445,6 +567,7 @@ module.exports = {
|
|
|
445
567
|
task_context: taskContextHandler,
|
|
446
568
|
goal_state: goalStateHandler,
|
|
447
569
|
recall_knowledge: recallKnowledgeHandler,
|
|
570
|
+
list_plans: listPlansHandler,
|
|
448
571
|
search: searchHandler,
|
|
449
572
|
plan_analysis: planAnalysisHandler,
|
|
450
573
|
},
|
|
@@ -281,15 +281,37 @@ const updateTaskDefinition = {
|
|
|
281
281
|
type: 'string',
|
|
282
282
|
description: 'Optional: also write a knowledge episode (recommended on completion)',
|
|
283
283
|
},
|
|
284
|
+
session_id: {
|
|
285
|
+
type: 'string',
|
|
286
|
+
description: 'Optional work-session id returned by claim_next_task. Uses the agent-loop completion/block endpoint when status is completed or blocked.',
|
|
287
|
+
},
|
|
288
|
+
decision: {
|
|
289
|
+
type: 'object',
|
|
290
|
+
description: 'Optional decision to queue when blocking a session through the agent-loop endpoint.',
|
|
291
|
+
},
|
|
284
292
|
},
|
|
285
293
|
required: ['task_id'],
|
|
286
294
|
},
|
|
287
295
|
};
|
|
288
296
|
|
|
289
297
|
async function updateTaskHandler(args, apiClient) {
|
|
290
|
-
const { task_id, status, log_message, add_learning, release_claim } = args;
|
|
298
|
+
const { task_id, status, log_message, add_learning, release_claim, session_id, decision } = args;
|
|
291
299
|
let planId = args.plan_id;
|
|
292
300
|
|
|
301
|
+
if (session_id && (status === 'completed' || status === 'blocked')) {
|
|
302
|
+
try {
|
|
303
|
+
const path = status === 'blocked' ? 'block' : 'complete';
|
|
304
|
+
const response = await apiClient.axiosInstance.post(`/agent/work-sessions/${session_id}/${path}`, {
|
|
305
|
+
summary: log_message,
|
|
306
|
+
learning: add_learning ? { content: add_learning } : undefined,
|
|
307
|
+
decision,
|
|
308
|
+
});
|
|
309
|
+
return formatResponse(response.data);
|
|
310
|
+
} catch {
|
|
311
|
+
// Fall back to legacy fan-out for older APIs or if the session was not found.
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
293
315
|
// Resolve plan_id from task if not provided.
|
|
294
316
|
if (!planId) {
|
|
295
317
|
try {
|
|
@@ -405,6 +427,21 @@ async function claimNextTaskHandler(args, apiClient) {
|
|
|
405
427
|
const { scope = {}, ttl_minutes = 30, fresh = false, context_depth = 2, dry_run = false } = args;
|
|
406
428
|
const { plan_id, goal_id } = scope;
|
|
407
429
|
|
|
430
|
+
try {
|
|
431
|
+
const response = await apiClient.axiosInstance.post('/agent/work-sessions', {
|
|
432
|
+
plan_id,
|
|
433
|
+
goal_id,
|
|
434
|
+
ttl_minutes,
|
|
435
|
+
fresh,
|
|
436
|
+
dry_run,
|
|
437
|
+
depth: context_depth,
|
|
438
|
+
agent_id: 'mcp-agent',
|
|
439
|
+
});
|
|
440
|
+
return formatResponse(response.data);
|
|
441
|
+
} catch {
|
|
442
|
+
// Fall back to the pre-facade fan-out for self-hosted older APIs.
|
|
443
|
+
}
|
|
444
|
+
|
|
408
445
|
let chosen = null;
|
|
409
446
|
let source = null;
|
|
410
447
|
|
|
@@ -711,6 +748,37 @@ async function createSubtree(apiClient, planId, parentId, children, results) {
|
|
|
711
748
|
async function formIntentionHandler(args, apiClient) {
|
|
712
749
|
const { goal_id, title, description, rationale, status = 'active', visibility = 'private', tree = [] } = args;
|
|
713
750
|
|
|
751
|
+
if (apiClient.agentLoop?.createIntention) {
|
|
752
|
+
const treeError = validateTreeShape(tree);
|
|
753
|
+
if (treeError) {
|
|
754
|
+
return errorResponse('tree_shape_invalid', treeError);
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
const result = await apiClient.agentLoop.createIntention({
|
|
758
|
+
goal_id,
|
|
759
|
+
title,
|
|
760
|
+
description,
|
|
761
|
+
rationale,
|
|
762
|
+
status,
|
|
763
|
+
visibility,
|
|
764
|
+
tree,
|
|
765
|
+
});
|
|
766
|
+
return formatResponse({
|
|
767
|
+
...result,
|
|
768
|
+
plan_id: result.plan?.id || result.plan_id,
|
|
769
|
+
goal_id,
|
|
770
|
+
status: result.plan?.status || status,
|
|
771
|
+
is_draft: (result.plan?.status || status) === 'draft',
|
|
772
|
+
nodes_created: Array.isArray(result.tree) ? result.tree.length : undefined,
|
|
773
|
+
next_step: (result.plan?.status || status) === 'draft'
|
|
774
|
+
? "Plan created as draft. Will surface in dashboard pending for human review. Auto-promotes to active when first task moves to in_progress."
|
|
775
|
+
: "Plan active. Claim a task with claim_next_task({plan_id}) to begin work.",
|
|
776
|
+
});
|
|
777
|
+
} catch {
|
|
778
|
+
// Fall through to the legacy multi-call path for older/self-hosted APIs.
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
714
782
|
// Validate goal exists.
|
|
715
783
|
let goal;
|
|
716
784
|
try {
|
package/src/tools/bdi/utility.js
CHANGED
|
@@ -26,7 +26,7 @@ async function getStartedHandler(args) {
|
|
|
26
26
|
"Beliefs (state queries), Desires (goals), and Intentions (committed actions). " +
|
|
27
27
|
"Each tool answers one whole agentic question and returns an `as_of` timestamp.",
|
|
28
28
|
tools_by_namespace: {
|
|
29
|
-
beliefs: ['briefing', 'task_context', 'goal_state', 'recall_knowledge', 'search', 'plan_analysis'],
|
|
29
|
+
beliefs: ['briefing', 'list_plans', 'task_context', 'goal_state', 'recall_knowledge', 'search', 'plan_analysis'],
|
|
30
30
|
desires: ['list_goals', 'update_goal'],
|
|
31
31
|
intentions: ['claim_next_task', 'update_task', 'release_task', 'queue_decision', 'resolve_decision', 'add_learning'],
|
|
32
32
|
},
|