@mariozechner/pi-coding-agent 0.31.1 → 0.32.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +56 -5
  3. package/dist/cli/file-processor.d.ts +5 -1
  4. package/dist/cli/file-processor.d.ts.map +1 -1
  5. package/dist/cli/file-processor.js +28 -8
  6. package/dist/cli/file-processor.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +45 -17
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +93 -42
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts +6 -1
  12. package/dist/core/auth-storage.d.ts.map +1 -1
  13. package/dist/core/auth-storage.js +16 -1
  14. package/dist/core/auth-storage.js.map +1 -1
  15. package/dist/core/custom-tools/types.d.ts +1 -1
  16. package/dist/core/custom-tools/types.d.ts.map +1 -1
  17. package/dist/core/custom-tools/types.js.map +1 -1
  18. package/dist/core/hooks/loader.d.ts +4 -1
  19. package/dist/core/hooks/loader.d.ts.map +1 -1
  20. package/dist/core/hooks/loader.js +2 -2
  21. package/dist/core/hooks/loader.js.map +1 -1
  22. package/dist/core/hooks/runner.d.ts +2 -2
  23. package/dist/core/hooks/runner.d.ts.map +1 -1
  24. package/dist/core/hooks/runner.js +3 -3
  25. package/dist/core/hooks/runner.js.map +1 -1
  26. package/dist/core/hooks/types.d.ts +10 -4
  27. package/dist/core/hooks/types.d.ts.map +1 -1
  28. package/dist/core/hooks/types.js.map +1 -1
  29. package/dist/core/messages.d.ts +2 -0
  30. package/dist/core/messages.d.ts.map +1 -1
  31. package/dist/core/messages.js +4 -0
  32. package/dist/core/messages.js.map +1 -1
  33. package/dist/core/model-registry.d.ts +5 -2
  34. package/dist/core/model-registry.d.ts.map +1 -1
  35. package/dist/core/model-registry.js +85 -49
  36. package/dist/core/model-registry.js.map +1 -1
  37. package/dist/core/model-resolver.d.ts.map +1 -1
  38. package/dist/core/model-resolver.js +1 -0
  39. package/dist/core/model-resolver.js.map +1 -1
  40. package/dist/core/sdk.d.ts.map +1 -1
  41. package/dist/core/sdk.js +9 -6
  42. package/dist/core/sdk.js.map +1 -1
  43. package/dist/core/settings-manager.d.ts +17 -3
  44. package/dist/core/settings-manager.d.ts.map +1 -1
  45. package/dist/core/settings-manager.js +41 -6
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/core/tools/index.d.ts +9 -4
  48. package/dist/core/tools/index.d.ts.map +1 -1
  49. package/dist/core/tools/index.js +6 -6
  50. package/dist/core/tools/index.js.map +1 -1
  51. package/dist/core/tools/read.d.ts +5 -1
  52. package/dist/core/tools/read.d.ts.map +1 -1
  53. package/dist/core/tools/read.js +22 -5
  54. package/dist/core/tools/read.js.map +1 -1
  55. package/dist/index.d.ts +3 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +2 -2
  58. package/dist/index.js.map +1 -1
  59. package/dist/main.d.ts.map +1 -1
  60. package/dist/main.js +5 -5
  61. package/dist/main.js.map +1 -1
  62. package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
  63. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  64. package/dist/modes/interactive/components/bash-execution.js +6 -4
  65. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  66. package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
  67. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  68. package/dist/modes/interactive/components/custom-editor.js +7 -1
  69. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  70. package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/hook-editor.js +3 -3
  72. package/dist/modes/interactive/components/hook-editor.js.map +1 -1
  73. package/dist/modes/interactive/components/hook-input.d.ts.map +1 -1
  74. package/dist/modes/interactive/components/hook-input.js +3 -3
  75. package/dist/modes/interactive/components/hook-input.js.map +1 -1
  76. package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/hook-selector.js +3 -3
  78. package/dist/modes/interactive/components/hook-selector.js.map +1 -1
  79. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  80. package/dist/modes/interactive/components/model-selector.js +3 -3
  81. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  82. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/oauth-selector.js +3 -3
  84. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  85. package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
  86. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  87. package/dist/modes/interactive/components/settings-selector.js +37 -6
  88. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  89. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/tool-execution.js +7 -1
  91. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  92. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  93. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  94. package/dist/modes/interactive/interactive-mode.js +74 -26
  95. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  96. package/dist/modes/print-mode.d.ts.map +1 -1
  97. package/dist/modes/print-mode.js +3 -3
  98. package/dist/modes/print-mode.js.map +1 -1
  99. package/dist/modes/rpc/rpc-client.d.ts +12 -4
  100. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  101. package/dist/modes/rpc/rpc-client.js +18 -6
  102. package/dist/modes/rpc/rpc-client.js.map +1 -1
  103. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  104. package/dist/modes/rpc/rpc-mode.js +21 -12
  105. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  106. package/dist/modes/rpc/rpc-types.d.ts +25 -6
  107. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  108. package/dist/modes/rpc/rpc-types.js.map +1 -1
  109. package/dist/utils/image-resize.d.ts +29 -0
  110. package/dist/utils/image-resize.d.ts.map +1 -0
  111. package/dist/utils/image-resize.js +111 -0
  112. package/dist/utils/image-resize.js.map +1 -0
  113. package/docs/hooks.md +16 -9
  114. package/examples/README.md +6 -0
  115. package/examples/custom-tools/README.md +2 -0
  116. package/examples/hooks/README.md +1 -0
  117. package/examples/hooks/file-trigger.ts +1 -1
  118. package/examples/hooks/todo/index.ts +134 -0
  119. package/package.json +6 -5
  120. package/dist/modes/interactive/components/queue-mode-selector.d.ts +0 -10
  121. package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +0 -1
  122. package/dist/modes/interactive/components/queue-mode-selector.js +0 -42
  123. package/dist/modes/interactive/components/queue-mode-selector.js.map +0 -1
