mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
d26289060d
* fix(pi): preserve extension tool selections * fix(pi): release phase tools when the plan completes The agent_end plan-completion path dropped back to idle via restoreSavedState but never called releaseAddedPhaseTools(), so tools Plannotator activated for the executing phase stayed active in idle and a stale phaseAddedTools list was persisted. Mirror exitToIdle. Claude-Session: https://claude.ai/code/session_01H5KQWqXqjrPxyxUNso1QHS * fix(pi): keep grep/find/ls in the default planning tool set Dropping them from the planning phase profile silently removed native search from default planning sessions. Restore them as default planning phase tools so they now flow through the new tracking mechanism: they are recorded in phaseAddedTools and released on phase exit, and a discovery tool another extension already activated is neither claimed nor removed. A user's own planning.activeTools still overrides them. Claude-Session: https://claude.ai/code/session_01H5KQWqXqjrPxyxUNso1QHS * fix(pi): always keep the submit tool active during planning Planning tools now come purely from the resolved profile's activeTools, which uses replace semantics: a phases.planning.activeTools in ~/.pi/agent/plannotator.json or cwd/.pi/plannotator.json supersedes the built-in list wholesale. Pi's setActiveToolsByName then replaces the session's tool list outright, so such a config silently dropped plannotator_submit_plan while the planning system prompt kept telling the model to call it - a dead end with no way to submit a plan. Union PLAN_SUBMIT_TOOL into the configured list for the planning phase only (deduped if the config already lists it). It flows through phaseAddedTools like any other addition, so it is released normally on phase exit. Other tools are left to the user: overriding planning.activeTools is a deliberate choice of tool set, and only the submit tool is contractually required for the phase to function. Also corrects the README's "extra tools ... additive" wording, which described the old merge behavior. Claude-Session: https://claude.ai/code/session_01H5KQWqXqjrPxyxUNso1QHS --------- Co-authored-by: Michael Ramos <support@plannotator.ai> Co-authored-by: Michael Ramos <mdramos8@gmail.com>
50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
|
|
export type Phase = "idle" | "planning" | "executing";
|
|
|
|
export const PLAN_SUBMIT_TOOL = "plannotator_submit_plan";
|
|
|
|
const ALLOWED_PLAN_EXTENSIONS = new Set<string>([".md", ".mdx"]);
|
|
|
|
export function stripPlanningOnlyTools(tools: readonly string[]): string[] {
|
|
return tools.filter((tool) => tool !== PLAN_SUBMIT_TOOL);
|
|
}
|
|
|
|
export function applyPhaseTools(
|
|
activeTools: readonly string[],
|
|
previouslyAddedTools: readonly string[],
|
|
configuredTools: readonly string[],
|
|
): { activeTools: string[]; addedTools: string[] } {
|
|
const previousAdditions = new Set(previouslyAddedTools);
|
|
const baseTools = activeTools.filter((tool) => !previousAdditions.has(tool));
|
|
const baseToolSet = new Set(baseTools);
|
|
const addedTools = [...new Set(configuredTools)].filter(
|
|
(tool) => !baseToolSet.has(tool),
|
|
);
|
|
|
|
return {
|
|
activeTools: [...baseTools, ...addedTools],
|
|
addedTools,
|
|
};
|
|
}
|
|
|
|
export function releasePhaseTools(
|
|
activeTools: readonly string[],
|
|
addedTools: readonly string[],
|
|
): string[] {
|
|
const additions = new Set(addedTools);
|
|
return activeTools.filter((tool) => !additions.has(tool));
|
|
}
|
|
|
|
// Used by both the planning-phase write gate and plannotator_submit_plan.
|
|
// Path must resolve inside cwd (no traversal, no absolute escape) and end
|
|
// in a permitted markdown extension.
|
|
export function isPlanWritePathAllowed(inputPath: string, cwd: string): boolean {
|
|
if (!inputPath) return false;
|
|
const targetAbs = resolve(cwd, inputPath);
|
|
const rel = relative(resolve(cwd), targetAbs);
|
|
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return false;
|
|
const ext = extname(targetAbs).toLowerCase();
|
|
return ALLOWED_PLAN_EXTENSIONS.has(ext);
|
|
}
|