@mariozechner/pi-coding-agent 0.13.2 → 0.14.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 (49) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +62 -0
  3. package/dist/compaction.d.ts.map +1 -1
  4. package/dist/compaction.js +5 -2
  5. package/dist/compaction.js.map +1 -1
  6. package/dist/export-html.d.ts.map +1 -1
  7. package/dist/export-html.js +52 -25
  8. package/dist/export-html.js.map +1 -1
  9. package/dist/main.d.ts.map +1 -1
  10. package/dist/main.js +120 -9
  11. package/dist/main.js.map +1 -1
  12. package/dist/messages.d.ts +43 -0
  13. package/dist/messages.d.ts.map +1 -0
  14. package/dist/messages.js +71 -0
  15. package/dist/messages.js.map +1 -0
  16. package/dist/model-config.d.ts.map +1 -1
  17. package/dist/model-config.js +9 -0
  18. package/dist/model-config.js.map +1 -1
  19. package/dist/settings-manager.d.ts +6 -3
  20. package/dist/settings-manager.d.ts.map +1 -1
  21. package/dist/settings-manager.js +7 -0
  22. package/dist/settings-manager.js.map +1 -1
  23. package/dist/shell.d.ts +16 -0
  24. package/dist/shell.d.ts.map +1 -0
  25. package/dist/shell.js +108 -0
  26. package/dist/shell.js.map +1 -0
  27. package/dist/theme/dark.json +4 -1
  28. package/dist/theme/light.json +4 -1
  29. package/dist/theme/theme-schema.json +28 -0
  30. package/dist/theme/theme.d.ts +3 -2
  31. package/dist/theme/theme.d.ts.map +1 -1
  32. package/dist/theme/theme.js +32 -3
  33. package/dist/theme/theme.js.map +1 -1
  34. package/dist/tools/bash.d.ts.map +1 -1
  35. package/dist/tools/bash.js +3 -107
  36. package/dist/tools/bash.js.map +1 -1
  37. package/dist/tui/bash-execution.d.ts +33 -0
  38. package/dist/tui/bash-execution.d.ts.map +1 -0
  39. package/dist/tui/bash-execution.js +134 -0
  40. package/dist/tui/bash-execution.js.map +1 -0
  41. package/dist/tui/tui-renderer.d.ts +7 -1
  42. package/dist/tui/tui-renderer.d.ts.map +1 -1
  43. package/dist/tui/tui-renderer.js +225 -10
  44. package/dist/tui/tui-renderer.js.map +1 -1
  45. package/package.json +4 -4
  46. package/dist/fuzzy.test.d.ts +0 -2
  47. package/dist/fuzzy.test.d.ts.map +0 -1
  48. package/dist/fuzzy.test.js +0 -76
  49. package/dist/fuzzy.test.js.map +0 -1
package/dist/main.js CHANGED
@@ -1,19 +1,25 @@
1
1
  import { Agent, ProviderTransport } from "@mariozechner/pi-agent-core";
2
2
  import { ProcessTerminal, TUI } from "@mariozechner/pi-tui";
3
3
  import chalk from "chalk";
4
- import { existsSync, readFileSync, statSync } from "fs";
5
- import { homedir } from "os";
4
+ import { spawn } from "child_process";
5
+ import { randomBytes } from "crypto";
6
+ import { createWriteStream, existsSync, readFileSync, statSync } from "fs";
7
+ import { homedir, tmpdir } from "os";
6
8
  import { extname, join, resolve } from "path";
9
+ import stripAnsi from "strip-ansi";
7
10
  import { getChangelogPath, getNewEntries, parseChangelog } from "./changelog.js";
8
11
  import { calculateContextTokens, compact, shouldCompact } from "./compaction.js";
9
12
  import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, getAgentDir, getModelsPath, getReadmePath, VERSION, } from "./config.js";
10
13
  import { exportFromFile } from "./export-html.js";
14
+ import { messageTransformer } from "./messages.js";
11
15
  import { findModel, getApiKeyForModel, getAvailableModels } from "./model-config.js";
12
16
  import { loadSessionFromEntries, SessionManager } from "./session-manager.js";
13
17
  import { SettingsManager } from "./settings-manager.js";
18
+ import { getShellConfig } from "./shell.js";
14
19
  import { expandSlashCommand, loadSlashCommands } from "./slash-commands.js";
15
20
  import { initTheme } from "./theme/theme.js";
16
21
  import { allTools, codingTools } from "./tools/index.js";
