@earendil-works/pi-coding-agent 0.74.0 → 0.74.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 (139) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +8 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +10 -2
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +13 -16
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +2 -2
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/bash-executor.d.ts.map +1 -1
  16. package/dist/core/bash-executor.js +1 -1
  17. package/dist/core/bash-executor.js.map +1 -1
  18. package/dist/core/compaction/compaction.d.ts.map +1 -1
  19. package/dist/core/compaction/compaction.js +2 -2
  20. package/dist/core/compaction/compaction.js.map +1 -1
  21. package/dist/core/model-registry.d.ts.map +1 -1
  22. package/dist/core/model-registry.js +1 -0
  23. package/dist/core/model-registry.js.map +1 -1
  24. package/dist/core/model-resolver.d.ts.map +1 -1
  25. package/dist/core/model-resolver.js +1 -0
  26. package/dist/core/model-resolver.js.map +1 -1
  27. package/dist/core/package-manager.d.ts +1 -0
  28. package/dist/core/package-manager.d.ts.map +1 -1
  29. package/dist/core/package-manager.js +45 -7
  30. package/dist/core/package-manager.js.map +1 -1
  31. package/dist/core/prompt-templates.d.ts.map +1 -1
  32. package/dist/core/prompt-templates.js +6 -4
  33. package/dist/core/prompt-templates.js.map +1 -1
  34. package/dist/core/provider-display-names.d.ts.map +1 -1
  35. package/dist/core/provider-display-names.js +1 -0
  36. package/dist/core/provider-display-names.js.map +1 -1
  37. package/dist/core/sdk.d.ts +1 -1
  38. package/dist/core/sdk.d.ts.map +1 -1
  39. package/dist/core/sdk.js +1 -1
  40. package/dist/core/sdk.js.map +1 -1
  41. package/dist/core/session-manager.d.ts +1 -1
  42. package/dist/core/session-manager.d.ts.map +1 -1
  43. package/dist/core/session-manager.js +39 -9
  44. package/dist/core/session-manager.js.map +1 -1
  45. package/dist/core/skills.d.ts.map +1 -1
  46. package/dist/core/skills.js +2 -5
  47. package/dist/core/skills.js.map +1 -1
  48. package/dist/core/tools/read.d.ts.map +1 -1
  49. package/dist/core/tools/read.js +2 -1
  50. package/dist/core/tools/read.js.map +1 -1
  51. package/dist/core/tools/render-utils.d.ts.map +1 -1
  52. package/dist/core/tools/render-utils.js +1 -1
  53. package/dist/core/tools/render-utils.js.map +1 -1
  54. package/dist/main.d.ts.map +1 -1
  55. package/dist/main.js +0 -10
  56. package/dist/main.js.map +1 -1
  57. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/bash-execution.js +1 -1
  59. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  60. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  61. package/dist/modes/interactive/components/config-selector.js +23 -1
  62. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  63. package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
  64. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  65. package/dist/modes/interactive/components/extension-selector.js +6 -1
  66. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  67. package/dist/modes/interactive/components/keybinding-hints.d.ts +5 -0
  68. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/keybinding-hints.js +19 -5
  70. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  71. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/settings-selector.js +3 -1
  73. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  74. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/tree-selector.js +2 -1
  76. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  77. package/dist/modes/interactive/interactive-mode.d.ts +12 -4
  78. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.js +67 -27
  80. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  81. package/dist/modes/interactive/theme/dark.json +1 -1
  82. package/dist/modes/interactive/theme/light.json +1 -1
  83. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  84. package/dist/modes/interactive/theme/theme.js +3 -1
  85. package/dist/modes/interactive/theme/theme.js.map +1 -1
  86. package/dist/utils/ansi.d.ts +2 -0
  87. package/dist/utils/ansi.d.ts.map +1 -0
  88. package/dist/utils/ansi.js +52 -0
  89. package/dist/utils/ansi.js.map +1 -0
  90. package/dist/utils/html.d.ts +7 -0
  91. package/dist/utils/html.d.ts.map +1 -0
  92. package/dist/utils/html.js +40 -0
  93. package/dist/utils/html.js.map +1 -0
  94. package/dist/utils/mime.d.ts +1 -0
  95. package/dist/utils/mime.d.ts.map +1 -1
  96. package/dist/utils/mime.js +59 -16
  97. package/dist/utils/mime.js.map +1 -1
  98. package/dist/utils/paths.d.ts +2 -0
  99. package/dist/utils/paths.d.ts.map +1 -1
  100. package/dist/utils/paths.js +16 -0
  101. package/dist/utils/paths.js.map +1 -1
  102. package/dist/utils/syntax-highlight.d.ts +12 -0
  103. package/dist/utils/syntax-highlight.d.ts.map +1 -0
  104. package/dist/utils/syntax-highlight.js +118 -0
  105. package/dist/utils/syntax-highlight.js.map +1 -0
  106. package/dist/utils/tools-manager.d.ts.map +1 -1
  107. package/dist/utils/tools-manager.js +80 -8
  108. package/dist/utils/tools-manager.js.map +1 -1
  109. package/docs/custom-provider.md +58 -3
  110. package/docs/extensions.md +1 -1
  111. package/docs/index.md +7 -1
  112. package/docs/models.md +2 -2
  113. package/docs/providers.md +2 -0
  114. package/docs/sdk.md +24 -44
  115. package/docs/skills.md +3 -4
  116. package/docs/termux.md +3 -3
  117. package/docs/themes.md +2 -2
  118. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  119. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  120. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  121. package/examples/extensions/dynamic-resources/dynamic.json +1 -1
  122. package/examples/extensions/sandbox/package-lock.json +2 -2
  123. package/examples/extensions/sandbox/package.json +1 -1
  124. package/examples/extensions/with-deps/package-lock.json +2 -2
  125. package/examples/extensions/with-deps/package.json +1 -1
  126. package/examples/sdk/01-minimal.ts +14 -10
  127. package/examples/sdk/02-custom-model.ts +12 -8
  128. package/examples/sdk/03-custom-prompt.ts +24 -16
  129. package/examples/sdk/04-skills.ts +2 -2
  130. package/examples/sdk/05-tools.ts +8 -4
  131. package/examples/sdk/06-extensions.ts +11 -7
  132. package/examples/sdk/07-context-files.ts +2 -2
  133. package/examples/sdk/08-prompt-templates.ts +2 -2
  134. package/examples/sdk/09-api-keys-and-oauth.ts +8 -4
  135. package/examples/sdk/10-settings.ts +4 -4
  136. package/examples/sdk/11-sessions.ts +4 -0
  137. package/examples/sdk/12-full-control.ts +11 -7
  138. package/examples/sdk/README.md +5 -8
  139. package/package.json +6 -11
