@agent-plan/mcp 0.4.6 → 0.4.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/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -49
- package/package.json +3 -3
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA4BA,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA2LrI;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAUzE;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA4BA,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA2LrI;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAUzE;AAk5FD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as z from "zod/v4";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
|
-
import { PlanStore, PlanStoreError, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, findIdeaByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, replaceChecklist, buildPhaseContextBlock, buildPhaseWorkMap, buildBoundedAcceptedDecisionContext, renderAcceptedDecisionsSection, checkExplicitTaskStart, recommendNextTask, recommendNextWork, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, createStaleProcessCheck, runtimeCapabilities, runtimePackagesDiagnostic, markCanonicalFullReadForSessionId, contextReadEligibilityForSession, requirementReadEligibilityForSession, hasValidSessionAttestation, markRequirementReadForSessionId, startReadSession, invalidateReads, taskStartDenied, taskStartSucceeded, taskStartGateState, taskStartGateStateLine, noMutableFieldsReceived, normalizeDescriptionRef, projectGuidelinesReadStateForSession, reconcileRequirementMacroTasks, RequirementMacroTaskError, HANDOFF_COMPLETENESS_AUDIT_VERSION, HANDOFF_COMPLETENESS_CATEGORIES, HANDOFF_COLD_START_INVENTORY_VERSION, HANDOFF_COLD_START_INVENTORY_CATEGORIES, HandoffContractError, buildHandoffShowReply, buildHandoffPrepareReply, buildProjectContextLoadReply, buildPlannerLoadReply, buildScopedDecisionContext, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS, projectContextStaleAdvisory, LEGACY_DECISIONS_ARRAY_READ_ONLY_MESSAGE, LEGACY_DECISIONS_ARRAY_READ_ONLY_ERROR_CODE, buildMutationReply, longTextFieldLimitNotice, buildRecommendationReply, buildTaskOrderContext, taskOrderContextLine, taskCreatedPriorityFragment, findHigherPriorityOpenPhase, higherPriorityOpenPhaseAdvisory, listOpenPhaseWork, openPhaseWorkLines, boundedOpenPhaseWork, deleteFeatureCascade, evaluateTaskCompletionGate, taskCompletionChecklistRefusal, taskCompletionForceMotivationRequired, taskCompletionOverrideNote, taskCompletionMismatchLine, boundedPage, nowISO, resolveFeatureRefStrict, resolveAcceptedDecisionTarget, applyTaskLifecycleDates, handoffContractFailureReply, derivedStatusReadOnlyReply, acceptedDecisionMutationFailureReply, legacyContextMigrationSummary, moveTasks, buildTaskMoveReply } from "@agent-plan/core";
|
|
8
|
+
import { PlanStore, PlanStoreError, taskOwnershipConflict, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, findIdeaByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, replaceChecklist, buildPhaseContextBlock, buildPhaseWorkMap, buildBoundedAcceptedDecisionContext, renderAcceptedDecisionsSection, checkExplicitTaskStart, recommendNextTask, recommendNextWork, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, createStaleProcessCheck, runtimeCapabilities, runtimePackagesDiagnostic, markCanonicalFullReadForSessionId, contextReadEligibilityForSession, requirementReadEligibilityForSession, hasValidSessionAttestation, markRequirementReadForSessionId, startReadSession, invalidateReads, taskStartDenied, taskStartSucceeded, taskStartGateState, taskStartGateStateLine, noMutableFieldsReceived, normalizeDescriptionRef, projectGuidelinesReadStateForSession, reconcileRequirementMacroTasks, RequirementMacroTaskError, HANDOFF_COMPLETENESS_AUDIT_VERSION, HANDOFF_COMPLETENESS_CATEGORIES, HANDOFF_COLD_START_INVENTORY_VERSION, HANDOFF_COLD_START_INVENTORY_CATEGORIES, HandoffContractError, buildHandoffShowReply, buildHandoffPrepareReply, buildProjectContextLoadReply, buildPlannerLoadReply, buildScopedDecisionContext, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS, projectContextStaleAdvisory, LEGACY_DECISIONS_ARRAY_READ_ONLY_MESSAGE, LEGACY_DECISIONS_ARRAY_READ_ONLY_ERROR_CODE, buildMutationReply, longTextFieldLimitNotice, buildRecommendationReply, buildTaskOrderContext, taskOrderContextLine, taskCreatedPriorityFragment, findHigherPriorityOpenPhase, higherPriorityOpenPhaseAdvisory, listOpenPhaseWork, openPhaseWorkLines, boundedOpenPhaseWork, deleteFeatureCascade, evaluateTaskCompletionGate, taskCompletionChecklistRefusal, taskCompletionForceMotivationRequired, taskCompletionOverrideNote, taskCompletionMismatchLine, boundedPage, nowISO, resolveFeatureRefStrict, resolveAcceptedDecisionTarget, applyTaskLifecycleDates, handoffContractFailureReply, derivedStatusReadOnlyReply, acceptedDecisionMutationFailureReply, legacyContextMigrationSummary, moveTasks, buildTaskMoveReply } from "@agent-plan/core";
|
|
9
9
|
import { serve } from "@agent-plan/server";
|
|
10
10
|
import { createChecklistItemId, createFeatureId, createPhaseId, createRequirementId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, formatIdeaRef, featureNumberOfPhase, validateResolvedTarget } from "@agent-plan/core/naming";
|
|
11
11
|
const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
|
|
@@ -201,10 +201,38 @@ function withStaleServerNotice(result) {
|
|
|
201
201
|
return { ...result, content: [...result.content, { type: "text", text: notice }] };
|
|
202
202
|
}
|
|
203
203
|
{
|
|
204
|
+
const sessionOperations = new Set(["planner-load", "planner-task-start", "planner-task-switch", "planner-task-pause", "planner-task-complete", "planner-task-reopen", "planner-task-update", "planner-task-deviation", "planner-session-recover"]);
|
|
205
|
+
const registeredSessions = new Set();
|
|
204
206
|
const registerToolUnwrapped = server.registerTool.bind(server);
|
|
205
207
|
server.registerTool = (...args) => {
|
|
206
208
|
const handler = args[args.length - 1];
|
|
207
|
-
const wrapped = async (...handlerArgs) =>
|
|
209
|
+
const wrapped = async (...handlerArgs) => {
|
|
210
|
+
const invoke = async () => foldTextIntoStructured(withStaleServerNotice(await handler(...handlerArgs)));
|
|
211
|
+
if (!sessionOperations.has(args[0]))
|
|
212
|
+
return invoke();
|
|
213
|
+
const st = store();
|
|
214
|
+
if (!(await st.exists()))
|
|
215
|
+
return invoke();
|
|
216
|
+
const sessionId = plannerSessionIdFor(handlerArgs[1]);
|
|
217
|
+
const prepare = async () => {
|
|
218
|
+
if (!registeredSessions.has(sessionId)) {
|
|
219
|
+
await st.registerSession(sessionId);
|
|
220
|
+
registeredSessions.add(sessionId);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
// Server/watchers must be created outside a write transaction's async context.
|
|
224
|
+
if (args[0] === "planner-load") {
|
|
225
|
+
await st.runBatch(prepare);
|
|
226
|
+
return invoke();
|
|
227
|
+
}
|
|
228
|
+
return st.runBatch(async () => {
|
|
229
|
+
await prepare();
|
|
230
|
+
const result = await invoke();
|
|
231
|
+
if (!result.isError)
|
|
232
|
+
await st.refreshResume(undefined, undefined, sessionId);
|
|
233
|
+
return result;
|
|
234
|
+
});
|
|
235
|
+
};
|
|
208
236
|
return registerToolUnwrapped(...args.slice(0, -1), wrapped);
|
|
209
237
|
};
|
|
210
238
|
}
|
|
@@ -2120,6 +2148,10 @@ server.registerTool("planner-task-switch", {
|
|
|
2120
2148
|
return text(`Task not found: ${to_task}`);
|
|
2121
2149
|
if (source.task.id === target.task.id)
|
|
2122
2150
|
return text("Source and temporary task must differ.");
|
|
2151
|
+
const allDeviations = await st.listAllWorkDeviations();
|
|
2152
|
+
if ([source.task, target.task].some((task) => taskOwnershipConflict(task, allDeviations, plannerSessionId))) {
|
|
2153
|
+
return { ...text("Task switch denied: source or target belongs to another session.", { started: false, errorCode: "TASK_OWNED_BY_ANOTHER_SESSION" }), isError: true };
|
|
2154
|
+
}
|
|
2123
2155
|
if (source.task.status !== "in-progress" && !source.task.pauseSnapshot) {
|
|
2124
2156
|
return text(`Task switch denied: source is ${source.task.status}; only in-progress or checkpointed work can be switched.`);
|
|
2125
2157
|
}
|
|
@@ -2202,27 +2234,7 @@ server.registerTool("planner-task-switch", {
|
|
|
2202
2234
|
});
|
|
2203
2235
|
}
|
|
2204
2236
|
sourceCheckpointed = true;
|
|
2205
|
-
|
|
2206
|
-
await st.resumeTask(target.phase.id, target.task.id, timestamp, plannerSessionId);
|
|
2207
|
-
}
|
|
2208
|
-
else {
|
|
2209
|
-
await st.updatePhase(target.phase.id, (phase) => {
|
|
2210
|
-
const task = phase.tasks.find((entry) => entry.id === target.task.id);
|
|
2211
|
-
if (!task)
|
|
2212
|
-
throw new Error(`Temporary task disappeared: ${to_task}`);
|
|
2213
|
-
const previousStatus = task.status;
|
|
2214
|
-
applyTaskLifecycleDates(task, "in-progress", timestamp);
|
|
2215
|
-
task.activeOwnerSession = plannerSessionId;
|
|
2216
|
-
task.statusLog = [...task.statusLog, {
|
|
2217
|
-
id: createChecklistItemId(task.id, task.statusLog.length + 1, `${previousStatus}-in-progress`),
|
|
2218
|
-
date: timestamp, fromStatus: previousStatus, toStatus: "in-progress",
|
|
2219
|
-
title: `${previousStatus} → in-progress`, description: `Temporary switch from ${source.task.title}.`,
|
|
2220
|
-
}];
|
|
2221
|
-
task.updatedAt = timestamp;
|
|
2222
|
-
phase.updatedAt = timestamp;
|
|
2223
|
-
return phase;
|
|
2224
|
-
});
|
|
2225
|
-
}
|
|
2237
|
+
await st.claimTask(target.phase.id, target.task.id, plannerSessionId, timestamp);
|
|
2226
2238
|
targetStarted = true;
|
|
2227
2239
|
await st.addWorkDeviation(record, plannerSessionId);
|
|
2228
2240
|
if (target.task.pauseSnapshot) {
|
|
@@ -2284,6 +2296,22 @@ server.registerTool("planner-task-reopen", {
|
|
|
2284
2296
|
return { ...text(`Task reopen denied: ${error instanceof Error ? error.message : "unknown error"}`, { reopened: false, errorCode: details?.errorCode ?? "TASK_REOPEN_FAILED" }), isError: true };
|
|
2285
2297
|
}
|
|
2286
2298
|
});
|
|
2299
|
+
server.registerTool("planner-session-recover", {
|
|
2300
|
+
description: "Explicitly recover a stopped caller's task ownership, complete return stack and resume focus into this fresh connection. Identify the old session by an active task or a pending return task, without client-side session-ID mapping. Requires confirmed=true after confirming the previous caller has stopped; refuses a registered live process and a caller that already owns work. Task statuses, checkpoints and context-read gates are preserved. Then read the task context and use planner-task-start normally.",
|
|
2301
|
+
inputSchema: { task: z.string().min(1), confirmed: z.boolean() },
|
|
2302
|
+
}, async ({ task: ref, confirmed }, extra) => {
|
|
2303
|
+
const st = await requireStore();
|
|
2304
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
2305
|
+
if (!found)
|
|
2306
|
+
return { ...text(`Task not found: ${ref}`, { recovered: false, errorCode: "TASK_NOT_FOUND" }), isError: true };
|
|
2307
|
+
try {
|
|
2308
|
+
const result = await st.recoverSessionForTask(found.phase.id, found.task.id, plannerSessionIdFor(extra), confirmed);
|
|
2309
|
+
return text("Session recovered. Existing tasks, checkpoints and return stack are preserved; read the task context before continuing.", { recovered: true, ...result });
|
|
2310
|
+
}
|
|
2311
|
+
catch (error) {
|
|
2312
|
+
return { ...text(`Session recovery denied: ${error instanceof Error ? error.message : String(error)}`, { recovered: false, errorCode: error instanceof PlanStoreError ? error.details?.errorCode ?? "SESSION_RECOVERY_FAILED" : "SESSION_RECOVERY_FAILED" }), isError: true };
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2287
2315
|
server.registerTool("planner-task-start", {
|
|
2288
2316
|
description: "Set a task to in-progress or resume checkpointed work. Call this tool before reading context so it can reuse valid sessionInfo attestations; if denied, perform only the missing/stale reads listed in nextActions, then retry. Reads may be performed in any order within the current session. If nextActions includes planner-project-guidelines-show, read that section and keep it in working memory while doing task work. Every denied start is an MCP isError result with started=false; NEVER claim work started unless structuredContent.started is true. Success is returned only after persisted in-progress state is verified.",
|
|
2289
2317
|
inputSchema: { task: z.string().min(1) },
|
|
@@ -2347,10 +2375,19 @@ server.registerTool("planner-task-start", {
|
|
|
2347
2375
|
await st.recordContextRead({ sessionId: plannerSessionId, phaseId: found.phase.id, taskId: found.task.id, ...(found.phase.featureId ? { featureId: found.phase.featureId } : {}), requirementIds: linkedRequirementIds });
|
|
2348
2376
|
}
|
|
2349
2377
|
if (found.task.status === "in-progress") {
|
|
2378
|
+
let claimed;
|
|
2379
|
+
try {
|
|
2380
|
+
claimed = await st.claimTask(found.phase.id, found.task.id, plannerSessionId);
|
|
2381
|
+
}
|
|
2382
|
+
catch (error) {
|
|
2383
|
+
if (!(error instanceof PlanStoreError))
|
|
2384
|
+
throw error;
|
|
2385
|
+
return taskStartError(taskStartDenied(error.details?.errorCode, error.message, ["Continue with the owning caller, or use planner-session-recover after confirming it has stopped."], { taskId: found.task.id }));
|
|
2386
|
+
}
|
|
2350
2387
|
const outcome = taskStartSucceeded(found.task.id, true);
|
|
2351
2388
|
const higherPriorityPhase = findHigherPriorityOpenPhase(initialPhases, features, found.phase.id, found.phase.priority ?? found.phase.number);
|
|
2352
2389
|
const phaseOrderAdvisory = higherPriorityOpenPhaseAdvisory(higherPriorityPhase);
|
|
2353
|
-
return text(`✅ Task already started: ${taskRef} — ${found.task.title} (in-progress)\nstarted: true${projectContextAdvisory}${phaseOrderAdvisory}`, { ...outcome, task:
|
|
2390
|
+
return text(`✅ Task already started: ${taskRef} — ${found.task.title} (in-progress)\nstarted: true${projectContextAdvisory}${phaseOrderAdvisory}`, { ...outcome, task: claimed.task, projectContextStale: projectContextReadState === "stale", higherPriorityOpenPhase: higherPriorityPhase });
|
|
2354
2391
|
}
|
|
2355
2392
|
const [phases, focus] = await Promise.all([st.loadAllPhases(), st.loadResume(plannerSessionId)]);
|
|
2356
2393
|
const eligibility = checkExplicitTaskStart(features, phases, found.task.id, project.workDeviations);
|
|
@@ -2405,31 +2442,13 @@ server.registerTool("planner-task-start", {
|
|
|
2405
2442
|
: "";
|
|
2406
2443
|
const timestamp = nowISO();
|
|
2407
2444
|
let updatedTask;
|
|
2408
|
-
|
|
2409
|
-
updatedTask = await st.
|
|
2445
|
+
try {
|
|
2446
|
+
updatedTask = (await st.claimTask(found.phase.id, found.task.id, plannerSessionId, timestamp)).task;
|
|
2410
2447
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
return phase;
|
|
2416
|
-
const previousStatus = task.status;
|
|
2417
|
-
applyTaskLifecycleDates(task, "in-progress", timestamp);
|
|
2418
|
-
const entry = {
|
|
2419
|
-
id: createChecklistItemId(task.id, (task.statusLog?.length ?? 0) + 1, `${previousStatus}-in-progress`),
|
|
2420
|
-
date: timestamp,
|
|
2421
|
-
fromStatus: previousStatus,
|
|
2422
|
-
toStatus: "in-progress",
|
|
2423
|
-
title: `${previousStatus} → in-progress`,
|
|
2424
|
-
description: "",
|
|
2425
|
-
};
|
|
2426
|
-
task.activeOwnerSession = plannerSessionId;
|
|
2427
|
-
task.statusLog = [...(task.statusLog ?? []), entry];
|
|
2428
|
-
task.updatedAt = timestamp;
|
|
2429
|
-
phase.updatedAt = timestamp;
|
|
2430
|
-
updatedTask = task;
|
|
2431
|
-
return phase;
|
|
2432
|
-
});
|
|
2448
|
+
catch (error) {
|
|
2449
|
+
if (!(error instanceof PlanStoreError))
|
|
2450
|
+
throw error;
|
|
2451
|
+
return taskStartError(taskStartDenied(error.details?.errorCode, error.message, ["Re-read the task and reconcile its owner before retrying."], { taskId: found.task.id }));
|
|
2433
2452
|
}
|
|
2434
2453
|
await st.syncTaskStatusRollup(found.phase.id);
|
|
2435
2454
|
const approvedDeviation = project.workDeviations.find((deviation) => deviation.temporaryTaskId === found.task.id && deviation.state === "approved");
|
|
@@ -2443,7 +2462,7 @@ server.registerTool("planner-task-start", {
|
|
|
2443
2462
|
await st.setWorkDeviationState(resumedDeviation.id, "resumed", timestamp);
|
|
2444
2463
|
await st.writeGenerated();
|
|
2445
2464
|
const persistedTask = (await st.loadPhase(found.phase.id)).tasks.find((candidate) => candidate.id === found.task.id);
|
|
2446
|
-
if (!updatedTask || persistedTask?.status !== "in-progress") {
|
|
2465
|
+
if (!updatedTask || persistedTask?.status !== "in-progress" || persistedTask.activeOwnerSession !== plannerSessionId) {
|
|
2447
2466
|
return taskStartError(taskStartDenied("PERSISTENCE_VERIFICATION_FAILED", `The lifecycle write could not be verified as in-progress for ${taskRef}.`, ["Inspect planner-task-show for the persisted state and retry only after resolving the persistence failure."], { taskId: found.task.id }));
|
|
2448
2467
|
}
|
|
2449
2468
|
const outcome = taskStartSucceeded(persistedTask.id);
|
|
@@ -3164,6 +3183,7 @@ server.registerTool("planner-load", {
|
|
|
3164
3183
|
return text(`Planner load aborted: ${error instanceof Error ? error.message : String(error)}`, { errorCode: "PLANNER_SESSION_PREPARATION_FAILED", loaded: false });
|
|
3165
3184
|
}
|
|
3166
3185
|
const project = preparation.project;
|
|
3186
|
+
await st.runBatch(() => st.refreshResume(undefined, undefined, plannerSessionId));
|
|
3167
3187
|
if (project.projectGuidelines.content.trim()) {
|
|
3168
3188
|
await st.recordProjectGuidelinesRead({ sessionId: plannerSessionId });
|
|
3169
3189
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
45
45
|
"zod": "^3.24.0",
|
|
46
|
-
"@agent-plan/
|
|
47
|
-
"@agent-plan/
|
|
46
|
+
"@agent-plan/server": "0.4.8",
|
|
47
|
+
"@agent-plan/core": "0.4.8"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsc -p tsconfig.json",
|