@mariozechner/pi-coding-agent 0.31.1 → 0.32.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 (112) hide show
  1. package/CHANGELOG.md +44 -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 +41 -16
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +90 -41
  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/model-registry.d.ts +5 -2
  30. package/dist/core/model-registry.d.ts.map +1 -1
  31. package/dist/core/model-registry.js +85 -49
  32. package/dist/core/model-registry.js.map +1 -1
  33. package/dist/core/model-resolver.d.ts.map +1 -1
  34. package/dist/core/model-resolver.js +1 -0
  35. package/dist/core/model-resolver.js.map +1 -1
  36. package/dist/core/sdk.d.ts.map +1 -1
  37. package/dist/core/sdk.js +9 -6
  38. package/dist/core/sdk.js.map +1 -1
  39. package/dist/core/settings-manager.d.ts +17 -3
  40. package/dist/core/settings-manager.d.ts.map +1 -1
  41. package/dist/core/settings-manager.js +41 -6
  42. package/dist/core/settings-manager.js.map +1 -1
  43. package/dist/core/tools/index.d.ts +9 -4
  44. package/dist/core/tools/index.d.ts.map +1 -1
  45. package/dist/core/tools/index.js +6 -6
  46. package/dist/core/tools/index.js.map +1 -1
  47. package/dist/core/tools/read.d.ts +5 -1
  48. package/dist/core/tools/read.d.ts.map +1 -1
  49. package/dist/core/tools/read.js +22 -5
  50. package/dist/core/tools/read.js.map +1 -1
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/main.d.ts.map +1 -1
  56. package/dist/main.js +5 -5
  57. package/dist/main.js.map +1 -1
  58. package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
  59. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/custom-editor.js +7 -1
  61. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  62. package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/hook-editor.js +3 -3
  64. package/dist/modes/interactive/components/hook-editor.js.map +1 -1
  65. package/dist/modes/interactive/components/hook-input.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/hook-input.js +3 -3
  67. package/dist/modes/interactive/components/hook-input.js.map +1 -1
  68. package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/hook-selector.js +3 -3
  70. package/dist/modes/interactive/components/hook-selector.js.map +1 -1
  71. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/model-selector.js +3 -3
  73. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  74. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/oauth-selector.js +3 -3
  76. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  77. package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
  78. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/settings-selector.js +37 -6
  80. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  81. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  82. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  83. package/dist/modes/interactive/interactive-mode.js +66 -19
  84. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  85. package/dist/modes/print-mode.d.ts.map +1 -1
  86. package/dist/modes/print-mode.js +3 -3
  87. package/dist/modes/print-mode.js.map +1 -1
  88. package/dist/modes/rpc/rpc-client.d.ts +12 -4
  89. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  90. package/dist/modes/rpc/rpc-client.js +18 -6
  91. package/dist/modes/rpc/rpc-client.js.map +1 -1
  92. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-mode.js +21 -12
  94. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  95. package/dist/modes/rpc/rpc-types.d.ts +25 -6
  96. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  97. package/dist/modes/rpc/rpc-types.js.map +1 -1
  98. package/dist/utils/image-resize.d.ts +29 -0
  99. package/dist/utils/image-resize.d.ts.map +1 -0
  100. package/dist/utils/image-resize.js +111 -0
  101. package/dist/utils/image-resize.js.map +1 -0
  102. package/docs/hooks.md +16 -9
  103. package/examples/README.md +6 -0
  104. package/examples/custom-tools/README.md +2 -0
  105. package/examples/hooks/README.md +1 -0
  106. package/examples/hooks/file-trigger.ts +1 -1
  107. package/examples/hooks/todo/index.ts +134 -0
  108. package/package.json +6 -5
  109. package/dist/modes/interactive/components/queue-mode-selector.d.ts +0 -10
  110. package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +0 -1
  111. package/dist/modes/interactive/components/queue-mode-selector.js +0 -42
  112. 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("!");
@@ -775,9 +788,9 @@ export class InteractiveMode {
775
788
  return;
776
789
  }
777
790
  }
