@mariozechner/pi-coding-agent 0.46.0 → 0.48.0

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 (144) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +34 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +10 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +16 -3
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +125 -22
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  11. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  12. package/dist/core/compaction/branch-summarization.js +11 -4
  13. package/dist/core/compaction/branch-summarization.js.map +1 -1
  14. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  15. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  16. package/dist/core/export-html/ansi-to-html.js +249 -0
  17. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  18. package/dist/core/export-html/index.d.ts +17 -0
  19. package/dist/core/export-html/index.d.ts.map +1 -1
  20. package/dist/core/export-html/index.js +52 -23
  21. package/dist/core/export-html/index.js.map +1 -1
  22. package/dist/core/export-html/template.css +0 -33
  23. package/dist/core/export-html/template.js +171 -18
  24. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  25. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  26. package/dist/core/export-html/tool-renderer.js +57 -0
  27. package/dist/core/export-html/tool-renderer.js.map +1 -0
  28. package/dist/core/extensions/index.d.ts +1 -1
  29. package/dist/core/extensions/index.d.ts.map +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/loader.d.ts.map +1 -1
  32. package/dist/core/extensions/loader.js.map +1 -1
  33. package/dist/core/extensions/runner.d.ts +8 -1
  34. package/dist/core/extensions/runner.d.ts.map +1 -1
  35. package/dist/core/extensions/runner.js +41 -0
  36. package/dist/core/extensions/runner.js.map +1 -1
  37. package/dist/core/extensions/types.d.ts +45 -6
  38. package/dist/core/extensions/types.d.ts.map +1 -1
  39. package/dist/core/extensions/types.js.map +1 -1
  40. package/dist/core/prompt-templates.d.ts +5 -1
  41. package/dist/core/prompt-templates.d.ts.map +1 -1
  42. package/dist/core/prompt-templates.js +22 -28
  43. package/dist/core/prompt-templates.js.map +1 -1
  44. package/dist/core/sdk.d.ts.map +1 -1
  45. package/dist/core/sdk.js +5 -1
  46. package/dist/core/sdk.js.map +1 -1
  47. package/dist/core/session-manager.d.ts +8 -0
  48. package/dist/core/session-manager.d.ts.map +1 -1
  49. package/dist/core/session-manager.js +43 -0
  50. package/dist/core/session-manager.js.map +1 -1
  51. package/dist/core/settings-manager.d.ts +9 -0
  52. package/dist/core/settings-manager.d.ts.map +1 -1
  53. package/dist/core/settings-manager.js +21 -0
  54. package/dist/core/settings-manager.js.map +1 -1
  55. package/dist/core/skills.d.ts.map +1 -1
  56. package/dist/core/skills.js +6 -37
  57. package/dist/core/skills.js.map +1 -1
  58. package/dist/core/system-prompt.d.ts.map +1 -1
  59. package/dist/core/system-prompt.js +19 -14
  60. package/dist/core/system-prompt.js.map +1 -1
  61. package/dist/core/tools/bash.d.ts +2 -0
  62. package/dist/core/tools/bash.d.ts.map +1 -1
  63. package/dist/core/tools/bash.js +4 -1
  64. package/dist/core/tools/bash.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +4 -1
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/index.js +8 -3
  68. package/dist/core/tools/index.js.map +1 -1
  69. package/dist/core/tools/path-utils.d.ts +1 -0
  70. package/dist/core/tools/path-utils.d.ts.map +1 -1
  71. package/dist/core/tools/path-utils.js +7 -0
  72. package/dist/core/tools/path-utils.js.map +1 -1
  73. package/dist/core/tools/read.d.ts.map +1 -1
  74. package/dist/core/tools/read.js +13 -2
  75. package/dist/core/tools/read.js.map +1 -1
  76. package/dist/index.d.ts +3 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/main.d.ts.map +1 -1
  81. package/dist/main.js +84 -14
  82. package/dist/main.js.map +1 -1
  83. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  84. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/custom-editor.js +2 -2
  86. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  87. package/dist/modes/interactive/components/extension-editor.d.ts +2 -2
  88. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/extension-editor.js +3 -3
  90. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  91. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  92. package/dist/modes/interactive/components/session-selector.js +5 -3
  93. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  94. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  95. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/settings-selector.js +12 -0
  97. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  98. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  99. package/dist/modes/interactive/components/tool-execution.js +3 -1
  100. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  101. package/dist/modes/interactive/components/tree-selector.d.ts +7 -0
  102. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/tree-selector.js +140 -4
  104. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  105. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  106. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/dist/modes/interactive/interactive-mode.js +123 -106
  108. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/dist/modes/interactive/theme/theme-schema.json +23 -3
  110. package/dist/modes/print-mode.d.ts.map +1 -1
  111. package/dist/modes/print-mode.js +7 -2
  112. package/dist/modes/print-mode.js.map +1 -1
  113. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  114. package/dist/modes/rpc/rpc-mode.js +7 -1
  115. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  116. package/dist/utils/frontmatter.d.ts +8 -0
  117. package/dist/utils/frontmatter.d.ts.map +1 -0
  118. package/dist/utils/frontmatter.js +26 -0
  119. package/dist/utils/frontmatter.js.map +1 -0
  120. package/dist/utils/image-convert.d.ts.map +1 -1
  121. package/dist/utils/image-convert.js +6 -1
  122. package/dist/utils/image-convert.js.map +1 -1
  123. package/dist/utils/image-resize.d.ts.map +1 -1
  124. package/dist/utils/image-resize.js +14 -1
  125. package/dist/utils/image-resize.js.map +1 -1
  126. package/dist/utils/photon.d.ts +28 -0
  127. package/dist/utils/photon.d.ts.map +1 -0
  128. package/dist/utils/photon.js +51 -0
  129. package/dist/utils/photon.js.map +1 -0
  130. package/docs/extensions.md +83 -4
  131. package/docs/rpc.md +17 -15
  132. package/docs/sdk.md +1 -1
  133. package/docs/tree.md +20 -2
  134. package/docs/tui.md +26 -0
  135. package/examples/extensions/input-transform.ts +43 -0
  136. package/examples/extensions/modal-editor.ts +1 -1
  137. package/examples/extensions/overlay-test.ts +8 -3
  138. package/examples/extensions/question.ts +1 -1
  139. package/examples/extensions/questionnaire.ts +1 -1
  140. package/examples/extensions/rainbow-editor.ts +1 -8
  141. package/examples/extensions/subagent/agents.ts +3 -32
  142. package/examples/extensions/with-deps/package-lock.json +2 -2
  143. package/examples/extensions/with-deps/package.json +1 -1
  144. package/package.json +6 -5
