@mariozechner/pi-coding-agent 0.58.4 → 0.60.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 (97) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +14 -11
  3. package/dist/bun/cli.d.ts +3 -0
  4. package/dist/bun/cli.d.ts.map +1 -0
  5. package/dist/bun/cli.js +6 -0
  6. package/dist/bun/cli.js.map +1 -0
  7. package/dist/bun/register-bedrock.d.ts +2 -0
  8. package/dist/bun/register-bedrock.d.ts.map +1 -0
  9. package/dist/bun/register-bedrock.js +85 -0
  10. package/dist/bun/register-bedrock.js.map +1 -0
  11. package/dist/cli/args.d.ts +1 -0
  12. package/dist/cli/args.d.ts.map +1 -1
  13. package/dist/cli/args.js +4 -0
  14. package/dist/cli/args.js.map +1 -1
  15. package/dist/cli/initial-message.d.ts +18 -0
  16. package/dist/cli/initial-message.d.ts.map +1 -0
  17. package/dist/cli/initial-message.js +22 -0
  18. package/dist/cli/initial-message.js.map +1 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +0 -3
  21. package/dist/cli.js.map +1 -1
  22. package/dist/core/agent-session.d.ts +1 -0
  23. package/dist/core/agent-session.d.ts.map +1 -1
  24. package/dist/core/agent-session.js +23 -3
  25. package/dist/core/agent-session.js.map +1 -1
  26. package/dist/core/bash-executor.d.ts +6 -7
  27. package/dist/core/bash-executor.d.ts.map +1 -1
  28. package/dist/core/bash-executor.js +8 -107
  29. package/dist/core/bash-executor.js.map +1 -1
  30. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  31. package/dist/core/compaction/branch-summarization.js +1 -0
  32. package/dist/core/compaction/branch-summarization.js.map +1 -1
  33. package/dist/core/compaction/compaction.d.ts.map +1 -1
  34. package/dist/core/compaction/compaction.js +2 -0
  35. package/dist/core/compaction/compaction.js.map +1 -1
  36. package/dist/core/extensions/runner.d.ts +5 -2
  37. package/dist/core/extensions/runner.d.ts.map +1 -1
  38. package/dist/core/extensions/runner.js +21 -4
  39. package/dist/core/extensions/runner.js.map +1 -1
  40. package/dist/core/extensions/types.d.ts +1 -1
  41. package/dist/core/extensions/types.d.ts.map +1 -1
  42. package/dist/core/extensions/types.js.map +1 -1
  43. package/dist/core/footer-data-provider.d.ts +6 -1
  44. package/dist/core/footer-data-provider.d.ts.map +1 -1
  45. package/dist/core/footer-data-provider.js +83 -37
  46. package/dist/core/footer-data-provider.js.map +1 -1
  47. package/dist/core/keybindings.d.ts +3 -0
  48. package/dist/core/keybindings.d.ts.map +1 -1
  49. package/dist/core/keybindings.js +21 -11
  50. package/dist/core/keybindings.js.map +1 -1
  51. package/dist/core/package-manager.d.ts +15 -1
  52. package/dist/core/package-manager.d.ts.map +1 -1
  53. package/dist/core/package-manager.js +194 -15
  54. package/dist/core/package-manager.js.map +1 -1
  55. package/dist/core/session-manager.d.ts.map +1 -1
  56. package/dist/core/session-manager.js +6 -7
  57. package/dist/core/session-manager.js.map +1 -1
  58. package/dist/core/slash-commands.d.ts.map +1 -1
  59. package/dist/core/slash-commands.js +1 -1
  60. package/dist/core/slash-commands.js.map +1 -1
  61. package/dist/core/system-prompt.d.ts.map +1 -1
  62. package/dist/core/system-prompt.js +3 -2
  63. package/dist/core/system-prompt.js.map +1 -1
  64. package/dist/core/tools/bash.d.ts +8 -0
  65. package/dist/core/tools/bash.d.ts.map +1 -1
  66. package/dist/core/tools/bash.js +75 -69
  67. package/dist/core/tools/bash.js.map +1 -1
  68. package/dist/core/tools/index.d.ts +1 -1
  69. package/dist/core/tools/index.d.ts.map +1 -1
  70. package/dist/core/tools/index.js +1 -1
  71. package/dist/core/tools/index.js.map +1 -1
  72. package/dist/index.d.ts +1 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +1 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/main.d.ts.map +1 -1
  77. package/dist/main.js +50 -19
  78. package/dist/main.js.map +1 -1
  79. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  80. package/dist/modes/interactive/components/tree-selector.js +12 -1
  81. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  82. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/user-message.js +2 -1
  84. package/dist/modes/interactive/components/user-message.js.map +1 -1
  85. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  86. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.js +50 -6
  88. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  89. package/docs/extensions.md +17 -3
  90. package/docs/keybindings.md +2 -0
  91. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  92. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  93. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  94. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  95. package/examples/extensions/with-deps/package-lock.json +2 -2
  96. package/examples/extensions/with-deps/package.json +1 -1
  97. package/package.json +5 -5
