@earendil-works/pi-coding-agent 0.79.9 → 0.79.10

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +2 -1
  3. package/dist/config.d.ts +6 -2
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +21 -13
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +3 -1
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +10 -1
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/extensions/types.d.ts +8 -0
  12. package/dist/core/extensions/types.d.ts.map +1 -1
  13. package/dist/core/extensions/types.js.map +1 -1
  14. package/dist/core/tools/find.d.ts.map +1 -1
  15. package/dist/core/tools/find.js +19 -11
  16. package/dist/core/tools/find.js.map +1 -1
  17. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  18. package/dist/modes/interactive/interactive-mode.js +30 -24
  19. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  20. package/dist/package-manager-cli.d.ts.map +1 -1
  21. package/dist/package-manager-cli.js +29 -16
  22. package/dist/package-manager-cli.js.map +1 -1
  23. package/docs/compaction.md +3 -1
  24. package/docs/extensions.md +6 -1
  25. package/docs/tui.md +3 -3
  26. package/docs/usage.md +3 -1
  27. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  28. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  29. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  30. package/examples/extensions/gondolin/package-lock.json +2 -2
  31. package/examples/extensions/gondolin/package.json +1 -1
  32. package/examples/extensions/plan-mode/README.md +3 -2
  33. package/examples/extensions/plan-mode/index.ts +87 -37
  34. package/examples/extensions/sandbox/package-lock.json +2 -2
  35. package/examples/extensions/sandbox/package.json +1 -1
  36. package/examples/extensions/with-deps/package-lock.json +2 -2
  37. package/examples/extensions/with-deps/package.json +1 -1
  38. package/npm-shrinkwrap.json +12 -12
  39. package/package.json +4 -4
@@ -2,7 +2,7 @@
2
2
  * Plan Mode Extension
3
3
  *
4
4
  * Read-only exploration mode for safe code analysis.
5
- * When enabled, only read-only tools are available.
5
+ * When enabled, built-in write tools are disabled.
6
6
  *
7
7
  * Features:
8
8
  * - /plan command or Ctrl+Alt+P to toggle
@@ -21,6 +21,15 @@ import { extractTodoItems, isSafeCommand, markCompletedSteps, type TodoItem } fr
21
21
  // Tools
22
22
  const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls", "questionnaire"];
23
23
  const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
24
+ const PLAN_MODE_DISABLED_TOOLS = new Set<string>(["edit", "write"]);
25
+ const PLAN_MANAGED_TOOLS = new Set<string>([...PLAN_MODE_TOOLS, ...NORMAL_MODE_TOOLS]);
26
+
27
+ interface PlanModeState {
28
+ enabled: boolean;
29
+ todos?: TodoItem[];
30
+ executing?: boolean;
31
+ toolsBeforePlanMode?: string[];
32
+ }
24
33
 
25
34
  // Type guard for assistant messages
26
35
  function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