22
+ import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
17
23
  import { ensureTool } from "./tools-manager.js";
18
24
  import { SessionSelectorComponent } from "./tui/session-selector.js";
19
25
  import { TuiRenderer } from "./tui/tui-renderer.js";
@@ -88,11 +94,16 @@ function parseArgs(args) {
88
94
  }
89
95
  else if (arg === "--thinking" && i + 1 < args.length) {
90
96
  const level = args[++i];
91
- if (level === "off" || level === "minimal" || level === "low" || level === "medium" || level === "high") {
97
+ if (level === "off" ||
98
+ level === "minimal" ||
99
+ level === "low" ||
100
+ level === "medium" ||
101
+ level === "high" ||
102
+ level === "xhigh") {
92
103
  result.thinking = level;
93
104
  }
94
105
  else {
95
- console.error(chalk.yellow(`Warning: Invalid thinking level "${level}". Valid values: off, minimal, low, medium, high`));
106
+ console.error(chalk.yellow(`Warning: Invalid thinking level "${level}". Valid values: off, minimal, low, medium, high, xhigh`));
96
107
  }
97
108
  }
98
109
  else if (arg === "--print" || arg === "-p") {
@@ -212,7 +223,7 @@ ${chalk.bold("Options:")}
212
223
  --models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P
213
224
  --tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
214
225
  Available: read, bash, edit, write, grep, find, ls
215
- --thinking <level> Set thinking level: off, minimal, low, medium, high
226
+ --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
216
227
  --export <file> Export session file to HTML and exit
217
228
  --help, -h Show this help
218
229
 
@@ -509,7 +520,12 @@ async function resolveModelScope(patterns) {
509
520
  let thinkingLevel = "off";
510
521
  if (parts.length > 1) {
511
522
  const level = parts[1];
512
- if (level === "off" || level === "minimal" || level === "low" || level === "medium" || level === "high") {
523
+ if (level === "off" ||
524
+ level === "minimal" ||
525
+ level === "low" ||
526
+ level === "medium" ||
527
+ level === "high" ||
528
+ level === "xhigh") {
513
529
  thinkingLevel = level;
514
530
  }
515
531
  else {
@@ -599,8 +615,8 @@ async function selectSession(sessionManager) {
599
615
  ui.start();
600
616
  });
601
617
  }
602
- async function runInteractiveMode(agent, sessionManager, settingsManager, version, changelogMarkdown = null, modelFallbackMessage = null, versionCheckPromise, scopedModels = [], initialMessages = [], initialMessage, initialAttachments, fdPath = null) {
603
- const renderer = new TuiRenderer(agent, sessionManager, settingsManager, version, changelogMarkdown, scopedModels, fdPath);
618
+ async function runInteractiveMode(agent, sessionManager, settingsManager, version, changelogMarkdown = null, collapseChangelog = false, modelFallbackMessage = null, versionCheckPromise, scopedModels = [], initialMessages = [], initialMessage, initialAttachments, fdPath = null) {
619
+ const renderer = new TuiRenderer(agent, sessionManager, settingsManager, version, changelogMarkdown, collapseChangelog, scopedModels, fdPath);
604
620
  // Initialize TUI (subscribes to agent events internally)
605
621
  await renderer.init();
606
622
  // Handle version check result when it completes (don't block)
@@ -687,6 +703,70 @@ async function runSingleShotMode(agent, _sessionManager, messages, mode, initial
687
703
  }
688
704
  }
689
705
  }
706
+ /**
707
+ * Execute a bash command for RPC mode.
708
+ * Similar to tui-renderer's executeBashCommand but without streaming callbacks.
709
+ */
710
+ async function executeRpcBashCommand(command) {
711
+ return new Promise((resolve, reject) => {
712
+ const { shell, args } = getShellConfig();
713
+ const child = spawn(shell, [...args, command], {
714
+ detached: true,
715
+ stdio: ["ignore", "pipe", "pipe"],
716
+ });
717
+ const chunks = [];
718
+ let chunksBytes = 0;
719
+ const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
720
+ let tempFilePath;
721
+ let tempFileStream;
722
+ let totalBytes = 0;
723
+ const handleData = (data) => {
724
+ totalBytes += data.length;
725
+ // Start writing to temp file if exceeds threshold
726
+ if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
727
+ const id = randomBytes(8).toString("hex");
728
+ tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
729
+ tempFileStream = createWriteStream(tempFilePath);
730
+ for (const chunk of chunks) {
731
+ tempFileStream.write(chunk);
732
+ }
733
+ }
734
+ if (tempFileStream) {
735
+ tempFileStream.write(data);
736
+ }
737
+ // Keep rolling buffer
738
+ chunks.push(data);
739
+ chunksBytes += data.length;
740
+ while (chunksBytes > maxChunksBytes && chunks.length > 1) {
741
+ const removed = chunks.shift();
742
+ chunksBytes -= removed.length;
743
+ }
744
+ };
745
+ child.stdout?.on("data", handleData);
746
+ child.stderr?.on("data", handleData);
747
+ child.on("close", (code) => {
748
+ if (tempFileStream) {
749
+ tempFileStream.end();
750
+ }
751
+ // Combine buffered chunks
752
+ const fullBuffer = Buffer.concat(chunks);
753
+ const fullOutput = stripAnsi(fullBuffer.toString("utf-8")).replace(/\r/g, "");
754
+ const truncationResult = truncateTail(fullOutput);
755
+ resolve({
756
+ output: fullOutput,
757
+ exitCode: code,
758
+ truncationResult: truncationResult.truncated ? truncationResult : undefined,
759
+ fullOutputPath: tempFilePath,
760
+ });
761
+ });
762
+ child.on("error", (err) => {
763
+ if (tempFileStream) {
764
+ tempFileStream.end();
765
+ }
766
+ reject(err);
767
+ });
768
+ });
769
+ }
690
770
  async function runRpcMode(agent, sessionManager, settingsManager) {
691
771
  // Track if auto-compaction is in progress
692
772
  let autoCompactionInProgress = false;
@@ -796,6 +876,35 @@ async function runRpcMode(agent, sessionManager, settingsManager) {
796
876
  console.log(JSON.stringify({ type: "error", error: `Compaction failed: ${error.message}` }));
797
877
  }
798
878
  }
879
+ else if (input.type === "bash" && input.command) {
880
+ // Execute bash command and add to context
881
+ try {
882
+ const result = await executeRpcBashCommand(input.command);
883
+ // Create bash execution message
884
+ const bashMessage = {
885
+ role: "bashExecution",
886
+ command: input.command,
887
+ output: result.truncationResult?.content || result.output,
888
+ exitCode: result.exitCode,
889
+ cancelled: false,
890
+ truncated: result.truncationResult?.truncated || false,
891
+ fullOutputPath: result.fullOutputPath,
892
+ timestamp: Date.now(),
893
+ };
894
+ // Add to agent state and save to session
895
+ agent.appendMessage(bashMessage);
896
+ sessionManager.saveMessage(bashMessage);
897
+ // Initialize session if needed (same logic as message_end handler)
898
+ if (sessionManager.shouldInitializeSession(agent.state.messages)) {
899
+ sessionManager.startSession(agent.state);
900
+ }
901
+ // Emit bash_end event with the message
902
+ console.log(JSON.stringify({ type: "bash_end", message: bashMessage }));
903
+ }
904
+ catch (error) {
905
+ console.log(JSON.stringify({ type: "error", error: `Bash command failed: ${error.message}` }));
906
+ }
907
+ }
799
908
  }
800
909
  catch (error) {
801
910
  // Output error as JSON
@@ -1049,6 +1158,7 @@ export async function main(args) {
1049
1158
  thinkingLevel: initialThinking,
1050
1159
  tools: selectedTools,
1051
1160
  },
1161
+ messageTransformer,
1052
1162
  queueMode: settingsManager.getQueueMode(),
1053
1163
  transport: new ProviderTransport({
1054
1164
  // Dynamic API key lookup based on current model's provider
@@ -1163,7 +1273,8 @@ export async function main(args) {
1163
1273
  // Ensure fd tool is available for file autocomplete
1164
1274
  const fdPath = await ensureTool("fd");
1165
1275
  // Interactive mode - use TUI (may have initial messages from CLI args)
1166
- await runInteractiveMode(agent, sessionManager, settingsManager, VERSION, changelogMarkdown, modelFallbackMessage, versionCheckPromise, scopedModels, parsed.messages, initialMessage, initialAttachments, fdPath);
1276
+ const collapseChangelog = settingsManager.getCollapseChangelog();
1277
+ await runInteractiveMode(agent, sessionManager, settingsManager, VERSION, changelogMarkdown, collapseChangelog, modelFallbackMessage, versionCheckPromise, scopedModels, parsed.messages, initialMessage, initialAttachments, fdPath);
1167
1278
  }
1168
1279
  else {
1169
1280
  // Non-interactive mode (--print flag or --mode flag)