778
- // Queue regular messages if agent is streaming
791
+ // Queue steering message if agent is streaming (interrupts current work)
779
792
  if (this.session.isStreaming) {
780
- await this.session.queueMessage(text);
793
+ await this.session.steer(text);
781
794
  this.updatePendingMessagesDisplay();
782
795
  this.editor.addToHistory(text);
783
796
  this.editor.setText("");
@@ -1226,6 +1239,23 @@ export class InteractiveMode {
1226
1239
  // Send SIGTSTP to process group (pid=0 means all processes in group)
1227
1240
  process.kill(0, "SIGTSTP");
1228
1241
  }
1242
+ async handleAltEnter() {
1243
+ const text = this.editor.getText().trim();
1244
+ if (!text)
1245
+ return;
1246
+ // Alt+Enter queues a follow-up message (waits until agent finishes)
1247
+ if (this.session.isStreaming) {
1248
+ await this.session.followUp(text);
1249
+ this.updatePendingMessagesDisplay();
1250
+ this.editor.addToHistory(text);
1251
+ this.editor.setText("");
1252
+ this.ui.requestRender();
1253
+ }
1254
+ // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
1255
+ else if (this.editor.onSubmit) {
1256
+ this.editor.onSubmit(text);
1257
+ }
1258
+ }
1229
1259
  updateEditorBorderColor() {
1230
1260
  if (this.isBashMode) {
1231
1261
  this.editor.borderColor = theme.getBashModeBorderColor();
@@ -1357,12 +1387,17 @@ export class InteractiveMode {
1357
1387
  }
1358
1388
  updatePendingMessagesDisplay() {
1359
1389
  this.pendingMessagesContainer.clear();
1360
- const queuedMessages = this.session.getQueuedMessages();
1361
- if (queuedMessages.length > 0) {
1390
+ const steeringMessages = this.session.getSteeringMessages();
1391
+ const followUpMessages = this.session.getFollowUpMessages();
1392
+ if (steeringMessages.length > 0 || followUpMessages.length > 0) {
1362
1393
  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));
1394
+ for (const message of steeringMessages) {
1395
+ const text = theme.fg("dim", `Steering: ${message}`);
1396
+ this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
1397
+ }
1398
+ for (const message of followUpMessages) {
1399
+ const text = theme.fg("dim", `Follow-up: ${message}`);
1400
+ this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
1366
1401
  }
1367
1402
  }
1368
1403
  }
@@ -1398,13 +1433,16 @@ export class InteractiveMode {
1398
1433
  const selector = new SettingsSelectorComponent({
1399
1434
  autoCompact: this.session.autoCompactionEnabled,
1400
1435
  showImages: this.settingsManager.getShowImages(),
1401
- queueMode: this.session.queueMode,
1436
+ autoResizeImages: this.settingsManager.getImageAutoResize(),
1437
+ steeringMode: this.session.steeringMode,
1438
+ followUpMode: this.session.followUpMode,
1402
1439
  thinkingLevel: this.session.thinkingLevel,
1403
1440
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
1404
1441
  currentTheme: this.settingsManager.getTheme() || "dark",
1405
1442
  availableThemes: getAvailableThemes(),
1406
1443
  hideThinkingBlock: this.hideThinkingBlock,
1407
1444
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
1445
+ doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
1408
1446
  }, {
1409
1447
  onAutoCompactChange: (enabled) => {
1410
1448
  this.session.setAutoCompactionEnabled(enabled);
@@ -1418,8 +1456,14 @@ export class InteractiveMode {
1418
1456
  }
1419
1457
  }
1420
1458
  },
1421
- onQueueModeChange: (mode) => {
1422
- this.session.setQueueMode(mode);
1459
+ onAutoResizeImagesChange: (enabled) => {
1460
+ this.settingsManager.setImageAutoResize(enabled);
1461
+ },
1462
+ onSteeringModeChange: (mode) => {
1463
+ this.session.setSteeringMode(mode);
1464
+ },
1465
+ onFollowUpModeChange: (mode) => {
1466
+ this.session.setFollowUpMode(mode);
1423
1467
  },
1424
1468
  onThinkingLevelChange: (level) => {
1425
1469
  this.session.setThinkingLevel(level);
@@ -1455,6 +1499,9 @@ export class InteractiveMode {
1455
1499
  onCollapseChangelogChange: (collapsed) => {
1456
1500
  this.settingsManager.setCollapseChangelog(collapsed);
1457
1501
  },
1502
+ onDoubleEscapeActionChange: (action) => {
1503
+ this.settingsManager.setDoubleEscapeAction(action);
1504
+ },
1458
1505
  onCancel: () => {
1459
1506
  done();
1460
1507
  this.ui.requestRender();
@@ -1835,7 +1882,7 @@ export class InteractiveMode {
1835
1882
  handleSessionCommand() {
1836
1883
  const stats = this.session.getSessionStats();
1837
1884
  let info = `${theme.bold("Session Info")}\n\n`;
1838
- info += `${theme.fg("dim", "File:")} ${stats.sessionFile}\n`;
1885
+ info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
1839
1886
  info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
1840
1887
  info += `${theme.bold("Messages")}\n`;
1841
1888
  info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;