@@ -39,6 +48,7 @@ export default function planModeExtension(pi: ExtensionAPI): void {
39
48
  let planModeEnabled = false;
40
49
  let executionMode = false;
41
50
  let todoItems: TodoItem[] = [];
51
+ let toolsBeforePlanMode: string[] | undefined;
42
52
 
43
53
  pi.registerFlag("plan", {
44
54
  description: "Start in plan mode (read-only exploration)",
@@ -73,19 +83,34 @@ export default function planModeExtension(pi: ExtensionAPI): void {
73
83
  }
74
84
  }
75
85
 
76
- function togglePlanMode(ctx: ExtensionContext): void {
77
- planModeEnabled = !planModeEnabled;
78
- executionMode = false;
79
- todoItems = [];
86
+ function uniqueToolNames(toolNames: string[]): string[] {
87
+ return [...new Set(toolNames)];
88
+ }
80
89
 
81
- if (planModeEnabled) {
82
- pi.setActiveTools(PLAN_MODE_TOOLS);
83
- ctx.ui.notify(`Plan mode enabled. Tools: ${PLAN_MODE_TOOLS.join(", ")}`);
84
- } else {
85
- pi.setActiveTools(NORMAL_MODE_TOOLS);
86
- ctx.ui.notify("Plan mode disabled. Full access restored.");
90
+ function getPlanModeTools(activeToolNames: string[]): string[] {
91
+ return uniqueToolNames([
92
+ ...activeToolNames.filter((name) => !PLAN_MODE_DISABLED_TOOLS.has(name)),
93
+ ...PLAN_MODE_TOOLS,
94
+ ]);
95
+ }
96
+
97
+ function getNormalModeTools(activeToolNames: string[]): string[] {
98
+ return uniqueToolNames([
99
+ ...NORMAL_MODE_TOOLS,
100
+ ...activeToolNames.filter((name) => !PLAN_MANAGED_TOOLS.has(name)),
101
+ ]);
102
+ }
103
+
104
+ function enablePlanModeTools(): void {
105
+ if (toolsBeforePlanMode === undefined) {
106
+ toolsBeforePlanMode = pi.getActiveTools();
87
107
  }
88
- updateStatus(ctx);
108
+ pi.setActiveTools(getPlanModeTools(toolsBeforePlanMode));
109
+ }
110
+
111
+ function restoreNormalModeTools(): void {
112
+ pi.setActiveTools(toolsBeforePlanMode ?? getNormalModeTools(pi.getActiveTools()));
113
+ toolsBeforePlanMode = undefined;
89
114
  }
90
115
 
91
116
  function persistState(): void {
@@ -93,9 +118,26 @@ export default function planModeExtension(pi: ExtensionAPI): void {
93
118
  enabled: planModeEnabled,
94
119
  todos: todoItems,
95
120
  executing: executionMode,
121
+ toolsBeforePlanMode,
96
122
  });
97
123
  }
98
124
 
125
+ function togglePlanMode(ctx: ExtensionContext): void {
126
+ planModeEnabled = !planModeEnabled;
127
+ executionMode = false;
128
+ todoItems = [];
129
+
130
+ if (planModeEnabled) {
131
+ enablePlanModeTools();
132
+ ctx.ui.notify("Plan mode enabled. Built-in write tools disabled.");
133
+ } else {
134
+ restoreNormalModeTools();
135
+ ctx.ui.notify("Plan mode disabled. Full access restored.");
136
+ }
137
+ updateStatus(ctx);
138
+ persistState();
139
+ }
140
+
99
141
  pi.registerCommand("plan", {
100
142
  description: "Toggle plan mode (read-only exploration)",
101
143
  handler: async (_args, ctx) => togglePlanMode(ctx),
@@ -165,8 +207,8 @@ export default function planModeExtension(pi: ExtensionAPI): void {
165
207
  You are in plan mode - a read-only exploration mode for safe code analysis.
166
208
 
167
209
  Restrictions:
168
- - You can only use: read, bash, grep, find, ls, questionnaire
169
- - You CANNOT use: edit, write (file modifications are disabled)
210
+ - Built-in edit and write tools are disabled
211
+ - Other currently active tools remain available
170
212
  - Bash is restricted to an allowlist of read-only commands
171
213
 
172
214
  Ask clarifying questions using the questionnaire tool.
@@ -228,7 +270,6 @@ After completing a step, include a [DONE:n] tag in your response.`,
228
270
  );
229
271
  executionMode = false;
230
272
  todoItems = [];
231
- pi.setActiveTools(NORMAL_MODE_TOOLS);
232
273
  updateStatus(ctx);
233
274
  persistState(); // Save cleared state so resume doesn't restore old execution mode
234
275
  }
@@ -246,43 +287,51 @@ After completing a step, include a [DONE:n] tag in your response.`,
246
287
  }
247
288
  }
248
289
 
290
+ if (todoItems.length === 0) return;
291
+ persistState();
292
+
249
293
  // Show plan steps and prompt for next action
250
- if (todoItems.length > 0) {
251
- const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
252
- pi.sendMessage(
253
- {
254
- customType: "plan-todo-list",
255
- content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
256
- display: true,
257
- },
258
- { triggerTurn: false },
259
- );
260
- }
294
+ const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
295
+ const planTodoListMessage = {
296
+ customType: "plan-todo-list",
297
+ content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
298
+ display: true,
299
+ };
261
300
 
262
301
  const choice = await ctx.ui.select("Plan mode - what next?", [
263
- todoItems.length > 0 ? "Execute the plan (track progress)" : "Execute the plan",
302
+ "Execute the plan (track progress)",
264
303
  "Stay in plan mode",
265
304
  "Refine the plan",
266
305
  ]);
267
306
 
268
307
  if (choice?.startsWith("Execute")) {
308
+ const firstTodoItem = todoItems[0];
309
+ if (!firstTodoItem) return;
310
+
269
311
  planModeEnabled = false;
270
- executionMode = todoItems.length > 0;
271
- pi.setActiveTools(NORMAL_MODE_TOOLS);
312
+ executionMode = true;
313
+ restoreNormalModeTools();
272
314
  updateStatus(ctx);
315
+ persistState();
316
+
317
+ const remainingList = todoItems.map((t) => `${t.step}. ${t.text}`).join("\n");
318
+ const execMessage = `Execute the plan.
319
+
320
+ Remaining steps:
321
+ ${remainingList}
273
322
 
274
- const execMessage =
275
- todoItems.length > 0
276
- ? `Execute the plan. Start with: ${todoItems[0].text}`
277
- : "Execute the plan you just created.";
323
+ Start with: ${firstTodoItem.text}
324
+ After completing a step, include a [DONE:n] tag in your response.`;
325
+ pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
278
326
  pi.sendMessage(
279
327
  { customType: "plan-mode-execute", content: execMessage, display: true },
280
- { triggerTurn: true },
328
+ { triggerTurn: true, deliverAs: "followUp" },
281
329
  );
282
330
  } else if (choice === "Refine the plan") {
283
331
  const refinement = await ctx.ui.editor("Refine the plan:", "");
284
332
  if (refinement?.trim()) {
285
- pi.sendUserMessage(refinement.trim());
333
+ pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
334
+ pi.sendUserMessage(refinement.trim(), { deliverAs: "followUp" });
286
335
  }
287
336
  }
288
337
  });
@@ -298,12 +347,13 @@ After completing a step, include a [DONE:n] tag in your response.`,
298
347
  // Restore persisted state
299
348
  const planModeEntry = entries
300
349
  .filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "plan-mode")
301
- .pop() as { data?: { enabled: boolean; todos?: TodoItem[]; executing?: boolean } } | undefined;
350
+ .pop() as { data?: PlanModeState } | undefined;
302
351
 
303
352
  if (planModeEntry?.data) {
304
353
  planModeEnabled = planModeEntry.data.enabled ?? planModeEnabled;
305
354
  todoItems = planModeEntry.data.todos ?? todoItems;
306
355
  executionMode = planModeEntry.data.executing ?? executionMode;
356
+ toolsBeforePlanMode = planModeEntry.data.toolsBeforePlanMode ?? toolsBeforePlanMode;
307
357
  }
308
358
 
309
359
  // On resume: re-scan messages to rebuild completion state
@@ -333,7 +383,7 @@ After completing a step, include a [DONE:n] tag in your response.`,
333
383
  }
334
384
 
335
385
  if (planModeEnabled) {
336
- pi.setActiveTools(PLAN_MODE_TOOLS);
386
+ enablePlanModeTools();
337
387
  }
338
388
  updateStatus(ctx);
339
389
  });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.9.9",
