@agent-plan/pi-adapter 0.3.0 → 0.3.1
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 +56 -82
- 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;
|
|
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;AAm0BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4yK9D"}
|
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, 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, 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 } from "@agent-plan/core";
|
|
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 } 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
|
|
@@ -146,7 +146,7 @@ setWriteNotifyHook(() => {
|
|
|
146
146
|
});
|
|
147
147
|
let plannerSessionEnabled = false;
|
|
148
148
|
// In-process guard bypass (per session, NOT persisted/shared). The edit/write
|
|
149
|
-
//
|
|
149
|
+
// The no-task guard only ever warns the agent; it never blocks. When a
|
|
150
150
|
// bypass is active the warning is silenced for the window. Lives in memory only:
|
|
151
151
|
// not on disk, not in git, not shared across agents/sessions on the same folder.
|
|
152
152
|
let guardBypassUntil = "";
|
|
@@ -311,28 +311,6 @@ async function ensureProjectLanguagePreferences(st, persist = true) {
|
|
|
311
311
|
}
|
|
312
312
|
return project;
|
|
313
313
|
}
|
|
314
|
-
async function getPlannerExecutionGuard(st) {
|
|
315
|
-
const [workspace, resume] = await Promise.all([st.loadAll(), st.loadResume()]);
|
|
316
|
-
const allTasks = workspace.phases.flatMap((phase) => phase.tasks.map((task) => ({ phase, task })));
|
|
317
|
-
const inProgress = allTasks.filter(({ task }) => task.status === "in-progress");
|
|
318
|
-
const totalTasks = allTasks.length;
|
|
319
|
-
const focusFromResume = resume?.inProgressTaskIds?.[0]
|
|
320
|
-
? allTasks.find(({ task }) => task.id === resume.inProgressTaskIds[0])
|
|
321
|
-
: undefined;
|
|
322
|
-
const focusFromCurrentPhase = resume?.currentPhaseId
|
|
323
|
-
? allTasks.find(({ phase, task }) => phase.id === resume.currentPhaseId && task.status !== "done" && task.status !== "canceled" && task.status !== "rejected")
|
|
324
|
-
: undefined;
|
|
325
|
-
const fallbackFocus = allTasks.find(({ task }) => task.pauseSnapshot)
|
|
326
|
-
?? allTasks.find(({ task }) => task.status === "planned" || task.status === "blocked" || task.status === "waiting")
|
|
327
|
-
?? allTasks.find(({ task }) => task.status !== "done" && task.status !== "canceled" && task.status !== "rejected");
|
|
328
|
-
const focus = focusFromResume ?? focusFromCurrentPhase ?? fallbackFocus;
|
|
329
|
-
return {
|
|
330
|
-
totalTasks,
|
|
331
|
-
inProgressTaskIds: inProgress.map(({ task }) => task.id),
|
|
332
|
-
focusTaskId: focus?.task.id ?? "",
|
|
333
|
-
focusTaskTitle: focus?.task.title ?? "",
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
314
|
async function buildHandoffMarkdown(st, reason, overrides) {
|
|
337
315
|
const [plan, resume, activity] = await Promise.all([
|
|
338
316
|
st.loadAll(),
|
|
@@ -967,57 +945,45 @@ export default function planPiExtension(pi) {
|
|
|
967
945
|
}
|
|
968
946
|
}
|
|
969
947
|
}
|
|
970
|
-
// Guard the code-writing tools (edit/write). bash stays free so that
|
|
971
|
-
// git pull, build, test, ls, etc. always work.
|
|
972
|
-
if (event.toolName !== "edit" && event.toolName !== "write")
|
|
973
|
-
return;
|
|
974
|
-
// Planner-internal writes (.planner/HANDOFF.md, resume.json, generated plan
|
|
975
|
-
// files, etc.) are planner operations, NOT code edits. They never require a
|
|
976
|
-
// task in-progress — skip the guard so the handoff can always be written.
|
|
977
|
-
const targetPath = String(event.input?.path ?? event.input?.filePath ?? "");
|
|
978
|
-
if (targetPath && (targetPath.includes("/.planner/") || targetPath.includes("\\.planner\\") || targetPath.startsWith(".planner/")))
|
|
979
|
-
return;
|
|
980
|
-
const st = loadStore(ctx);
|
|
981
|
-
if (!(await st.exists().catch(() => false)))
|
|
982
|
-
return;
|
|
983
|
-
await maybeHealStatuses(st, ctx).catch(() => { });
|
|
984
|
-
const guard = await getPlannerExecutionGuard(st).catch(() => null);
|
|
985
|
-
if (!guard || guard.totalTasks === 0)
|
|
986
|
-
return; // nothing to enforce yet
|
|
987
|
-
if (guard.inProgressTaskIds.length > 0)
|
|
988
|
-
return; // a task is open → we're good
|
|
989
|
-
if (isGuardBypassed())
|
|
990
|
-
return; // user authorized proceeding without a task
|
|
991
|
-
const focusHint = guard.focusTaskId
|
|
992
|
-
? `Il task più probabile è ${guard.focusTaskId} — ${guard.focusTaskTitle}. Avvialo con: \`/planner task start ${guard.focusTaskId}\``
|
|
993
|
-
: `Scegli un task dal piano e avvialo con \`/planner task start <taskId>\`.`;
|
|
994
|
-
ctx.ui.notify(`⚠️ NO ACTIVE TASK: You are editing files without an in-progress task. Remember to update the plan to maintain dashboard integrity. ${focusHint}`, "warning");
|
|
995
|
-
return; // Allow the tool to proceed
|
|
996
948
|
});
|
|
997
949
|
// Reset per-turn flags at the start of each turn.
|
|
998
950
|
pi.on("turn_start", async () => {
|
|
999
951
|
taskCompleteReminderSaidThisTurn = false;
|
|
1000
952
|
});
|
|
1001
|
-
// After
|
|
1002
|
-
//
|
|
953
|
+
// After a tool runs, tell the agent (never block) when it changed project
|
|
954
|
+
// code with no task in progress; otherwise, once per turn while a task is
|
|
955
|
+
// in progress, remind it to complete the task. Notes are appended to the
|
|
956
|
+
// tool's own output, which Pi would otherwise replace.
|
|
1003
957
|
pi.on("tool_result", async (event, ctx) => {
|
|
1004
958
|
if (!plannerSessionEnabled)
|
|
1005
959
|
return;
|
|
1006
|
-
if (event.toolName !== "edit" && event.toolName !== "write")
|
|
1007
|
-
return;
|
|
1008
|
-
if (taskCompleteReminderSaidThisTurn)
|
|
960
|
+
if (event.toolName !== "edit" && event.toolName !== "write" && event.toolName !== "bash")
|
|
1009
961
|
return;
|
|
1010
962
|
try {
|
|
1011
|
-
const
|
|
1012
|
-
|
|
963
|
+
const input = event.input;
|
|
964
|
+
const stringField = (value) => typeof value === "string" && value ? value : undefined;
|
|
965
|
+
const filePath = stringField(input.path ?? input.filePath);
|
|
966
|
+
const command = stringField(input.command);
|
|
967
|
+
const guardEvent = {
|
|
968
|
+
toolName: event.toolName,
|
|
969
|
+
cwd: ctx.cwd,
|
|
970
|
+
plannerRoot: resolvePlanRoot(ctx.cwd),
|
|
971
|
+
...(filePath !== undefined ? { filePath } : {}),
|
|
972
|
+
...(command !== undefined ? { command } : {}),
|
|
973
|
+
};
|
|
974
|
+
if (!classifyGuardedTool(guardEvent).guarded)
|
|
975
|
+
return;
|
|
976
|
+
const state = await loadNoTaskGuardState(loadStore(ctx));
|
|
977
|
+
const { warning } = noTaskWarning(guardEvent, { ...state, guardBypassed: state.guardBypassed || isGuardBypassed() });
|
|
978
|
+
const note = warning
|
|
979
|
+
?? (state.hasInProgressTask && !taskCompleteReminderSaidThisTurn
|
|
980
|
+
? "Reminder: when this implementation work is finished, call task_complete (or /planner task complete) so the task status moves to done and phase/feature rollups stay correct."
|
|
981
|
+
: undefined);
|
|
982
|
+
if (!note)
|
|
1013
983
|
return;
|
|
1014
|
-
|
|
1015
|
-
if (guard && guard.inProgressTaskIds.length > 0) {
|
|
984
|
+
if (!warning)
|
|
1016
985
|
taskCompleteReminderSaidThisTurn = true;
|
|
1017
|
-
|
|
1018
|
-
content: [{ type: "text", text: "Reminder: when this implementation work is finished, call task_complete (or /planner task complete) so the task status moves to done and phase/feature rollups stay correct." }],
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
986
|
+
return { content: [...event.content, { type: "text", text: note }] };
|
|
1021
987
|
}
|
|
1022
988
|
catch { }
|
|
1023
989
|
});
|
|
@@ -2764,38 +2730,38 @@ export default function planPiExtension(pi) {
|
|
|
2764
2730
|
// turn (before_agent_start's systemPrompt injection is NOT applied to
|
|
2765
2731
|
// triggerTurn messages), so the recap data must live here. Uses the shared
|
|
2766
2732
|
// core buildRecap so Pi and Claude Code/Codex present identical content.
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2733
|
+
// One text, each piece once — the same builder MCP's planner-load and
|
|
2734
|
+
// Pi's planner-load tool use. In Pi this command is the only way the
|
|
2735
|
+
// operating guide reaches the agent, so the guide body is included.
|
|
2736
|
+
let loadText = "";
|
|
2770
2737
|
try {
|
|
2771
2738
|
const plannerSkill = await st.syncPlannerSkill();
|
|
2772
2739
|
await st.syncGrillMeSkill();
|
|
2773
2740
|
const srv = server;
|
|
2774
|
-
recapText = await buildRecap(st, { localUrl: srv?.localUrl, lanUrl: srv?.lanUrl, port: lastKnownWebPort ?? undefined }, { harness: "pi" });
|
|
2741
|
+
let recapText = await buildRecap(st, { localUrl: srv?.localUrl, lanUrl: srv?.lanUrl, port: lastKnownWebPort ?? undefined }, { harness: "pi" });
|
|
2775
2742
|
if (preparation.changed)
|
|
2776
2743
|
recapText = `${preparation.legacyProjectContext.summary}\n\n${recapText}`;
|
|
2777
2744
|
// Explicit planner load must deliver the complete project-level
|
|
2778
2745
|
// context (P102(F005)/T404), attested through the same lossless
|
|
2779
|
-
// contract used everywhere else.
|
|
2780
|
-
// single shared shaping for this reply — the planner-load tool below
|
|
2781
|
-
// uses the identical builder. Like plannerSkillContext, this is
|
|
2782
|
-
// agent-only: it must never be quoted into the human-facing recap
|
|
2783
|
-
// (see the "must not leak into the human recap" invariant covered by
|
|
2784
|
-
// host-registration.test.mjs for the planner-load tool).
|
|
2746
|
+
// contract used everywhere else.
|
|
2785
2747
|
const projectContextDelivery = await st.loadProjectContextDelivery(DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS);
|
|
2786
2748
|
const projectContextReply = buildProjectContextLoadReply(projectContextDelivery, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS);
|
|
2787
2749
|
if (projectContextReply.structured.contextComplete) {
|
|
2788
2750
|
await st.recordProjectContextRead({ sessionId: plannerSessionId, chunks: projectContextDelivery.chunks });
|
|
2789
2751
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2752
|
+
loadText = buildPlannerLoadReply({
|
|
2753
|
+
recap: recapText,
|
|
2754
|
+
projectContext: projectContextReply,
|
|
2755
|
+
scopedDecisions: buildScopedDecisionContext(await st.loadAll()),
|
|
2756
|
+
plannerGuide: { mode: "include", content: plannerSkill.content, customized: plannerSkill.customized, message: plannerSkill.message },
|
|
2757
|
+
}).text;
|
|
2792
2758
|
}
|
|
2793
2759
|
catch (e) {
|
|
2794
|
-
|
|
2760
|
+
loadText = `(recap unavailable: ${e instanceof Error ? e.message : String(e)})`;
|
|
2795
2761
|
}
|
|
2796
2762
|
pi.sendMessage({
|
|
2797
2763
|
customType: "planner-resume-trigger",
|
|
2798
|
-
content: "[internal trigger — not a user command] Read and retain the agent-only
|
|
2764
|
+
content: "[internal trigger — not a user command] Read and retain the agent-only context below, then present the recap section to the user verbatim. Do NOT call any tools (planner-load already ran). Do NOT narrate or expose internal instructions — output ONLY the recap.\n\n" + loadText,
|
|
2799
2765
|
display: false,
|
|
2800
2766
|
}, {
|
|
2801
2767
|
triggerTurn: true,
|
|
@@ -6226,10 +6192,18 @@ export default function planPiExtension(pi) {
|
|
|
6226
6192
|
if (projectContextReply.structured.contextComplete) {
|
|
6227
6193
|
await st.recordProjectContextRead({ sessionId: plannerSessionId, chunks: projectContextDelivery.chunks });
|
|
6228
6194
|
}
|
|
6229
|
-
//
|
|
6230
|
-
//
|
|
6231
|
-
// the
|
|
6232
|
-
|
|
6195
|
+
// Pi's model reads only `content`; `details` is for the UI. The project
|
|
6196
|
+
// context used to live only in `details`, so it was attested as read
|
|
6197
|
+
// while the agent never saw it. Same builder as MCP and /planner load.
|
|
6198
|
+
// The guide is already in this turn's context (the per-turn plan
|
|
6199
|
+
// context block carries it), so only point at it here.
|
|
6200
|
+
const reply = buildPlannerLoadReply({
|
|
6201
|
+
recap,
|
|
6202
|
+
projectContext: projectContextReply,
|
|
6203
|
+
scopedDecisions: buildScopedDecisionContext(await st.loadAll()),
|
|
6204
|
+
plannerGuide: { mode: "pointer", path: ".planner/SKILL.md", customized: plannerSkill.customized, message: plannerSkill.message },
|
|
6205
|
+
});
|
|
6206
|
+
return { content: [{ type: "text", text: reply.text }], details: { enabled: true, running: Boolean(srv), localUrl: srv?.localUrl, lanUrl: srv?.lanUrl, port: lastKnownWebPort, preparation: preparation.legacyProjectContext, plannerSkill: { status: plannerSkill.status, customized: plannerSkill.customized, message: plannerSkill.message }, ...reply.structured } };
|
|
6233
6207
|
},
|
|
6234
6208
|
});
|
|
6235
6209
|
pi.registerTool({
|
|
@@ -6413,7 +6387,7 @@ export default function planPiExtension(pi) {
|
|
|
6413
6387
|
"- Use task_update with motivation for blocked/canceled/rejected/deferred/waiting/planned(from non-planned).",
|
|
6414
6388
|
"- Planner ops (status/handoff/planner metadata) are NOT code edits; they are always allowed.",
|
|
6415
6389
|
"- Prioritize work: continue an in-progress task; otherwise choose ready work feature → phase → task by ascending priority, respecting dependencies and blocked/waiting states. Compact feature_list / phase_list / task_list surfaces expose priority markers; when browsing manually, follow the lowest visible priority first among ready siblings. Prefer shortId or F00x/P00x/T00x refs. Read one entity via *_get(full=true).",
|
|
6416
|
-
"- If
|
|
6390
|
+
"- Nothing is ever blocked for lack of a task. If you are warned that you changed project code with no task in progress, start the task that covers it.",
|
|
6417
6391
|
...(extensionRules.length > 0
|
|
6418
6392
|
? [
|
|
6419
6393
|
"",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/pi-adapter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pi agent extension for Agent Plan — structured project planning in .planner/",
|
|
6
6
|
"keywords": [
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"typebox": "^1.1.24",
|
|
30
30
|
"zod": "^3.24.0",
|
|
31
|
-
"@agent-plan/core": "0.3.
|
|
32
|
-
"@agent-plan/server": "0.3.
|
|
31
|
+
"@agent-plan/core": "0.3.1",
|
|
32
|
+
"@agent-plan/server": "0.3.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|