@oh-my-pi/pi-coding-agent 3.33.0 → 3.35.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 (72) hide show
  1. package/CHANGELOG.md +57 -8
  2. package/docs/custom-tools.md +1 -1
  3. package/docs/extensions.md +4 -4
  4. package/docs/hooks.md +2 -2
  5. package/docs/sdk.md +4 -8
  6. package/examples/custom-tools/README.md +2 -2
  7. package/examples/extensions/README.md +1 -1
  8. package/examples/extensions/todo.ts +1 -1
  9. package/examples/hooks/custom-compaction.ts +4 -2
  10. package/examples/hooks/handoff.ts +1 -1
  11. package/examples/hooks/qna.ts +1 -1
  12. package/examples/sdk/02-custom-model.ts +1 -1
  13. package/examples/sdk/README.md +1 -1
  14. package/package.json +5 -5
  15. package/src/capability/ssh.ts +42 -0
  16. package/src/cli/file-processor.ts +1 -1
  17. package/src/cli/list-models.ts +1 -1
  18. package/src/core/agent-session.ts +214 -31
  19. package/src/core/auth-storage.ts +1 -1
  20. package/src/core/compaction/branch-summarization.ts +2 -2
  21. package/src/core/compaction/compaction.ts +2 -2
  22. package/src/core/compaction/utils.ts +1 -1
  23. package/src/core/custom-tools/types.ts +1 -1
  24. package/src/core/extensions/runner.ts +1 -1
  25. package/src/core/extensions/types.ts +1 -1
  26. package/src/core/extensions/wrapper.ts +1 -1
  27. package/src/core/hooks/runner.ts +2 -2
  28. package/src/core/hooks/types.ts +1 -1
  29. package/src/core/index.ts +11 -0
  30. package/src/core/messages.ts +1 -1
  31. package/src/core/model-registry.ts +1 -1
  32. package/src/core/model-resolver.ts +7 -6
  33. package/src/core/sdk.ts +33 -4
  34. package/src/core/session-manager.ts +16 -1
  35. package/src/core/settings-manager.ts +20 -6
  36. package/src/core/ssh/connection-manager.ts +466 -0
  37. package/src/core/ssh/ssh-executor.ts +190 -0
  38. package/src/core/ssh/sshfs-mount.ts +162 -0
  39. package/src/core/ssh-executor.ts +5 -0
  40. package/src/core/system-prompt.ts +424 -1
  41. package/src/core/title-generator.ts +2 -2
  42. package/src/core/tools/edit.ts +1 -0
  43. package/src/core/tools/grep.ts +1 -1
  44. package/src/core/tools/index.test.ts +1 -0
  45. package/src/core/tools/index.ts +5 -0
  46. package/src/core/tools/output.ts +1 -1
  47. package/src/core/tools/read.ts +24 -11
  48. package/src/core/tools/renderers.ts +3 -0
  49. package/src/core/tools/ssh.ts +302 -0
  50. package/src/core/tools/task/index.ts +11 -2
  51. package/src/core/tools/task/model-resolver.ts +5 -4
  52. package/src/core/tools/task/types.ts +1 -1
  53. package/src/core/tools/task/worker.ts +1 -1
  54. package/src/core/voice.ts +1 -1
  55. package/src/discovery/index.ts +3 -0
  56. package/src/discovery/ssh.ts +162 -0
  57. package/src/main.ts +4 -1
  58. package/src/modes/interactive/components/assistant-message.ts +1 -1
  59. package/src/modes/interactive/components/custom-message.ts +1 -1
  60. package/src/modes/interactive/components/footer.ts +1 -1
  61. package/src/modes/interactive/components/hook-message.ts +1 -1
  62. package/src/modes/interactive/components/model-selector.ts +1 -1
  63. package/src/modes/interactive/components/oauth-selector.ts +1 -1
  64. package/src/modes/interactive/components/status-line.ts +1 -1
  65. package/src/modes/interactive/components/tool-execution.ts +15 -12
  66. package/src/modes/interactive/interactive-mode.ts +43 -9
  67. package/src/modes/print-mode.ts +1 -1
  68. package/src/modes/rpc/rpc-client.ts +1 -1
  69. package/src/modes/rpc/rpc-types.ts +1 -1
  70. package/src/prompts/system-prompt.md +4 -0
  71. package/src/prompts/tools/ssh.md +74 -0
  72. package/src/utils/image-resize.ts +1 -1