@@ -12,11 +12,15 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
+ import { readFileSync } from "node:fs";
15
16
  import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
16
17
  import { getAuthPath } from "../config.js";
18
+ import { theme } from "../modes/interactive/theme/theme.js";
19
+ import { stripFrontmatter } from "../utils/frontmatter.js";
17
20
  import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
18
21
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
19
22
  import { exportSessionToHtml } from "./export-html/index.js";
23
+ import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
20
24
  import { expandPromptTemplate } from "./prompt-templates.js";
21
25
  // ============================================================================
22
26
  // Constants
@@ -394,8 +398,25 @@ export class AgentSession {
394
398
  return;
395
399
  }
396
400
  }
397
- // Expand file-based prompt templates if requested
398
- const expandedText = expandPromptTemplates ? expandPromptTemplate(text, [...this._promptTemplates]) : text;
401
+ // Emit input event for extension interception (before skill/template expansion)
402
+ let currentText = text;
403
+ let currentImages = options?.images;
404
+ if (this._extensionRunner?.hasHandlers("input")) {
405
+ const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive");
406
+ if (inputResult.action === "handled") {
407
+ return;
408
+ }
409
+ if (inputResult.action === "transform") {
410
+ currentText = inputResult.text;
411
+ currentImages = inputResult.images ?? currentImages;
412
+ }
413
+ }
414
+ // Expand skill commands (/skill:name args) and prompt templates (/template args)
415
+ let expandedText = currentText;
416
+ if (expandPromptTemplates) {
417
+ expandedText = this._expandSkillCommand(expandedText);
418
+ expandedText = expandPromptTemplate(expandedText, [...this._promptTemplates]);
419
+ }
399
420
  // If streaming, queue via steer() or followUp() based on option
