@earendil-works/pi-coding-agent 0.76.0 → 0.78.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 (119) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +9 -0
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +23 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session-services.d.ts +1 -0
  8. package/dist/core/agent-session-services.d.ts.map +1 -1
  9. package/dist/core/agent-session-services.js +1 -0
  10. package/dist/core/agent-session-services.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +4 -1
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +23 -4
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/extensions/runner.d.ts +1 -1
  16. package/dist/core/extensions/runner.d.ts.map +1 -1
  17. package/dist/core/extensions/runner.js +8 -2
  18. package/dist/core/extensions/runner.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +7 -5
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js.map +1 -1
  22. package/dist/core/model-registry.d.ts.map +1 -1
  23. package/dist/core/model-registry.js +65 -13
  24. package/dist/core/model-registry.js.map +1 -1
  25. package/dist/core/model-resolver.d.ts.map +1 -1
  26. package/dist/core/model-resolver.js +1 -1
  27. package/dist/core/model-resolver.js.map +1 -1
  28. package/dist/core/resolve-config-value.d.ts +9 -1
  29. package/dist/core/resolve-config-value.d.ts.map +1 -1
  30. package/dist/core/resolve-config-value.js +134 -11
  31. package/dist/core/resolve-config-value.js.map +1 -1
  32. package/dist/core/sdk.d.ts +2 -0
  33. package/dist/core/sdk.d.ts.map +1 -1
  34. package/dist/core/sdk.js +4 -5
  35. package/dist/core/sdk.js.map +1 -1
  36. package/dist/core/session-manager.d.ts +3 -5
  37. package/dist/core/session-manager.d.ts.map +1 -1
  38. package/dist/core/session-manager.js +42 -17
  39. package/dist/core/session-manager.js.map +1 -1
  40. package/dist/core/system-prompt.d.ts.map +1 -1
  41. package/dist/core/system-prompt.js +0 -3
  42. package/dist/core/system-prompt.js.map +1 -1
  43. package/dist/core/tools/edit.d.ts.map +1 -1
  44. package/dist/core/tools/edit.js +7 -10
  45. package/dist/core/tools/edit.js.map +1 -1
  46. package/dist/core/tools/find.d.ts.map +1 -1
  47. package/dist/core/tools/find.js.map +1 -1
  48. package/dist/core/tools/grep.d.ts.map +1 -1
  49. package/dist/core/tools/grep.js.map +1 -1
  50. package/dist/core/tools/ls.d.ts.map +1 -1
  51. package/dist/core/tools/ls.js +5 -7
  52. package/dist/core/tools/ls.js.map +1 -1
  53. package/dist/core/tools/read.d.ts.map +1 -1
  54. package/dist/core/tools/read.js +6 -7
  55. package/dist/core/tools/read.js.map +1 -1
  56. package/dist/core/tools/render-utils.d.ts +5 -2
  57. package/dist/core/tools/render-utils.d.ts.map +1 -1
  58. package/dist/core/tools/render-utils.js +17 -1
  59. package/dist/core/tools/render-utils.js.map +1 -1
  60. package/dist/core/tools/write.d.ts.map +1 -1
  61. package/dist/core/tools/write.js +5 -6
  62. package/dist/core/tools/write.js.map +1 -1
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/main.d.ts.map +1 -1
  68. package/dist/main.js +15 -2
  69. package/dist/main.js.map +1 -1
  70. package/dist/migrations.d.ts.map +1 -1
  71. package/dist/migrations.js +118 -1
  72. package/dist/migrations.js.map +1 -1
  73. package/dist/modes/interactive/components/login-dialog.d.ts +1 -3
  74. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/login-dialog.js +2 -4
  76. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  77. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  78. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.js +59 -6
  80. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  81. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  82. package/dist/modes/interactive/theme/theme.js +10 -0
  83. package/dist/modes/interactive/theme/theme.js.map +1 -1
  84. package/dist/utils/deprecation.d.ts +4 -0
  85. package/dist/utils/deprecation.d.ts.map +1 -0
  86. package/dist/utils/deprecation.js +13 -0
  87. package/dist/utils/deprecation.js.map +1 -0
  88. package/dist/utils/json.d.ts +3 -0
  89. package/dist/utils/json.d.ts.map +1 -0
  90. package/dist/utils/json.js +7 -0
  91. package/dist/utils/json.js.map +1 -0
  92. package/docs/custom-provider.md +13 -10
  93. package/docs/development.md +1 -1
  94. package/docs/extensions.md +12 -6
  95. package/docs/models.md +25 -12
  96. package/docs/providers.md +13 -5
  97. package/docs/quickstart.md +1 -0
  98. package/docs/rpc.md +2 -1
  99. package/docs/sdk.md +6 -0
  100. package/docs/session-format.md +1 -1
  101. package/docs/sessions.md +8 -0
  102. package/docs/settings.md +1 -1
  103. package/docs/terminal-setup.md +2 -0
  104. package/docs/tui.md +2 -2
  105. package/docs/usage.md +9 -0
  106. package/examples/extensions/README.md +1 -0
  107. package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
  108. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  109. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  110. package/examples/extensions/custom-provider-gitlab-duo/index.ts +54 -3
  111. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  112. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  113. package/examples/extensions/input-transform-streaming.ts +39 -0
  114. package/examples/extensions/sandbox/package-lock.json +2 -2
  115. package/examples/extensions/sandbox/package.json +1 -1
  116. package/examples/extensions/with-deps/package-lock.json +2 -2
  117. package/examples/extensions/with-deps/package.json +1 -1
  118. package/npm-shrinkwrap.json +71 -56
  119. package/package.json +6 -6