@@ -6,8 +6,8 @@
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@mariozechner/pi-ai";
10
9
  import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
10
+ import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@oh-my-pi/pi-ai";
11
11
  import type { SlashCommand } from "@oh-my-pi/pi-tui";
12
12
  import {
13
13
  CombinedAutocompleteProvider,
@@ -38,6 +38,7 @@ import { VoiceSupervisor } from "../../core/voice-supervisor";
38
38
  import { disableProvider, enableProvider } from "../../discovery";
39
39
  import { getChangelogPath, parseChangelog } from "../../utils/changelog";
40
40
  import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
41
+ import { resizeImage } from "../../utils/image-resize";
41
42
  import { registerAsyncCleanup } from "../cleanup";
42
43
  import { ArminComponent } from "./components/armin";
43
44
  import { AssistantMessageComponent } from "./components/assistant-message";
@@ -1139,7 +1140,9 @@ export class InteractiveMode {
1139
1140
  if (this.session.isStreaming) {
1140
1141
  this.editor.addToHistory(text);
1141
1142
  this.editor.setText("");
1142
- await this.session.prompt(text, { streamingBehavior: "steer" });
1143
+ const images = this.pendingImages.length > 0 ? [...this.pendingImages] : undefined;
1144
+ this.pendingImages = [];
1145
+ await this.session.prompt(text, { streamingBehavior: "steer", images });
1143
1146
  this.updatePendingMessagesDisplay();
1144
1147
  this.ui.requestRender();
1145
1148
  return;
@@ -1504,22 +1507,24 @@ export class InteractiveMode {
1504
1507
  * If multiple status messages are emitted back-to-back (without anything else being added to the chat),
1505
1508
  * we update the previous status line instead of appending new ones to avoid log spam.
1506
1509
  */
1507
- private showStatus(message: string): void {
1510
+ private showStatus(message: string, options?: { dim?: boolean }): void {
1508
1511
  if (this.isBackgrounded) {
1509
1512
  return;
1510
1513
  }
1511
1514
  const children = this.chatContainer.children;
1512
1515
  const last = children.length > 0 ? children[children.length - 1] : undefined;
1513
1516
  const secondLast = children.length > 1 ? children[children.length - 2] : undefined;
1517
+ const useDim = options?.dim ?? true;
1518
+ const rendered = useDim ? theme.fg("dim", message) : message;
1514
1519
 
1515
1520
  if (last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {
1516
- this.lastStatusText.setText(theme.fg("dim", message));
1521
+ this.lastStatusText.setText(rendered);
1517
1522
  this.ui.requestRender();
1518
1523
  return;
1519
1524
  }
1520
1525
 
1521
1526
  const spacer = new Spacer(1);
1522
- const text = new Text(theme.fg("dim", message), 1, 0);
1527
+ const text = new Text(rendered, 1, 0);
1523
1528
  this.chatContainer.addChild(spacer);
1524
1529
  this.chatContainer.addChild(text);
1525
1530
  this.lastStatusSpacer = spacer;
@@ -1822,10 +1827,24 @@ export class InteractiveMode {
1822
1827
  try {
1823
1828
  const image = await readImageFromClipboard();
1824
1829
  if (image) {
1830
+ let imageData = image;
1831
+ if (this.settingsManager.getImageAutoResize()) {
1832
+ try {
1833
+ const resized = await resizeImage({
1834
+ type: "image",
1835
+ data: image.data,
1836
+ mimeType: image.mimeType,
1837
+ });
1838
+ imageData = { data: resized.data, mimeType: resized.mimeType };
1839
+ } catch {
1840
+ imageData = image;
1841
+ }
1842
+ }
1843
+
1825
1844
  this.pendingImages.push({
1826
1845
  type: "image",
1827
- data: image.data,
1828
- mimeType: image.mimeType,
1846
+ data: imageData.data,
1847
+ mimeType: imageData.mimeType,
1829
1848
  });
1830
1849
  // Insert styled placeholder at cursor like Claude does
1831
1850
  const imageNum = this.pendingImages.length;
@@ -1980,7 +1999,8 @@ export class InteractiveMode {
1980
1999
 
1981
2000
  private async cycleRoleModel(options?: { temporary?: boolean }): Promise<void> {
1982
2001
  try {
1983
- const result = await this.session.cycleRoleModels(["slow", "default", "smol"], options);
2002
+ const roleOrder = ["slow", "default", "smol"];
2003
+ const result = await this.session.cycleRoleModels(roleOrder, options);
1984
2004
  if (!result) {
1985
2005
  this.showStatus("Only one role model available");
1986
2006
  return;
@@ -1989,10 +2009,24 @@ export class InteractiveMode {
1989
2009
  this.statusLine.invalidate();
1990
2010
  this.updateEditorBorderColor();
1991
2011
  const roleLabel = result.role === "default" ? "default" : result.role;
2012
+ const roleLabelStyled = theme.bold(theme.fg("accent", roleLabel));
1992
2013
  const thinkingStr =
1993
2014
  result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
1994
2015
  const tempLabel = options?.temporary ? " (temporary)" : "";
1995
- this.showStatus(`Switched to ${roleLabel}: ${result.model.name || result.model.id}${thinkingStr}${tempLabel}`);
2016
+ const cycleSeparator = theme.fg("dim", " > ");
2017
+ const cycleLabel = roleOrder
2018
+ .map((role) => {
2019
+ if (role === result.role) {
2020
+ return theme.bold(theme.fg("accent", role));
2021
+ }
2022
+ return theme.fg("muted", role);
2023
+ })
2024
+ .join(cycleSeparator);
2025
+ const orderLabel = ` (cycle: ${cycleLabel})`;
2026
+ this.showStatus(
2027
+ `Switched to ${roleLabelStyled}: ${result.model.name || result.model.id}${thinkingStr}${tempLabel}${orderLabel}`,
2028
+ { dim: false },
2029
+ );
1996
2030
  } catch (error) {
1997
2031
  this.showError(error instanceof Error ? error.message : String(error));
1998
2032
  }
@@ -6,7 +6,7 @@
6
6
  * - `omp --mode json "prompt"` - JSON event stream
7
7
  */
8
8
 
9
- import type { AssistantMessage, ImageContent } from "@mariozechner/pi-ai";
9
+ import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
10
10
  import type { AgentSession } from "../core/agent-session";
11
11
 
12
12
  /**
@@ -4,8 +4,8 @@
4
4
  * Spawns the agent in RPC mode and provides a typed API for all operations.
5
5
  */
6
6
 
7
- import type { ImageContent } from "@mariozechner/pi-ai";
8
7
  import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
9
9
  import type { Subprocess } from "bun";
10
10
  import type { SessionStats } from "../../core/agent-session";
11
11
  import type { BashResult } from "../../core/bash-executor";
@@ -5,8 +5,8 @@
5
5
  * Responses and events are emitted as JSON lines on stdout.
6
6
  */
7
7
 
8
- import type { ImageContent, Model } from "@mariozechner/pi-ai";
9
8
  import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
9
+ import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
10
10
  import type { SessionStats } from "../../core/agent-session";
11
11
  import type { BashResult } from "../../core/bash-executor";
12
12
  import type { CompactionResult } from "../../core/compaction/index";
@@ -21,6 +21,10 @@ Core behavior:
21
21
  - After each tool result, check relevance; iterate or clarify if results conflict or are insufficient.
22
22
  - Use concise, scannable responses; include file paths in backticks; use short bullets for multi-item lists; avoid dumping large files.
23
23
 
24
+ <environment>
25
+ {{environmentInfo}}
26
+ </environment>
27
+
24
28
  Documentation:
25
29
  - Main documentation: {{readmePath}}
26
30
  - Additional docs: {{docsPath}}
@@ -0,0 +1,74 @@
1
+ Execute commands on remote SSH hosts.
2
+
3
+ ## Critical: Match Commands to Host Shell
4
+
5
+ Each host runs a specific shell. **You MUST use commands native to that shell.**
6
+
7
+ ### Command Reference
8
+
9
+ **linux/bash, linux/zsh, macos/bash, macos/zsh** — Unix-like systems:
10
+ - Files: `ls`, `cat`, `head`, `tail`, `grep`, `find`
11
+ - System: `ps`, `top`, `df`, `uname`, `free` (Linux), `df`, `uname`, `top` (macOS)
12
+ - Navigation: `cd`, `pwd`
13
+
14
+ **windows/bash, windows/sh** — Windows with Unix compatibility layer (WSL, Cygwin, Git Bash):
15
+ - Files: `ls`, `cat`, `head`, `tail`, `grep`, `find`
16
+ - System: `ps`, `top`, `df`, `uname`
17
+ - Navigation: `cd`, `pwd`
18
+ - Note: These are Windows hosts but use Unix commands
19
+
20
+ **windows/powershell** — Native Windows PowerShell:
21
+ - Files: `Get-ChildItem`, `Get-Content`, `Select-String`
22
+ - System: `Get-Process`, `Get-ComputerInfo`
23
+ - Navigation: `Set-Location`, `Get-Location`
24
+
25
+ **windows/cmd** — Native Windows Command Prompt:
26
+ - Files: `dir`, `type`, `findstr`, `where`
27
+ - System: `tasklist`, `systeminfo`
28
+ - Navigation: `cd`, `echo %CD%`
29
+
30
+ ## Execution Pattern
31
+
32
+ 1. Check the host's shell type from "Available hosts" below
33
+ 2. Use ONLY commands for that shell type
34
+ 3. Construct your command using the reference above
35
+
36
+ ## Examples
37
+
38
+ <example>
39
+ Task: List files in /home/user on host "server1"
40
+ Host: server1 (10.0.0.1) | linux/bash
41
+ Command: `ls -la /home/user`
42
+ </example>
43
+
44
+ <example>
45
+ Task: Show running processes on host "winbox"
46
+ Host: winbox (192.168.1.5) | windows/cmd
47
+ Command: `tasklist /v`
48
+ </example>
49
+
50
+ <example>
51
+ Task: Check disk usage on host "wsl-dev"
52
+ Host: wsl-dev (192.168.1.10) | windows/bash
53
+ Command: `df -h`
54
+ Note: Windows host with WSL — use Unix commands
55
+ </example>
56
+
57
+ <example>
58
+ Task: Get system info on host "macbook"
59
+ Host: macbook (10.0.0.20) | macos/zsh
60
+ Command: `uname -a && sw_vers`
61
+ </example>
62
+
63
+ ## Parameters
64
+
65
+ - **host**: Host name from "Available hosts" below
66
+ - **command**: Command to execute (see Command Reference above)
67
+ - **cwd**: Working directory (optional)
68
+ - **timeout**: Timeout in seconds (optional)
69
+
70
+ ## Output
71
+
72
+ Truncated at 50KB. Exit codes captured.
73
+
74
+ **Before executing: verify host shell type below and use matching commands.**
@@ -1,4 +1,4 @@
1
- import type { ImageContent } from "@mariozechner/pi-ai";
1
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { getImageDimensionsWithImageMagick, resizeWithImageMagick } from "./image-magick.js";
3
3
 
4
4
  export interface ImageResizeOptions {