@@ -7,7 +7,7 @@ import * as fs from "node:fs";
7
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
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
10
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
11
11
  import { spawn, spawnSync } from "child_process";
12
12
  import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
13
13
  import { parseSkillBlock } from "../../core/agent-session.js";
@@ -26,6 +26,7 @@ import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/cha
26
26
  import { copyToClipboard } from "../../utils/clipboard.js";
27
27
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
28
28
  import { parseGitUrl } from "../../utils/git.js";
29
+ import { getCwdRelativePath } from "../../utils/paths.js";
29
30
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
30
31
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
31
32
  import { ensureTool } from "../../utils/tools-manager.js";
@@ -46,7 +47,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
46
47
  import { ExtensionInputComponent } from "./components/extension-input.js";
47
48
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
48
49
  import { FooterComponent } from "./components/footer.js";
49
- import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
50
+ import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
50
51
  import { LoginDialogComponent } from "./components/login-dialog.js";
51
52
  import { ModelSelectorComponent } from "./components/model-selector.js";
52
53
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
@@ -388,6 +389,19 @@ export class InteractiveMode {
388
389
  // Both are needed: fd for autocomplete, rg for grep tool and bash commands
389
390
  const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
390
391
  this.fdPath = fdPath;
392
+ if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
393
+ const modelList = this.session.scopedModels
394
+ .map((sm) => {
395
+ const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
396
+ return `${sm.model.id}${thinkingStr}`;
397
+ })
398
+ .join(", ");
399
+ const cycleKeys = this.keybindings.getKeys("app.model.cycleForward");
400
+ const cycleHint = cycleKeys.length > 0
401
+ ? theme.fg("muted", ` (${formatKeyText(cycleKeys.join("/"), { capitalize: true })} to cycle)`)
402
+ : "";
403
+ console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
404
+ }
391
405
  // Add header container as first child