@@ -194,6 +194,9 @@ export class InteractiveMode {
194
194
  theme.fg("dim", "!") +
195
195
  theme.fg("muted", " to run bash") +
196
196
  "\n" +
197
+ theme.fg("dim", "alt+enter") +
198
+ theme.fg("muted", " to queue follow-up") +
199
+ "\n" +
197
200
  theme.fg("dim", "drop files") +
198
201
  theme.fg("muted", " to attach");
199
202
  const header = new Text(`${logo}\n${instructions}`, 1, 0);
@@ -230,6 +233,9 @@ export class InteractiveMode {
230
233
  // Start the UI
231
234
  this.ui.start();
232
235
  this.isInitialized = true;
236
+ // Set terminal title
237
+ const cwdBasename = path.basename(process.cwd());
238
+ this.ui.terminal.setTitle(`pi - ${cwdBasename}`);
233
239
  // Initialize hooks with TUI-based UI context
234
240
  await this.initHooksAndCustomTools();
235
241
  // Subscribe to agent events
@@ -312,10 +318,10 @@ export class InteractiveMode {
312
318
  }
313
319
  hookRunner.initialize({
314
320
  getModel: () => this.session.model,
315
- sendMessageHandler: (message, triggerTurn) => {
321
+ sendMessageHandler: (message, options) => {
316
322
  const wasStreaming = this.session.isStreaming;
317
323
  this.session
318
- .sendHookMessage(message, triggerTurn)
324
+ .sendHookMessage(message, options)
319
325
  .then(() => {
320
326
  // For non-streaming cases with display=true, update UI
321
327
  // (streaming cases update via message_end event)
@@ -388,7 +394,7 @@ export class InteractiveMode {
388
394
  abort: () => {
389
395
  this.session.abort();
390
396
  },
391
- hasQueuedMessages: () => this.session.queuedMessageCount > 0,
397
+ hasPendingMessages: () => this.session.pendingMessageCount > 0,
392
398
  uiContext,
393
399
  hasUI: true,
394
400
  });
@@ -420,7 +426,7 @@ export class InteractiveMode {
420
426
  modelRegistry: this.session.modelRegistry,
421
427
  model: this.session.model,
422
428
  isIdle: () => !this.session.isStreaming,
423
- hasQueuedMessages: () => this.session.queuedMessageCount > 0,
429
+ hasPendingMessages: () => this.session.pendingMessageCount > 0,
424
430
  abort: () => {
425
431
  this.session.abort();
426
432
  },
@@ -596,8 +602,9 @@ export class InteractiveMode {
596
602
  this.editor.onEscape = () => {
597
603
  if (this.loadingAnimation) {
598
604
  // Abort and restore queued messages to editor
599
- const queuedMessages = this.session.clearQueue();
600
- const queuedText = queuedMessages.join("\n\n");
605
+ const { steering, followUp } = this.session.clearQueue();
606
+ const allQueued = [...steering, ...followUp];
607
+ const queuedText = allQueued.join("\n\n");
601
608
  const currentText = this.editor.getText();
602
609
  const combinedText = [queuedText, currentText].filter((t) => t.trim()).join("\n\n");
603
610
  this.editor.setText(combinedText);
@@ -613,10 +620,15 @@ export class InteractiveMode {
613
620
  this.updateEditorBorderColor();
614
621
  }
615
622
  else if (!this.editor.getText().trim()) {
616
- // Double-escape with empty editor triggers /branch
623
+ // Double-escape with empty editor triggers /tree or /branch based on setting
617
624
  const now = Date.now();
618
625
  if (now - this.lastEscapeTime < 500) {
619
- this.showUserMessageSelector();
626
+ if (this.settingsManager.getDoubleEscapeAction() === "tree") {
627
+ this.showTreeSelector();
628
+ }
629
+ else {
630
+ this.showUserMessageSelector();
631
+ }
620
632
  this.lastEscapeTime = 0;
621
633
  }
622
634
  else {
@@ -636,6 +648,7 @@ export class InteractiveMode {
636
648
  this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
637
649
  this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
638
650
  this.editor.onCtrlG = () => this.openExternalEditor();
651
+ this.editor.onAltEnter = () => this.handleAltEnter();
639
652
  this.editor.onChange = (text) => {
640
653
  const wasBashMode = this.isBashMode;
641
654
  this.isBashMode = text.trimStart().startsWith("!");
@@ -742,9 +755,10 @@ export class InteractiveMode {
742
755
  this.editor.setText("");
743
756
  return;
744
757
  }
745
- // Handle bash command
758
+ // Handle bash command (! for normal, !! for excluded from context)
746
759
  if (text.startsWith("!")) {
747
- const command = text.slice(1).trim();
760
+ const isExcluded = text.startsWith("!!");
761
+ const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
748
762
  if (command) {
749
763
  if (this.session.isBashRunning) {
750
764
  this.showWarning("A bash command is already running. Press Esc to cancel it first.");
@@ -752,7 +766,7 @@ export class InteractiveMode {
752
766
  return;
753
767
  }
754
768
  this.editor.addToHistory(text);
755
- await this.handleBashCommand(command);
769
+ await this.handleBashCommand(command, isExcluded);
756
770
  this.isBashMode = false;
757
771
  this.updateEditorBorderColor();
758
772
  return;
@@ -775,9 +789,9 @@ export class InteractiveMode {
775
789
  return;
776
790
  }
777
791
  }
778
- // Queue regular messages if agent is streaming
792
+ // Queue steering message if agent is streaming (interrupts current work)
779
793
  if (this.session.isStreaming) {
780
- await this.session.queueMessage(text);
794
+ await this.session.steer(text);
781
795
  this.updatePendingMessagesDisplay();
782
796
  this.editor.addToHistory(text);
783
797
  this.editor.setText("");
@@ -1051,7 +1065,7 @@ export class InteractiveMode {
1051
1065
  addMessageToChat(message, options) {
1052
1066
  switch (message.role) {
1053
1067
  case "bashExecution": {
1054
- const component = new BashExecutionComponent(message.command, this.ui);
1068
+ const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
1055
1069
  if (message.output) {
1056
1070
  component.appendOutput(message.output);
1057
1071
  }
@@ -1226,6 +1240,23 @@ export class InteractiveMode {
1226
1240
  // Send SIGTSTP to process group (pid=0 means all processes in group)
1227
1241
  process.kill(0, "SIGTSTP");
1228
1242
  }
1243
+ async handleAltEnter() {
1244
+ const text = this.editor.getText().trim();
1245
+ if (!text)
1246
+ return;
1247
+ // Alt+Enter queues a follow-up message (waits until agent finishes)
1248
+ if (this.session.isStreaming) {
1249
+ await this.session.followUp(text);
1250
+ this.updatePendingMessagesDisplay();
1251
+ this.editor.addToHistory(text);
1252
+ this.editor.setText("");
1253
+ this.ui.requestRender();
1254
+ }
1255
+ // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
1256
+ else if (this.editor.onSubmit) {
1257
+ this.editor.onSubmit(text);
1258
+ }
1259
+ }
1229
1260
  updateEditorBorderColor() {
1230
1261
  if (this.isBashMode) {
1231
1262
  this.editor.borderColor = theme.getBashModeBorderColor();
@@ -1357,12 +1388,17 @@ export class InteractiveMode {
1357
1388
  }
1358
1389
  updatePendingMessagesDisplay() {
1359
1390
  this.pendingMessagesContainer.clear();
1360
- const queuedMessages = this.session.getQueuedMessages();
1361
- if (queuedMessages.length > 0) {
1391
+ const steeringMessages = this.session.getSteeringMessages();
1392
+ const followUpMessages = this.session.getFollowUpMessages();
1393
+ if (steeringMessages.length > 0 || followUpMessages.length > 0) {
1362
1394
  this.pendingMessagesContainer.addChild(new Spacer(1));
1363
- for (const message of queuedMessages) {
1364
- const queuedText = theme.fg("dim", `Queued: ${message}`);
1365
- this.pendingMessagesContainer.addChild(new TruncatedText(queuedText, 1, 0));
1395
+ for (const message of steeringMessages) {
1396
+ const text = theme.fg("dim", `Steering: ${message}`);
1397
+ this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
1398
+ }
1399
+ for (const message of followUpMessages) {
1400
+ const text = theme.fg("dim", `Follow-up: ${message}`);
1401
+ this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
1366
1402
  }
1367
1403
  }
1368
1404
  }
@@ -1398,13 +1434,16 @@ export class InteractiveMode {
1398
1434
  const selector = new SettingsSelectorComponent({
1399
1435
  autoCompact: this.session.autoCompactionEnabled,
1400
1436
  showImages: this.settingsManager.getShowImages(),
1401
- queueMode: this.session.queueMode,
1437
+ autoResizeImages: this.settingsManager.getImageAutoResize(),
1438
+ steeringMode: this.session.steeringMode,
1439
+ followUpMode: this.session.followUpMode,
1402
1440
  thinkingLevel: this.session.thinkingLevel,
1403
1441
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
1404
1442
  currentTheme: this.settingsManager.getTheme() || "dark",
1405
1443
  availableThemes: getAvailableThemes(),
1406
1444
  hideThinkingBlock: this.hideThinkingBlock,
1407
1445
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
1446
+ doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
1408
1447
  }, {
1409
1448
  onAutoCompactChange: (enabled) => {
1410
1449
  this.session.setAutoCompactionEnabled(enabled);
@@ -1418,8 +1457,14 @@ export class InteractiveMode {
1418
1457
  }
1419
1458
  }
1420
1459
  },
1421
- onQueueModeChange: (mode) => {
1422
- this.session.setQueueMode(mode);
1460
+ onAutoResizeImagesChange: (enabled) => {
1461
+ this.settingsManager.setImageAutoResize(enabled);
1462
+ },
1463
+ onSteeringModeChange: (mode) => {
1464
+ this.session.setSteeringMode(mode);
1465
+ },
1466
+ onFollowUpModeChange: (mode) => {
1467
+ this.session.setFollowUpMode(mode);
1423
1468
  },
1424
1469
  onThinkingLevelChange: (level) => {
1425
1470
  this.session.setThinkingLevel(level);
@@ -1455,6 +1500,9 @@ export class InteractiveMode {
1455
1500
  onCollapseChangelogChange: (collapsed) => {
1456
1501
  this.settingsManager.setCollapseChangelog(collapsed);
1457
1502
  },
1503
+ onDoubleEscapeActionChange: (action) => {
1504
+ this.settingsManager.setDoubleEscapeAction(action);
1505
+ },
1458
1506
  onCancel: () => {
1459
1507
  done();
1460
1508
  this.ui.requestRender();
@@ -1835,7 +1883,7 @@ export class InteractiveMode {
1835
1883
  handleSessionCommand() {
1836
1884
  const stats = this.session.getSessionStats();
1837
1885
  let info = `${theme.bold("Session Info")}\n\n`;
1838
- info += `${theme.fg("dim", "File:")} ${stats.sessionFile}\n`;
1886
+ info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
1839
1887
  info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
1840
1888
  info += `${theme.bold("Messages")}\n`;
1841
1889
  info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
@@ -1971,9 +2019,9 @@ export class InteractiveMode {
1971
2019
  this.chatContainer.addChild(new ArminComponent(this.ui));
1972
2020
  this.ui.requestRender();
1973
2021
  }
1974
- async handleBashCommand(command) {
2022
+ async handleBashCommand(command, excludeFromContext = false) {
1975
2023
  const isDeferred = this.session.isStreaming;
1976
- this.bashComponent = new BashExecutionComponent(command, this.ui);
2024
+ this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
1977
2025
  if (isDeferred) {
1978
2026
  // Show in pending area when agent is streaming
1979
2027
  this.pendingMessagesContainer.addChild(this.bashComponent);
@@ -1990,7 +2038,7 @@ export class InteractiveMode {
1990
2038
  this.bashComponent.appendOutput(chunk);
1991
2039
  this.ui.requestRender();
1992
2040
  }
1993
- });
2041
+ }, { excludeFromContext });
1994
2042
  if (this.bashComponent) {
1995
2043
  this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
1996
2044
  }