@janvitos/pi-plan-build 0.1.6 → 0.1.7

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 CHANGED
@@ -21,6 +21,7 @@ A global [Pi coding agent](https://github.com/badlogic/pi-mono) extension that a
21
21
  - **Stay in Plan mode**
22
22
  - Staying in Plan mode produces a durable acknowledgement and stops the run until the user responds.
23
23
  - Mode state survives reloads, resumes, and forks.
24
+ - When Pi recreates the custom editor, the latest 100 user prompts from the active session branch are restored for Up/Down history navigation.
24
25
 
25
26
  ## Requirements
26
27
 
@@ -110,7 +111,7 @@ npm test
110
111
  npm pack --dry-run
111
112
  ```
112
113
 
113
- The tests cover state decoding, safe plan paths, mutation restrictions, deferred transitions, complete plan rendering, approval decisions, stop behavior, fresh-session handoff content, and question formatting.
114
+ The tests cover state decoding, safe plan paths, mutation restrictions, deferred transitions, mode rendering, session-based prompt history restoration, complete plan rendering, approval decisions, stop behavior, fresh-session handoff content, and question formatting.
114
115
 
115
116
  ### Publishing
116
117
 
package/index.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  buildPlanReviewMessage,
20
20
  classifyPlanExitChoice,
21
21
  decodeModeState,
22
+ extractPromptHistory,
22
23
  formatModeStatus,
23
24
  isAllowedPlanMutation,
24
25
  makePlanPath,
@@ -366,7 +367,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
366
367
  updateModeIndicator(ctx);
367
368
  });
368
369
 
369
- pi.on("session_start", async (_event, ctx) => {
370
+ pi.on("session_start", async (event, ctx) => {
370
371
  currentContext = ctx;
371
372
  const entries = ctx.sessionManager.getEntries();
372
373
  const latest = entries
@@ -389,6 +390,8 @@ export default function planBuildModes(pi: ExtensionAPI): void {
389
390
  updateModeIndicator(ctx);
390
391
 
391
392
  if (ctx.mode === "tui") {
393
+ // Startup history is populated after session_start; replacement flows recreate the editor after that step.
394
+ const promptHistory = event.reason === "startup" ? [] : extractPromptHistory(ctx.sessionManager.getBranch());
392
395
  class ModeEditor extends CustomEditor {
393
396
  onCycle?: () => void;
394
397
 
@@ -421,6 +424,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
421
424
  }
422
425
  ctx.ui.setEditorComponent((tui, theme, keybindings) => {
423
426
  const editor = new ModeEditor(tui, theme, keybindings);
427
+ for (const prompt of promptHistory) editor.addToHistory(prompt);
424
428
  requestEditorRender = () => editor.requestModeRender();
425
429
  editor.onCycle = () => {
426
430
  if (currentContext) void selectMode(nextMode(selectedMode), currentContext, "manual");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@janvitos/pi-plan-build",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Plan safely, approve explicitly, then implement here or in a clean session.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/utils.ts CHANGED
@@ -121,6 +121,34 @@ export function formatQuestionAnswers(answers: QuestionAnswerData[]): string {
121
121
  return answers.map((answer) => `"${answer.question}"="${answer.answers.length ? answer.answers.join(", ") : "Unanswered"}"`).join(", ");
122
122
  }
123
123
 
124
+ export function extractPromptHistory(entries: readonly unknown[], limit = 100): string[] {
125
+ const prompts: string[] = [];
126
+ for (const entry of entries) {
127
+ if (!entry || typeof entry !== "object") continue;
128
+ const candidate = entry as {
129
+ type?: unknown;
130
+ message?: { role?: unknown; content?: unknown };
131
+ };
132
+ if (candidate.type !== "message" || candidate.message?.role !== "user") continue;
133
+
134
+ const content = candidate.message.content;
135
+ const text = typeof content === "string"
136
+ ? content
137
+ : Array.isArray(content)
138
+ ? content
139
+ .filter((block): block is { type: "text"; text: string } =>
140
+ !!block && typeof block === "object" && (block as { type?: unknown }).type === "text" && typeof (block as { text?: unknown }).text === "string")
141
+ .map((block) => block.text)
142
+ .join("")
143
+ : "";
144
+ const trimmed = text.trim();
145
+ if (!trimmed || prompts.at(-1) === trimmed) continue;
146
+ prompts.push(trimmed);
147
+ }
148
+ const maxEntries = Math.max(0, Math.floor(limit));
149
+ return maxEntries === 0 ? [] : prompts.slice(-maxEntries);
150
+ }
151
+
124
152
  export function nextMode(mode: Mode): Mode {
125
153
  return mode === "build" ? "plan" : "build";
126
154
  }