3
+ "version": "1.9.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.9.9",
9
+ "version": "1.9.10",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.9.9",
4
+ "version": "1.9.10",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.79.9",
3
+ "version": "0.79.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.79.9",
9
+ "version": "0.79.10",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.79.9",
4
+ "version": "0.79.10",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-coding-agent",
3
- "version": "0.79.9",
3
+ "version": "0.79.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@earendil-works/pi-coding-agent",
9
- "version": "0.79.9",
9
+ "version": "0.79.10",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@earendil-works/pi-agent-core": "^0.79.9",
13
- "@earendil-works/pi-ai": "^0.79.9",
14
- "@earendil-works/pi-tui": "^0.79.9",
12
+ "@earendil-works/pi-agent-core": "^0.79.10",
13
+ "@earendil-works/pi-ai": "^0.79.10",
14
+ "@earendil-works/pi-tui": "^0.79.10",
15
15
  "@silvia-odwyer/photon-node": "0.3.4",
16
16
  "chalk": "5.6.2",
17
17
  "cross-spawn": "7.0.6",
@@ -474,11 +474,11 @@
474
474
  }
475
475
  },
476
476
  "node_modules/@earendil-works/pi-agent-core": {
477
- "version": "0.79.9",
478
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.79.9.tgz",
477
+ "version": "0.79.10",
478
+ "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.79.10.tgz",
479
479
  "license": "MIT",
480
480
  "dependencies": {
481
- "@earendil-works/pi-ai": "^0.79.9",
481
+ "@earendil-works/pi-ai": "^0.79.10",
482
482
  "ignore": "7.0.5",
483
483
  "typebox": "1.1.38",
484
484
  "yaml": "2.9.0"
@@ -488,8 +488,8 @@
488
488
  }
489
489
  },
490
490
  "node_modules/@earendil-works/pi-ai": {
491
- "version": "0.79.9",
492
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.79.9.tgz",
491
+ "version": "0.79.10",
492
+ "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.79.10.tgz",
493
493
  "license": "MIT",
494
494
  "dependencies": {
495
495
  "@anthropic-ai/sdk": "0.91.1",
@@ -512,8 +512,8 @@
512
512
  }
513
513
  },
514
514
  "node_modules/@earendil-works/pi-tui": {
515
- "version": "0.79.9",
516
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.79.9.tgz",
515
+ "version": "0.79.10",
516
+ "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.79.10.tgz",
517
517
  "license": "MIT",
518
518
  "dependencies": {
519
519
  "get-east-asian-width": "1.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-coding-agent",
3
- "version": "0.79.9",
3
+ "version": "0.79.10",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -36,9 +36,9 @@
36
36
  "prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
37
37
  },
38
38
  "dependencies": {
39
- "@earendil-works/pi-agent-core": "^0.79.9",
40
- "@earendil-works/pi-ai": "^0.79.9",
41
- "@earendil-works/pi-tui": "^0.79.9",
39
+ "@earendil-works/pi-agent-core": "^0.79.10",
40
+ "@earendil-works/pi-ai": "^0.79.10",
41
+ "@earendil-works/pi-tui": "^0.79.10",
42
42
  "@silvia-odwyer/photon-node": "0.3.4",
43
43
  "chalk": "5.6.2",
44
44
  "cross-spawn": "7.0.6",