392
406
  this.ui.addChild(this.headerContainer);
393
407
  // Add header with keybindings from config (unless silenced)
@@ -676,13 +690,9 @@ export class InteractiveMode {
676
690
  formatContextPath(p) {
677
691
  const cwd = path.resolve(this.sessionManager.getCwd());
678
692
  const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
679
- const relativePath = path.relative(cwd, absolutePath);
680
- const isInsideCwd = relativePath === "" ||
681
- (!relativePath.startsWith("..") &&
682
- !relativePath.startsWith(`..${path.sep}`) &&
683
- !path.isAbsolute(relativePath));
684
- if (isInsideCwd) {
685
- return relativePath || ".";
693
+ const relativePath = getCwdRelativePath(absolutePath, cwd);
694
+ if (relativePath !== undefined) {
695
+ return relativePath;
686
696
  }
687
697
  return this.formatDisplayPath(absolutePath);
688
698
  }
@@ -1591,7 +1601,7 @@ export class InteractiveMode {
1591
1601
  opts?.signal?.removeEventListener("abort", onAbort);
1592
1602
  this.hideExtensionSelector();
1593
1603
  resolve(undefined);
1594
- }, { tui: this.ui, timeout: opts?.timeout });
1604
+ }, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
1595
1605
  this.editorContainer.clear();
1596
1606
  this.editorContainer.addChild(this.extensionSelector);
1597
1607
  this.ui.setFocus(this.extensionSelector);
@@ -2646,6 +2656,38 @@ export class InteractiveMode {
2646
2656
  // extension cleanup can write restore sequences and re-trigger EIO.
2647
2657
  process.exit(129);
2648
2658
  }
2659
+ /**
2660
+ * Last-resort handler for uncaught exceptions. The TUI puts stdin into raw
2661
+ * mode and hides the cursor; without this handler, an uncaught throw from
2662
+ * anywhere (e.g. an extension's async `ChildProcess.on("exit")` callback)
2663
+ * tears down the process while leaving the terminal in raw mode with no
2664
+ * cursor, requiring `stty sane && reset` to recover.
2665
+ *
2666
+ * Unlike emergencyTerminalExit, the terminal is still alive here, so we
2667
+ * call ui.stop() to restore cooked mode, the cursor, and disable bracketed
2668
+ * paste / Kitty / modifyOtherKeys sequences.
2669
+ */
2670
+ uncaughtCrash(error) {
2671
+ if (this.isShuttingDown) {
2672
+ process.exit(1);
2673
+ }
2674
+ this.isShuttingDown = true;
2675
+ try {
2676
+ this.unregisterSignalHandlers();
2677
+ }
2678
+ catch { }
2679
+ try {
2680
+ killTrackedDetachedChildren();
2681
+ }
2682
+ catch { }
2683
+ try {
2684
+ this.ui.stop();
2685
+ }
2686
+ catch { }
2687
+ console.error("pi exiting due to uncaughtException:");
2688
+ console.error(error);
2689
+ process.exit(1);
2690
+ }
2649
2691
  /**
2650
2692
  * Check if shutdown was requested and perform shutdown if so.
2651
2693
  */