@@ -8,6 +8,7 @@ import { minimatch } from "minimatch";
8
8
  import { CONFIG_DIR_NAME } from "../config.js";
9
9
  import { parseGitUrl } from "../utils/git.js";
10
10
  const NETWORK_TIMEOUT_MS = 10000;
11
+ const UPDATE_CHECK_CONCURRENCY = 4;
11
12
  function isOfflineModeEnabled() {
12
13
  const value = process.env.PI_OFFLINE;
13
14
  if (!value)
@@ -708,6 +709,62 @@ export class DefaultPackageManager {
708
709
  return;
709
710
  }
710
711
  }
712
+ async checkForAvailableUpdates() {
713
+ if (isOfflineModeEnabled()) {
714
+ return [];
715
+ }
716
+ const globalSettings = this.settingsManager.getGlobalSettings();
717
+ const projectSettings = this.settingsManager.getProjectSettings();
718
+ const allPackages = [];
719
+ for (const pkg of projectSettings.packages ?? []) {
720
+ allPackages.push({ pkg, scope: "project" });
721
+ }
722
+ for (const pkg of globalSettings.packages ?? []) {
723
+ allPackages.push({ pkg, scope: "user" });
724
+ }
725
+ const packageSources = this.dedupePackages(allPackages);
726
+ const checks = packageSources
727
+ .filter((entry) => entry.scope !== "temporary")
728
+ .map((entry) => async () => {
729
+ const source = typeof entry.pkg === "string" ? entry.pkg : entry.pkg.source;
730
+ const parsed = this.parseSource(source);
731
+ if (parsed.type === "local" || parsed.pinned) {
732
+ return undefined;
733
+ }
734
+ if (parsed.type === "npm") {
735
+ const installedPath = this.getNpmInstallPath(parsed, entry.scope);
736
+ if (!existsSync(installedPath)) {
737
+ return undefined;
738
+ }
739
+ const hasUpdate = await this.npmHasAvailableUpdate(parsed, installedPath);
740
+ if (!hasUpdate) {
741
+ return undefined;
742
+ }
743
+ return {
744
+ source,
745
+ displayName: parsed.name,
746
+ type: "npm",
747
+ scope: entry.scope,
748
+ };
749
+ }
750
+ const installedPath = this.getGitInstallPath(parsed, entry.scope);
751
+ if (!existsSync(installedPath)) {
752
+ return undefined;
753
+ }
754
+ const hasUpdate = await this.gitHasAvailableUpdate(installedPath);
755
+ if (!hasUpdate) {
756
+ return undefined;
757
+ }
758
+ return {
759
+ source,
760
+ displayName: `${parsed.host}/${parsed.path}`,
761
+ type: "git",
762
+ scope: entry.scope,
763
+ };
764
+ });
765
+ const results = await this.runWithConcurrency(checks, UPDATE_CHECK_CONCURRENCY);
766
+ return results.filter((result) => result !== undefined);
767
+ }
711
768
  async resolvePackageSources(sources, accumulator, onMissing) {
712
769
  for (const { pkg, scope } of sources) {
713
770
  const sourceStr = typeof pkg === "string" ? pkg : pkg.source;
@@ -737,7 +794,8 @@ export class DefaultPackageManager {
737
794
  };
738
795
  if (parsed.type === "npm") {
739
796
  const installedPath = this.getNpmInstallPath(parsed, scope);
740
- const needsInstall = !existsSync(installedPath) || (await this.npmNeedsUpdate(parsed, installedPath));
797
+ const needsInstall = !existsSync(installedPath) ||
798
+ (parsed.pinned && !(await this.installedNpmMatchesPinnedVersion(parsed, installedPath)));
741
799
  if (needsInstall) {
742
800
  const installed = await installMissing();
743
801
  if (!installed)
@@ -863,30 +921,30 @@ export class DefaultPackageManager {
863
921
  }
864
922
  return { type: "local", path: source };
865
923
  }
866
- /**
867
- * Check if an npm package needs to be updated.
868
- * - For unpinned packages: check if registry has a newer version
869
- * - For pinned packages: check if installed version matches the pinned version
870
- */
871
- async npmNeedsUpdate(source, installedPath) {
924
+ async installedNpmMatchesPinnedVersion(source, installedPath) {
925
+ const installedVersion = this.getInstalledNpmVersion(installedPath);
926
+ if (!installedVersion) {
927
+ return false;
928
+ }
929
+ const { version: pinnedVersion } = this.parseNpmSpec(source.spec);
930
+ if (!pinnedVersion) {
931
+ return true;
932
+ }
933
+ return installedVersion === pinnedVersion;
934
+ }
935
+ async npmHasAvailableUpdate(source, installedPath) {
872
936
  if (isOfflineModeEnabled()) {
873
937
  return false;
874
938
  }
875
939
  const installedVersion = this.getInstalledNpmVersion(installedPath);
876
- if (!installedVersion)
877
- return true;
878
- const { version: pinnedVersion } = this.parseNpmSpec(source.spec);
879
- if (pinnedVersion) {
880
- // Pinned: check if installed matches pinned (exact match for now)
881
- return installedVersion !== pinnedVersion;
940
+ if (!installedVersion) {
941
+ return false;
882
942
  }
883
- // Unpinned: check registry for latest version
884
943
  try {
885
944
  const latestVersion = await this.getLatestNpmVersion(source.name);
886
945
  return latestVersion !== installedVersion;
887
946
  }
888
947
  catch {
889
- // If we can't check registry, assume it's fine
890
948
  return false;
891
949
  }
892
950
  }
@@ -912,6 +970,84 @@ export class DefaultPackageManager {
912
970
  const data = (await response.json());
913
971
  return data.version;
914
972
  }
973
+ async gitHasAvailableUpdate(installedPath) {
974
+ if (isOfflineModeEnabled()) {
975
+ return false;
976
+ }
977
+ try {
978
+ const localHead = await this.runCommandCapture("git", ["rev-parse", "HEAD"], {
979
+ cwd: installedPath,
980
+ timeoutMs: NETWORK_TIMEOUT_MS,
981
+ });
982
+ const remoteHead = await this.getRemoteGitHead(installedPath);
983
+ return localHead.trim() !== remoteHead.trim();
984
+ }
985
+ catch {
986
+ return false;
987
+ }
988
+ }
989
+ async getRemoteGitHead(installedPath) {
990
+ const upstreamRef = await this.getGitUpstreamRef(installedPath);
991
+ if (upstreamRef) {
992
+ const remoteHead = await this.runGitRemoteCommand(installedPath, ["ls-remote", "origin", upstreamRef]);
993
+ const match = remoteHead.match(/^([0-9a-f]{40})\s+/m);
994
+ if (match?.[1]) {
995
+ return match[1];
996
+ }
997
+ }
998
+ const remoteHead = await this.runGitRemoteCommand(installedPath, ["ls-remote", "origin", "HEAD"]);
999
+ const match = remoteHead.match(/^([0-9a-f]{40})\s+HEAD$/m);
1000
+ if (!match?.[1]) {
1001
+ throw new Error("Failed to determine remote HEAD");
1002
+ }
1003
+ return match[1];
1004
+ }
1005
+ async getGitUpstreamRef(installedPath) {
1006
+ try {
1007
+ const upstream = await this.runCommandCapture("git", ["rev-parse", "--abbrev-ref", "@{upstream}"], {
1008
+ cwd: installedPath,
1009
+ timeoutMs: NETWORK_TIMEOUT_MS,
1010
+ });
1011
+ const trimmed = upstream.trim();
1012
+ if (!trimmed.startsWith("origin/")) {
1013
+ return undefined;
1014
+ }
1015
+ const branch = trimmed.slice("origin/".length);
1016
+ return branch ? `refs/heads/${branch}` : undefined;
1017
+ }
1018
+ catch {
1019
+ return undefined;
1020
+ }
1021
+ }
1022
+ runGitRemoteCommand(installedPath, args) {
1023
+ return this.runCommandCapture("git", args, {
1024
+ cwd: installedPath,
1025
+ timeoutMs: NETWORK_TIMEOUT_MS,
1026
+ env: {
1027
+ GIT_TERMINAL_PROMPT: "0",
1028
+ },
1029
+ });
1030
+ }
1031
+ async runWithConcurrency(tasks, limit) {
1032
+ if (tasks.length === 0) {
1033
+ return [];
1034
+ }
1035
+ const results = new Array(tasks.length);
1036
+ let nextIndex = 0;
1037
+ const workerCount = Math.max(1, Math.min(limit, tasks.length));
1038
+ const worker = async () => {
1039
+ while (true) {
1040
+ const index = nextIndex;
1041
+ nextIndex += 1;
1042
+ if (index >= tasks.length) {
1043
+ return;
1044
+ }
1045
+ results[index] = await tasks[index]();
1046
+ }
1047
+ };
1048
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
1049
+ return results;
1050
+ }
915
1051
  /**
916
1052
  * Get a unique identity for a package, ignoring version/ref.
917
1053
  * Used to detect when the same package is in both global and project settings.
@@ -1447,6 +1583,49 @@ export class DefaultPackageManager {
1447
1583
  themes: toResolved(accumulator.themes),
1448
1584
  };
1449
1585
  }
1586
+ runCommandCapture(command, args, options) {
1587
+ return new Promise((resolvePromise, reject) => {
1588
+ const child = spawn(command, args, {
1589
+ cwd: options?.cwd,
1590
+ stdio: ["ignore", "pipe", "pipe"],
1591
+ shell: process.platform === "win32",
1592
+ env: options?.env ? { ...process.env, ...options.env } : process.env,
1593
+ });
1594
+ let stdout = "";
1595
+ let stderr = "";
1596
+ let timedOut = false;
1597
+ const timeout = typeof options?.timeoutMs === "number"
1598
+ ? setTimeout(() => {
1599
+ timedOut = true;
1600
+ child.kill();
1601
+ }, options.timeoutMs)
1602
+ : undefined;
1603
+ child.stdout?.on("data", (data) => {
1604
+ stdout += data.toString();
1605
+ });
1606
+ child.stderr?.on("data", (data) => {
1607
+ stderr += data.toString();
1608
+ });
1609
+ child.on("error", (error) => {
1610
+ if (timeout)
1611
+ clearTimeout(timeout);
1612
+ reject(error);
1613
+ });
1614
+ child.on("exit", (code) => {
1615
+ if (timeout)
1616
+ clearTimeout(timeout);
1617
+ if (timedOut) {
1618
+ reject(new Error(`${command} ${args.join(" ")} timed out after ${options?.timeoutMs}ms`));
1619
+ return;
1620
+ }
1621
+ if (code === 0) {
1622
+ resolvePromise(stdout.trim());
1623
+ return;
1624
+ }
1625
+ reject(new Error(`${command} ${args.join(" ")} failed with code ${code}: ${stderr || stdout}`));
1626
+ });
1627
+ });
1628
+ }
1450
1629
  runCommand(command, args, options) {
1451
1630
  return new Promise((resolvePromise, reject) => {
1452
1631
  const child = spawn(command, args, {