@@ -8,6 +8,7 @@ import * as os from "node:os";
8
8
  import * as path from "node:path";
9
9
  import { getProviders, } from "@earendil-works/pi-ai";
10
10
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
11
+ import chalk from "chalk";
11
12
  import { spawn, spawnSync } from "child_process";
12
13
  import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
13
14
  import { parseSkillBlock } from "../../core/agent-session.js";
@@ -91,6 +92,27 @@ function isAnthropicSubscriptionAuthKey(apiKey) {
91
92
  function isUnknownModel(model) {
92
93
  return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
93
94
  }
95
+ function quoteIfNeeded(value) {
96
+ if (value.length > 0 && !/[^a-zA-Z0-9_\-./~:@]/.test(value)) {
97
+ return value;
98
+ }
99
+ return `'${value.replace(/'/g, `'\\''`)}'`;
100
+ }
101
+ export function formatResumeCommand(sessionManager) {
102
+ if (!process.stdout.isTTY)
103
+ return undefined;
104
+ if (!sessionManager.isPersisted())
105
+ return undefined;
106
+ const sessionFile = sessionManager.getSessionFile();
107
+ if (!sessionFile || !fs.existsSync(sessionFile))
108
+ return undefined;
109
+ const args = [APP_NAME];
110
+ if (!sessionManager.usesDefaultSessionDir()) {
111
+ args.push("--session-dir", quoteIfNeeded(sessionManager.getSessionDir()));
112
+ }
113
+ args.push("--session", sessionManager.getSessionId());
114
+ return args.join(" ");
115
+ }
94
116
  function hasDefaultModelProvider(providerId) {
95
117
  return providerId in defaultModelPerProvider;
96
118
  }
@@ -125,6 +147,7 @@ export class InteractiveMode {
125
147
  version;
126
148
  isInitialized = false;
127
149
  onInputCallback;
150
+ pendingUserInputs = [];
128
151
  loadingAnimation = undefined;
129
152
  workingMessage = undefined;
130
153
  workingVisible = true;
@@ -2130,6 +2153,9 @@ export class InteractiveMode {
2130
2153
  if (this.onInputCallback) {
2131
2154
  this.onInputCallback(text);
2132
2155
  }
2156
+ else {
2157
+ this.pendingUserInputs.push(text);
2158
+ }
2133
2159
  this.editor.addToHistory?.(text);
2134
2160
  };
2135
2161
  }
@@ -2608,6 +2634,10 @@ export class InteractiveMode {
2608
2634
  }
2609
2635
  }
2610
2636
  async getUserInput() {
2637
+ const queuedInput = this.pendingUserInputs.shift();
2638
+ if (queuedInput !== undefined) {
2639
+ return queuedInput;
2640
+ }
2611
2641
  return new Promise((resolve) => {
2612
2642
  this.onInputCallback = (text) => {
2613
2643
  this.onInputCallback = undefined;
@@ -2643,16 +2673,36 @@ export class InteractiveMode {
2643
2673
  * repaint the final frame while the process is exiting.
2644
2674
  */
2645
2675
  isShuttingDown = false;
2646
- async shutdown() {
2676
+ async shutdown(options) {
2647
2677
  if (this.isShuttingDown)
2648
2678
  return;
2649
2679
  this.isShuttingDown = true;
2650
2680
  this.unregisterSignalHandlers();
2681
+ if (options?.fromSignal) {
2682
+ // Signal-triggered shutdown (SIGTERM/SIGHUP). Emit extension cleanup
2683
+ // (session_shutdown) BEFORE touching the terminal. Extension teardown
2684
+ // such as removing sockets does not write to the tty, so it must not be
2685
+ // skipped if a later terminal-restore write fails on a dead or stalled
2686
+ // terminal. If the terminal is gone, the restore writes below emit EIO,
2687
+ // which the stdout/stderr error handler turns into emergencyTerminalExit;
2688
+ // the render loop is already idle, so this cannot hot-spin (see #4144).
2689
+ await this.runtimeHost.dispose();
2690
+ await this.ui.terminal.drainInput(1000);
2691
+ this.stop();
2692
+ process.exit(0);
2693
+ }
2694
+ // Interactive quit (Ctrl+D, Ctrl+C, /quit, extension shutdown()). Stop the
2695
+ // TUI before emitting shutdown events so extension UI cleanup cannot repaint
2696
+ // the final frame while the process is exiting.
2651
2697
  // Drain any in-flight Kitty key release events before stopping.
2652
2698
  // This prevents escape sequences from leaking to the parent shell over slow SSH.
2653
2699
  await this.ui.terminal.drainInput(1000);
2654
2700
  this.stop();
2655
2701
  await this.runtimeHost.dispose();
2702
+ const resumeCommand = formatResumeCommand(this.sessionManager);
2703
+ if (resumeCommand) {
2704
+ process.stdout.write(`${chalk.dim("To resume this session:")} ${resumeCommand}\n`);
2705
+ }
2656
2706
  process.exit(0);
2657
2707
  }
2658
2708
  emergencyTerminalExit() {
@@ -2711,11 +2761,12 @@ export class InteractiveMode {
2711
2761
  }
2712
2762
  for (const signal of signals) {
2713
2763
  const handler = () => {
2714
- if (signal === "SIGHUP") {
2715
- this.emergencyTerminalExit();
2716
- }
2764
+ // SIGHUP no longer hard-exits: graceful shutdown emits session_shutdown
2765
+ // first, then attempts terminal restore. A genuinely dead terminal
2766
+ // surfaces as an EIO on the restore writes, which the stdout/stderr
2767
+ // error handler converts into emergencyTerminalExit (see #4144, #5080).
2717
2768
  killTrackedDetachedChildren();
2718
- void this.shutdown();
2769
+ void this.shutdown({ fromSignal: true });
2719
2770
  };
2720
2771
  process.prependListener(signal, handler);
2721
2772
  this.signalCleanupHandlers.push(() => process.off(signal, handler));
@@ -3642,7 +3693,9 @@ export class InteractiveMode {
3642
3693
  }
3643
3694
  showSessionSelector() {
3644
3695
  this.showSelector((done) => {
3645
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), SessionManager.listAll, async (sessionPath) => {
3696
+ const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
3697
+ ? SessionManager.listAll(onProgress)
3698
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
3646
3699
  done();
3647
3700
  await this.handleResumeSession(sessionPath);
3648
3701
  }, () => {