400
421
  if (this.isStreaming) {
401
422
  if (!options?.streamingBehavior) {
@@ -432,8 +453,8 @@ export class AgentSession {
432
453
  const messages = [];
433
454
  // Add user message
434
455
  const userContent = [{ type: "text", text: expandedText }];
435
- if (options?.images) {
436
- userContent.push(...options.images);
456
+ if (currentImages) {
457
+ userContent.push(...currentImages);
437
458
  }
438
459
  messages.push({
439
460
  role: "user",
@@ -447,7 +468,7 @@ export class AgentSession {
447
468
  this._pendingNextTurnMessages = [];
448
469
  // Emit before_agent_start extension event
449
470
  if (this._extensionRunner) {
450
- const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, options?.images, this._baseSystemPrompt);
471
+ const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt);
451
472
  // Add all custom messages from extensions
452
473
  if (result?.messages) {
453
474
  for (const msg of result.messages) {
@@ -502,10 +523,41 @@ export class AgentSession {
502
523
  return true;
503
524
  }
504
525
  }
526
+ /**
527
+ * Expand skill commands (/skill:name args) to their full content.
528
+ * Returns the expanded text, or the original text if not a skill command or skill not found.
529
+ * Emits errors via extension runner if file read fails.
530
+ */
531
+ _expandSkillCommand(text) {
532
+ if (!text.startsWith("/skill:"))
533
+ return text;
534
+ const spaceIndex = text.indexOf(" ");
535
+ const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
536
+ const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
537
+ const skill = this._skills.find((s) => s.name === skillName);
538
+ if (!skill)
539
+ return text; // Unknown skill, pass through
540
+ try {
541
+ const content = readFileSync(skill.filePath, "utf-8");
542
+ const body = stripFrontmatter(content).trim();
543
+ const header = `Skill location: ${skill.filePath}\nReferences are relative to ${skill.baseDir}.`;
544
+ const skillMessage = `${header}\n\n${body}`;
545
+ return args ? `${skillMessage}\n\n---\n\nUser: ${args}` : skillMessage;
546
+ }
547
+ catch (err) {
548
+ // Emit error like extension commands do
549
+ this._extensionRunner?.emitError({
550
+ extensionPath: skill.filePath,
551
+ event: "skill_expansion",
552
+ error: err instanceof Error ? err.message : String(err),
553
+ });
554
+ return text; // Return original on error
555
+ }
556
+ }
505
557
  /**
506
558
  * Queue a steering message to interrupt the agent mid-run.
507
559
  * Delivered after current tool execution, skips remaining tools.
508
- * Expands file-based prompt templates. Errors on extension commands.
560
+ * Expands skill commands and prompt templates. Errors on extension commands.
509
561
  * @throws Error if text is an extension command
510
562
  */
511
563
  async steer(text) {
@@ -513,14 +565,15 @@ export class AgentSession {
513
565
  if (text.startsWith("/")) {
514
566
  this._throwIfExtensionCommand(text);
515
567
  }
516
- // Expand file-based prompt templates
517
- const expandedText = expandPromptTemplate(text, [...this._promptTemplates]);
568
+ // Expand skill commands and prompt templates
569
+ let expandedText = this._expandSkillCommand(text);
570
+ expandedText = expandPromptTemplate(expandedText, [...this._promptTemplates]);
518
571
  await this._queueSteer(expandedText);
519
572
  }
520
573
  /**
521
574
  * Queue a follow-up message to be processed after the agent finishes.
522
575
  * Delivered only when agent has no more tool calls or steering messages.
523
- * Expands file-based prompt templates. Errors on extension commands.
576
+ * Expands skill commands and prompt templates. Errors on extension commands.
524
577
  * @throws Error if text is an extension command
525
578
  */
526
579
  async followUp(text) {
@@ -528,8 +581,9 @@ export class AgentSession {
528
581
  if (text.startsWith("/")) {
529
582
  this._throwIfExtensionCommand(text);
530
583
  }
531
- // Expand file-based prompt templates
532
- const expandedText = expandPromptTemplate(text, [...this._promptTemplates]);
584
+ // Expand skill commands and prompt templates
585
+ let expandedText = this._expandSkillCommand(text);
586
+ expandedText = expandPromptTemplate(expandedText, [...this._promptTemplates]);
533
587
  await this._queueFollowUp(expandedText);
534
588
  }
535
589
  /**
@@ -641,6 +695,7 @@ export class AgentSession {
641
695
  expandPromptTemplates: false,
642
696
  streamingBehavior: options?.deliverAs,
643
697
  images,
698
+ source: "extension",
644
699
  });
645
700
  }
646
701
  /**
@@ -1169,10 +1224,16 @@ export class AgentSession {
1169
1224
  }
1170
1225
  }
1171
1226
  catch (error) {
1172
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1173
- if (reason === "overflow") {
1174
- throw new Error(`Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`);
1175
- }
1227
+ const errorMessage = error instanceof Error ? error.message : "compaction failed";
1228
+ this._emit({
1229
+ type: "auto_compaction_end",
1230
+ result: undefined,
1231
+ aborted: false,
1232
+ willRetry: false,
1233
+ errorMessage: reason === "overflow"
1234
+ ? `Context overflow recovery failed: ${errorMessage}`
1235
+ : `Auto-compaction failed: ${errorMessage}`,
1236
+ });
1176
1237
  }
1177
1238
  finally {
1178
1239
  this._autoCompactionAbortController = undefined;
@@ -1203,8 +1264,8 @@ export class AgentSession {
1203
1264
  if (isContextOverflow(message, contextWindow))
1204
1265
  return false;
1205
1266
  const err = message.errorMessage;
1206
- // Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection error, other side closed, fetch failed
1207
- return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|other side closed|fetch failed/i.test(err);
1267
+ // Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection errors, fetch failed
1268
+ return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers/i.test(err);
1208
1269
  }
1209
1270
  /**
1210
1271
  * Handle retryable errors with exponential backoff.
@@ -1334,13 +1395,16 @@ export class AgentSession {
1334
1395
  */
1335
1396
  async executeBash(command, onChunk, options) {
1336
1397
  this._bashAbortController = new AbortController();
1398
+ // Apply command prefix if configured (e.g., "shopt -s expand_aliases" for alias support)
1399
+ const prefix = this.settingsManager.getShellCommandPrefix();
1400
+ const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
1337
1401
  try {
1338
1402
  const result = options?.operations
1339
- ? await executeBashWithOperations(command, process.cwd(), options.operations, {
1403
+ ? await executeBashWithOperations(resolvedCommand, process.cwd(), options.operations, {
1340
1404
  onChunk,
1341
1405
  signal: this._bashAbortController.signal,
1342
1406
  })
1343
- : await executeBashCommand(command, {
1407
+ : await executeBashCommand(resolvedCommand, {
1344
1408
  onChunk,
1345
1409
  signal: this._bashAbortController.signal,
1346
1410
  });
@@ -1529,6 +1593,8 @@ export class AgentSession {
1529
1593
  * @param targetId The entry ID to navigate to
1530
1594
  * @param options.summarize Whether user wants to summarize abandoned branch
1531
1595
  * @param options.customInstructions Custom instructions for summarizer
1596
+ * @param options.replaceInstructions If true, customInstructions replaces the default prompt
1597
+ * @param options.label Label to attach to the branch summary entry
1532
1598
  * @returns Result with editorText (if user message) and cancelled status
1533
1599
  */
1534
1600
  async navigateTree(targetId, options = {}) {
@@ -1547,13 +1613,19 @@ export class AgentSession {
1547
1613
  }
1548
1614
  // Collect entries to summarize (from old leaf to common ancestor)
1549
1615
  const { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(this.sessionManager, oldLeafId, targetId);
1550
- // Prepare event data
1616
+ // Prepare event data - mutable so extensions can override
1617
+ let customInstructions = options.customInstructions;
1618
+ let replaceInstructions = options.replaceInstructions;
1619
+ let label = options.label;
1551
1620
  const preparation = {
1552
1621
  targetId,
1553
1622
  oldLeafId,
1554
1623
  commonAncestorId,
1555
1624
  entriesToSummarize,
1556
1625
  userWantsSummary: options.summarize ?? false,
1626
+ customInstructions,
1627
+ replaceInstructions,
1628
+ label,
1557
1629
  };
1558
1630
  // Set up abort controller for summarization
1559
1631
  this._branchSummaryAbortController = new AbortController();
@@ -1573,6 +1645,16 @@ export class AgentSession {
1573
1645
  extensionSummary = result.summary;
1574
1646
  fromExtension = true;
1575
1647
  }
1648
+ // Allow extensions to override instructions and label
1649
+ if (result?.customInstructions !== undefined) {
1650
+ customInstructions = result.customInstructions;
1651
+ }
1652
+ if (result?.replaceInstructions !== undefined) {
1653
+ replaceInstructions = result.replaceInstructions;
1654
+ }
1655
+ if (result?.label !== undefined) {
1656
+ label = result.label;
1657
+ }
1576
1658
  }
1577
1659
  // Run default summarizer if needed
1578
1660
  let summaryText;
@@ -1588,7 +1670,8 @@ export class AgentSession {
1588
1670
  model,
1589
1671
  apiKey,
1590
1672
  signal: this._branchSummaryAbortController.signal,
1591
- customInstructions: options.customInstructions,
1673
+ customInstructions,
1674
+ replaceInstructions,
1592
1675
  reserveTokens: branchSummarySettings.reserveTokens,
1593
1676
  });
1594
1677
  this._branchSummaryAbortController = undefined;
@@ -1638,6 +1721,10 @@ export class AgentSession {
1638
1721
  // Create summary at target position (can be null for root)
1639
1722
  const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
1640
1723
  summaryEntry = this.sessionManager.getEntry(summaryId);
1724
+ // Attach label to the summary entry
1725
+ if (label) {
1726
+ this.sessionManager.appendLabelChange(summaryId, label);
1727
+ }
1641
1728
  }
1642
1729
  else if (newLeafId === null) {
1643
1730
  // No summary, navigating to root - reset leaf
@@ -1647,6 +1734,10 @@ export class AgentSession {
1647
1734
  // No summary, navigating to non-root
1648
1735
  this.sessionManager.branch(newLeafId);
1649
1736
  }
1737
+ // Attach label to target entry when not summarizing (no summary entry to label)
1738
+ if (label && !summaryText) {
1739
+ this.sessionManager.appendLabelChange(targetId, label);
1740
+ }
1650
1741
  // Update agent state
1651
1742
  const sessionContext = this.sessionManager.buildSessionContext();
1652
1743
  this.agent.replaceMessages(sessionContext.messages);
@@ -1743,7 +1834,19 @@ export class AgentSession {
1743
1834
  */
1744
1835
  async exportToHtml(outputPath) {
1745
1836
  const themeName = this.settingsManager.getTheme();
1746
- return await exportSessionToHtml(this.sessionManager, this.state, { outputPath, themeName });
1837
+ // Create tool renderer if we have an extension runner (for custom tool HTML rendering)
1838
+ let toolRenderer;
1839
+ if (this._extensionRunner) {
1840
+ toolRenderer = createToolHtmlRenderer({
1841
+ getToolDefinition: (name) => this._extensionRunner.getToolDefinition(name),
1842
+ theme,
1843
+ });
1844
+ }
1845
+ return await exportSessionToHtml(this.sessionManager, this.state, {
1846
+ outputPath,
1847
+ themeName,
1848
+ toolRenderer,
1849
+ });
1747
1850
  }
1748
1851
  // =========================================================================
1749
1852
  // Utilities