@@ -2681,6 +2723,12 @@ export class InteractiveMode {
2681
2723
  process.stderr.on("error", terminalErrorHandler);
2682
2724
  this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
2683
2725
  this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
2726
+ // Restore the terminal before the process dies on any uncaught throw.
2727
+ // Without this, an unhandled exception from extension code (or anywhere
2728
+ // in pi) leaves the terminal in raw mode with no cursor.
2729
+ const uncaughtExceptionHandler = (error) => this.uncaughtCrash(error);
2730
+ process.prependListener("uncaughtException", uncaughtExceptionHandler);
2731
+ this.signalCleanupHandlers.push(() => process.off("uncaughtException", uncaughtExceptionHandler));
2684
2732
  }
2685
2733
  unregisterSignalHandlers() {
2686
2734
  for (const cleanup of this.signalCleanupHandlers) {
@@ -2882,6 +2930,7 @@ export class InteractiveMode {
2882
2930
  showError(errorMessage) {
2883
2931
  this.chatContainer.addChild(new Spacer(1));
2884
2932
  this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
2933
+ this.chatContainer.addChild(new Spacer(1));
2885
2934
  this.ui.requestRender();
2886
2935
  }
2887
2936
  showWarning(warningMessage) {
@@ -2892,8 +2941,11 @@ export class InteractiveMode {
2892
2941
  showNewVersionNotification(newVersion) {
2893
2942
  const action = theme.fg("accent", `${APP_NAME} update`);
2894
2943
  const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
2895
- const changelogUrl = theme.fg("accent", "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md");
2896
- const changelogLine = theme.fg("muted", "Changelog: ") + changelogUrl;
2944
+ const changelogUrl = "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md";
2945
+ const changelogLink = getCapabilities().hyperlinks
2946
+ ? hyperlink(theme.fg("accent", "open changelog"), changelogUrl)
2947
+ : theme.fg("accent", changelogUrl);
2948
+ const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
2897
2949
  this.chatContainer.addChild(new Spacer(1));
2898
2950
  this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
2899
2951
  this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
@@ -4277,29 +4329,17 @@ export class InteractiveMode {
4277
4329
  this.chatContainer.addChild(new DynamicBorder());
4278
4330
  this.ui.requestRender();
4279
4331
  }
4280
- /**
4281
- * Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
4282
- */
4283
- capitalizeKey(key) {
4284
- return key
4285
- .split("/")
4286
- .map((k) => k
4287
- .split("+")
4288
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
4289
- .join("+"))
4290
- .join("/");
4291
- }
4292
4332
  /**
4293
4333
  * Get capitalized display string for an app keybinding action.
4294
4334
  */
4295
4335
  getAppKeyDisplay(action) {
4296
- return this.capitalizeKey(keyText(action));
4336
+ return keyDisplayText(action);
4297
4337
  }
4298
4338
  /**
4299
4339
  * Get capitalized display string for an editor keybinding action.
4300
4340
  */
4301
4341
  getEditorKeyDisplay(action) {
4302
- return this.capitalizeKey(keyText(action));
4342
+ return keyDisplayText(action);
4303
4343
  }
4304
4344
  handleHotkeysCommand() {
4305
4345
  // Navigation keybindings
@@ -4398,7 +4438,7 @@ export class InteractiveMode {
4398
4438
  `;
4399
4439
  for (const [key, shortcut] of shortcuts) {
4400
4440
  const description = shortcut.description ?? shortcut.extensionPath;
4401
- const keyDisplay = key.replace(/\b\w/g, (c) => c.toUpperCase());
4441
+ const keyDisplay = formatKeyText(key, { capitalize: true });
4402
4442
  hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
4403
4443
  }
4404
4444
  }