@agent-plan/pi-adapter 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 +78 -44
- 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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAw0BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAw0BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAy2K9D"}
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
import { paginatedSelect, paginatedNotify } from "./ui/paginate.js";
|
|
13
|
-
import { ExportService, PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, findIdeaByRef, buildRecap, classifyGuardedTool, loadNoTaskGuardState, noTaskWarning, addChecklistItem, removeChecklistItem, toggleChecklistItem, replaceChecklist, buildPhaseContextBlock, buildPhaseWorkMap, buildBoundedAcceptedDecisionContext, renderAcceptedDecisionsSection, checkExplicitTaskStart, recommendNextTask, recommendNextWork, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, 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_SOURCE_REVIEWS, HANDOFF_COLD_START_INVENTORY_CATEGORIES, handoffContentHash, HandoffContractError, buildHandoffShowReply, buildHandoffPrepareReply, buildProjectContextLoadReply, buildPlannerLoadReply, buildScopedDecisionContext, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS, projectContextStaleAdvisory, ACCEPTED_DECISION_OWNERSHIP_RULE, ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE, LEGACY_DECISIONS_ARRAY_READ_ONLY_MESSAGE, ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, LEGACY_DECISIONS_ARRAY_READ_ONLY_ERROR_CODE, buildDecisionRecordRedirectReply, 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, statusIcon, moveTasks, buildTaskMoveReply } from "@agent-plan/core";
|
|
13
|
+
import { ExportService, PlanStore, PlanStoreError, taskOwnershipConflict, setWriteBusyHook, setWriteNotifyHook, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, findIdeaByRef, buildRecap, classifyGuardedTool, loadNoTaskGuardState, noTaskWarning, addChecklistItem, removeChecklistItem, toggleChecklistItem, replaceChecklist, buildPhaseContextBlock, buildPhaseWorkMap, buildBoundedAcceptedDecisionContext, renderAcceptedDecisionsSection, checkExplicitTaskStart, recommendNextTask, recommendNextWork, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, 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_SOURCE_REVIEWS, HANDOFF_COLD_START_INVENTORY_CATEGORIES, handoffContentHash, HandoffContractError, buildHandoffShowReply, buildHandoffPrepareReply, buildProjectContextLoadReply, buildPlannerLoadReply, buildScopedDecisionContext, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS, projectContextStaleAdvisory, ACCEPTED_DECISION_OWNERSHIP_RULE, ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE, LEGACY_DECISIONS_ARRAY_READ_ONLY_MESSAGE, ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, LEGACY_DECISIONS_ARRAY_READ_ONLY_ERROR_CODE, buildDecisionRecordRedirectReply, 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, statusIcon, moveTasks, buildTaskMoveReply } from "@agent-plan/core";
|
|
14
14
|
import { createChecklistItemId, createFeatureId, createPhaseId, createRequirementId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, formatIdeaRef, featureNumberOfPhase, validateResolvedTarget } from "@agent-plan/core/naming";
|
|
15
15
|
/** Stated on every long-prose input so a caller learns the limit before the
|
|
16
16
|
* call rather than from the rejection. The number lives in the core
|
|
@@ -775,6 +775,37 @@ async function stopServer() {
|
|
|
775
775
|
// ─── Extension factory ──────────────────────────────────────────────────
|
|
776
776
|
export default function planPiExtension(pi) {
|
|
777
777
|
capturedPi = pi;
|
|
778
|
+
const registerSessionTool = pi.registerTool.bind(pi);
|
|
779
|
+
const sessionTools = new Set(["planner-load", "task_start", "task_switch", "task_pause", "task_complete", "task_reopen", "task_update", "task_deviation", "session_recover"]);
|
|
780
|
+
const registeredSessions = new Set();
|
|
781
|
+
pi.registerTool = (definition) => {
|
|
782
|
+
if (!sessionTools.has(definition.name))
|
|
783
|
+
return registerSessionTool(definition);
|
|
784
|
+
const execute = definition.execute;
|
|
785
|
+
registerSessionTool({ ...definition, async execute(id, params, signal, onUpdate, ctx) {
|
|
786
|
+
const st = await requirePlan(ctx);
|
|
787
|
+
if (!st)
|
|
788
|
+
return execute(id, params, signal, onUpdate, ctx);
|
|
789
|
+
const sessionId = piSessionIdOf(ctx);
|
|
790
|
+
const prepare = async () => {
|
|
791
|
+
if (!registeredSessions.has(sessionId)) {
|
|
792
|
+
await st.registerSession(sessionId);
|
|
793
|
+
registeredSessions.add(sessionId);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
if (definition.name === "planner-load") {
|
|
797
|
+
await st.runBatch(prepare);
|
|
798
|
+
return execute(id, params, signal, onUpdate, ctx);
|
|
799
|
+
}
|
|
800
|
+
return st.runBatch(async () => {
|
|
801
|
+
await prepare();
|
|
802
|
+
const result = await execute(id, params, signal, onUpdate, ctx);
|
|
803
|
+
if (!result.isError)
|
|
804
|
+
await st.refreshResume(undefined, undefined, sessionId);
|
|
805
|
+
return result;
|
|
806
|
+
});
|
|
807
|
+
} });
|
|
808
|
+
};
|
|
778
809
|
// ── Restore on session start/reload ─────────────────────────────────
|
|
779
810
|
pi.on("session_start", async (_event, ctx) => {
|
|
780
811
|
plannerSessionId = piSessionIdOf(ctx);
|
|
@@ -5570,6 +5601,10 @@ export default function planPiExtension(pi) {
|
|
|
5570
5601
|
return { content: [{ type: "text", text: `Task not found: ${params.to_task}` }], details: {} };
|
|
5571
5602
|
if (source.task.id === target.task.id)
|
|
5572
5603
|
return { content: [{ type: "text", text: "Source and temporary task must differ." }], details: {} };
|
|
5604
|
+
const allDeviations = await st.listAllWorkDeviations();
|
|
5605
|
+
if ([source.task, target.task].some((task) => taskOwnershipConflict(task, allDeviations, plannerSessionId))) {
|
|
5606
|
+
return { content: [{ type: "text", text: "Task switch denied: source or target belongs to another session." }], details: { started: false, errorCode: "TASK_OWNED_BY_ANOTHER_SESSION" }, isError: true };
|
|
5607
|
+
}
|
|
5573
5608
|
if (source.task.status !== "in-progress" && !source.task.pauseSnapshot) {
|
|
5574
5609
|
return { content: [{ type: "text", text: `Task switch denied: source is ${source.task.status}; only in-progress or checkpointed work can be switched.` }], details: {} };
|
|
5575
5610
|
}
|
|
@@ -5655,27 +5690,7 @@ export default function planPiExtension(pi) {
|
|
|
5655
5690
|
});
|
|
5656
5691
|
}
|
|
5657
5692
|
sourceCheckpointed = true;
|
|
5658
|
-
|
|
5659
|
-
await st.resumeTask(target.phase.id, target.task.id, timestamp, plannerSessionId);
|
|
5660
|
-
}
|
|
5661
|
-
else {
|
|
5662
|
-
await st.updatePhase(target.phase.id, (phase) => {
|
|
5663
|
-
const task = phase.tasks.find((entry) => entry.id === target.task.id);
|
|
5664
|
-
if (!task)
|
|
5665
|
-
throw new Error(`Temporary task disappeared: ${params.to_task}`);
|
|
5666
|
-
const previousStatus = task.status;
|
|
5667
|
-
applyTaskLifecycleDates(task, "in-progress", timestamp);
|
|
5668
|
-
task.activeOwnerSession = plannerSessionId;
|
|
5669
|
-
task.statusLog = [...task.statusLog, {
|
|
5670
|
-
id: createChecklistItemId(task.id, task.statusLog.length + 1, `${previousStatus}-in-progress`),
|
|
5671
|
-
date: timestamp, fromStatus: previousStatus, toStatus: "in-progress",
|
|
5672
|
-
title: `${previousStatus} → in-progress`, description: `Temporary switch from ${source.task.title}.`,
|
|
5673
|
-
}];
|
|
5674
|
-
task.updatedAt = timestamp;
|
|
5675
|
-
phase.updatedAt = timestamp;
|
|
5676
|
-
return phase;
|
|
5677
|
-
});
|
|
5678
|
-
}
|
|
5693
|
+
await st.claimTask(target.phase.id, target.task.id, plannerSessionId, timestamp);
|
|
5679
5694
|
targetStarted = true;
|
|
5680
5695
|
await st.addWorkDeviation(record, plannerSessionId);
|
|
5681
5696
|
if (target.task.pauseSnapshot) {
|
|
@@ -5749,6 +5764,27 @@ export default function planPiExtension(pi) {
|
|
|
5749
5764
|
}
|
|
5750
5765
|
},
|
|
5751
5766
|
});
|
|
5767
|
+
pi.registerTool({
|
|
5768
|
+
name: "session_recover",
|
|
5769
|
+
label: "Session Recover",
|
|
5770
|
+
description: "Explicitly recover a stopped caller's complete task ownership, return stack and focus into this fresh session. Confirm that the previous caller has stopped. Registered live processes and callers that already own work are refused. Then read context and use task_start normally.",
|
|
5771
|
+
parameters: Type.Object({ taskId: Type.String(), confirmed: Type.Boolean() }),
|
|
5772
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
5773
|
+
const st = await requirePlan(ctx);
|
|
5774
|
+
if (!st)
|
|
5775
|
+
return { content: [{ type: "text", text: "No planner found." }], details: { recovered: false, errorCode: "PLAN_NOT_FOUND" }, isError: true };
|
|
5776
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, params.taskId);
|
|
5777
|
+
if (!found)
|
|
5778
|
+
return { content: [{ type: "text", text: "Task not found." }], details: { recovered: false, errorCode: "TASK_NOT_FOUND" }, isError: true };
|
|
5779
|
+
try {
|
|
5780
|
+
const recovered = await st.recoverSessionForTask(found.phase.id, found.task.id, piSessionIdOf(ctx), params.confirmed);
|
|
5781
|
+
return { content: [{ type: "text", text: "Session recovered; tasks, checkpoints and return stack are preserved. Read task context before continuing." }], details: { recovered: true, ...recovered } };
|
|
5782
|
+
}
|
|
5783
|
+
catch (error) {
|
|
5784
|
+
return { content: [{ type: "text", text: `Session recovery denied: ${error instanceof Error ? error.message : String(error)}` }], details: { recovered: false, errorCode: error instanceof PlanStoreError ? error.details?.errorCode ?? "SESSION_RECOVERY_FAILED" : "SESSION_RECOVERY_FAILED" }, isError: true };
|
|
5785
|
+
}
|
|
5786
|
+
},
|
|
5787
|
+
});
|
|
5752
5788
|
pi.registerTool({
|
|
5753
5789
|
name: "task_start",
|
|
5754
5790
|
label: "Task Start",
|
|
@@ -5822,10 +5858,20 @@ export default function planPiExtension(pi) {
|
|
|
5822
5858
|
});
|
|
5823
5859
|
}
|
|
5824
5860
|
if (task.status === "in-progress") {
|
|
5861
|
+
let claimed;
|
|
5862
|
+
try {
|
|
5863
|
+
claimed = await st.claimTask(found.phase.id, task.id, plannerSessionId);
|
|
5864
|
+
}
|
|
5865
|
+
catch (error) {
|
|
5866
|
+
if (!(error instanceof PlanStoreError))
|
|
5867
|
+
throw error;
|
|
5868
|
+
return taskStartFailure(taskStartDenied(error.details?.errorCode, error.message, ["Continue with the owning caller, or explicitly recover its session after it has stopped."], { taskId: task.id }));
|
|
5869
|
+
}
|
|
5870
|
+
await st.refreshResume(undefined, undefined, plannerSessionId);
|
|
5825
5871
|
const outcome = taskStartSucceeded(task.id, true);
|
|
5826
5872
|
const higherPriorityPhase = findHigherPriorityOpenPhase(initialPhases, features, found.phase.id, found.phase.priority ?? found.phase.number);
|
|
5827
5873
|
const phaseOrderAdvisory = higherPriorityOpenPhaseAdvisory(higherPriorityPhase);
|
|
5828
|
-
return { content: [{ type: "text", text: `✅ Task already started: ${taskRef} — ${task.title} (in-progress)\nstarted: true${projectContextAdvisory}${phaseOrderAdvisory}` }], details: { ...task, ...outcome, task, projectContextStale: projectContextReadState === "stale", higherPriorityOpenPhase: higherPriorityPhase } };
|
|
5874
|
+
return { content: [{ type: "text", text: `✅ Task already started: ${taskRef} — ${task.title} (in-progress)\nstarted: true${projectContextAdvisory}${phaseOrderAdvisory}` }], details: { ...claimed.task, ...outcome, task: claimed.task, projectContextStale: projectContextReadState === "stale", higherPriorityOpenPhase: higherPriorityPhase } };
|
|
5829
5875
|
}
|
|
5830
5876
|
const [phases, focus] = await Promise.all([st.loadAllPhases(), st.loadResume(plannerSessionId)]);
|
|
5831
5877
|
const eligibility = checkExplicitTaskStart(features, phases, task.id, project.workDeviations);
|
|
@@ -5876,27 +5922,13 @@ export default function planPiExtension(pi) {
|
|
|
5876
5922
|
: "";
|
|
5877
5923
|
const now = nowISO();
|
|
5878
5924
|
let startedTask;
|
|
5879
|
-
|
|
5880
|
-
startedTask = await st.
|
|
5925
|
+
try {
|
|
5926
|
+
startedTask = (await st.claimTask(found.phase.id, found.task.id, plannerSessionId, now)).task;
|
|
5881
5927
|
}
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
return phase;
|
|
5887
|
-
const previousStatus = t.status;
|
|
5888
|
-
applyTaskLifecycleDates(t, "in-progress", now);
|
|
5889
|
-
t.activeOwnerSession = plannerSessionId;
|
|
5890
|
-
t.statusLog = [...t.statusLog, {
|
|
5891
|
-
id: createChecklistItemId(t.id, t.statusLog.length + 1, `${previousStatus}-in-progress`),
|
|
5892
|
-
date: now, fromStatus: previousStatus, toStatus: "in-progress",
|
|
5893
|
-
title: `${previousStatus} → in-progress`, description: "",
|
|
5894
|
-
}];
|
|
5895
|
-
t.updatedAt = now;
|
|
5896
|
-
phase.updatedAt = now;
|
|
5897
|
-
startedTask = t;
|
|
5898
|
-
return phase;
|
|
5899
|
-
});
|
|
5928
|
+
catch (error) {
|
|
5929
|
+
if (!(error instanceof PlanStoreError))
|
|
5930
|
+
throw error;
|
|
5931
|
+
return taskStartFailure(taskStartDenied(error.details?.errorCode, error.message, ["Re-read the task and reconcile its owner before retrying."], { taskId: task.id }));
|
|
5900
5932
|
}
|
|
5901
5933
|
await st.syncTaskStatusRollup(found.phase.id);
|
|
5902
5934
|
const approvedDeviation = project.workDeviations.find((deviation) => deviation.temporaryTaskId === task.id && deviation.state === "approved");
|
|
@@ -5910,10 +5942,11 @@ export default function planPiExtension(pi) {
|
|
|
5910
5942
|
await st.setWorkDeviationState(resumedDeviation.id, "resumed", now);
|
|
5911
5943
|
await st.writeGenerated();
|
|
5912
5944
|
const persistedTask = (await st.loadPhase(found.phase.id)).tasks.find((candidate) => candidate.id === task.id);
|
|
5913
|
-
if (!startedTask || persistedTask?.status !== "in-progress") {
|
|
5945
|
+
if (!startedTask || persistedTask?.status !== "in-progress" || persistedTask.activeOwnerSession !== plannerSessionId) {
|
|
5914
5946
|
return taskStartFailure(taskStartDenied("PERSISTENCE_VERIFICATION_FAILED", `The lifecycle write could not be verified as in-progress for ${taskRef}.`, ["Inspect task_get for the persisted state and retry task_start only after resolving the persistence failure."], { taskId: task.id }));
|
|
5915
5947
|
}
|
|
5916
5948
|
const outcome = taskStartSucceeded(persistedTask.id);
|
|
5949
|
+
await st.refreshResume(undefined, undefined, plannerSessionId);
|
|
5917
5950
|
const higherPriorityPhase = findHigherPriorityOpenPhase(phases, features, found.phase.id, found.phase.priority ?? found.phase.number);
|
|
5918
5951
|
const phaseOrderAdvisory = higherPriorityOpenPhaseAdvisory(higherPriorityPhase);
|
|
5919
5952
|
return {
|
|
@@ -6199,6 +6232,7 @@ export default function planPiExtension(pi) {
|
|
|
6199
6232
|
catch (error) {
|
|
6200
6233
|
return { content: [{ type: "text", text: `Planner load aborted: ${error instanceof Error ? error.message : String(error)}` }], details: { errorCode: "PLANNER_SESSION_PREPARATION_FAILED", enabled: false, running: false } };
|
|
6201
6234
|
}
|
|
6235
|
+
await st.runBatch(() => st.refreshResume(undefined, undefined, piSessionIdOf(ctx)));
|
|
6202
6236
|
enablePlannerSession();
|
|
6203
6237
|
if (!server) {
|
|
6204
6238
|
await startServer(ctx, undefined, "lan").catch(() => { });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/pi-adapter",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pi agent extension for Agent Plan — structured project planning in .planner/",
|
|
6
6
|
"keywords": [
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"zod": "^3.24.0",
|
|
30
|
-
"@agent-plan/
|
|
31
|
-
"@agent-plan/
|
|
30
|
+
"@agent-plan/server": "0.4.8",
|
|
31
|
+
"@agent-plan/core": "0.4.8"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"typebox": "*"
|