@oh-my-pi/pi-coding-agent 15.2.1 → 15.2.3
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.
- package/CHANGELOG.md +34 -1
- package/dist/types/cli/worktree-cli.d.ts +26 -0
- package/dist/types/commands/worktree.d.ts +34 -0
- package/dist/types/config/settings-schema.d.ts +23 -0
- package/dist/types/modes/theme/shimmer.d.ts +15 -7
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/yield-queue.d.ts +24 -0
- package/dist/types/slash-commands/helpers/format.d.ts +1 -1
- package/dist/types/task/worktree.d.ts +0 -1
- package/dist/types/tools/browser/launch.d.ts +2 -0
- package/dist/types/utils/git.d.ts +1 -0
- package/package.json +7 -7
- package/src/autoresearch/storage.ts +14 -2
- package/src/cli/worktree-cli.ts +291 -0
- package/src/cli.ts +1 -0
- package/src/commands/worktree.ts +56 -0
- package/src/config/settings-schema.ts +16 -0
- package/src/discovery/builtin.ts +30 -0
- package/src/modes/components/mcp-add-wizard.ts +4 -3
- package/src/modes/components/settings-selector.ts +23 -10
- package/src/modes/components/welcome.ts +77 -35
- package/src/modes/controllers/mcp-command-controller.ts +4 -3
- package/src/modes/theme/shimmer.ts +161 -30
- package/src/modes/utils/ui-helpers.ts +31 -13
- package/src/prompts/tools/async-result.md +5 -2
- package/src/sdk.ts +95 -21
- package/src/session/agent-session.ts +28 -0
- package/src/session/yield-queue.ts +155 -0
- package/src/slash-commands/helpers/format.ts +4 -1
- package/src/task/worktree.ts +2 -7
- package/src/tools/browser/launch.ts +63 -51
- package/src/tools/gh.ts +35 -32
- package/src/utils/git.ts +4 -0
package/src/tools/gh.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { scheduler } from "node:timers/promises";
|
|
5
5
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getWorktreeDir, hashPath, isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import * as z from "zod/v4";
|
|
9
9
|
import type { Settings } from "../config/settings";
|
|
10
10
|
import githubDescription from "../prompts/tools/github.md" with { type: "text" };
|
|
@@ -859,18 +859,8 @@ function sanitizeRemoteName(value: string): string {
|
|
|
859
859
|
return sanitized.length > 0 ? `fork-${sanitized}` : "fork";
|
|
860
860
|
}
|
|
861
861
|
|
|
862
|
-
/**
|
|
863
|
-
|
|
864
|
-
* Mirrors the legacy session-dir encoding used elsewhere in the project: drop
|
|
865
|
-
* the leading separator, then collapse `/`, `\\`, and `:` to `-`. The result
|
|
866
|
-
* is not strictly injective for pathological inputs (e.g. `/a/b` vs `/a-b`)
|
|
867
|
-
* but matches the rest of the codebase and stays human-readable.
|
|
868
|
-
*/
|
|
869
|
-
function encodeRepoPathForFilesystem(repoPath: string): string {
|
|
870
|
-
const resolved = path.resolve(repoPath);
|
|
871
|
-
const encoded = resolved.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-");
|
|
872
|
-
return encoded || "root";
|
|
873
|
-
}
|
|
862
|
+
/** Maximum disambiguation suffixes we try before giving up on a worktree path. */
|
|
863
|
+
const WORKTREE_PATH_MAX_SUFFIX = 100;
|
|
874
864
|
|
|
875
865
|
function toLocalBranchRef(value: string): string {
|
|
876
866
|
return `refs/heads/${value}`;
|
|
@@ -912,25 +902,38 @@ async function requireCurrentGitHead(cwd: string, signal?: AbortSignal): Promise
|
|
|
912
902
|
return headSha;
|
|
913
903
|
}
|
|
914
904
|
|
|
915
|
-
|
|
916
|
-
|
|
905
|
+
/**
|
|
906
|
+
* Resolve a worktree path that is free of conflicts.
|
|
907
|
+
*
|
|
908
|
+
* Given a `basePath`, return either `basePath` itself or `${basePath}-2`,
|
|
909
|
+
* `${basePath}-3`, … up to {@link WORKTREE_PATH_MAX_SUFFIX} — whichever is the
|
|
910
|
+
* first variant that is **not** registered with git as another worktree and
|
|
911
|
+
* **not** present on disk. The numeric tail salvages two rare cases that
|
|
912
|
+
* would otherwise abort a checkout: stale leftover dirs from an interrupted
|
|
913
|
+
* `git worktree add`, and the (vanishingly unlikely) `hashPath` collision
|
|
914
|
+
* between two repos that happen to produce the same 7-hex digest.
|
|
915
|
+
*/
|
|
916
|
+
async function resolveAvailableWorktreePath(
|
|
917
|
+
basePath: string,
|
|
917
918
|
existingWorktrees: git.GitWorktreeEntry[],
|
|
918
|
-
): Promise<
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
919
|
+
): Promise<string> {
|
|
920
|
+
const registered = new Set(existingWorktrees.map(entry => path.resolve(entry.path)));
|
|
921
|
+
for (let attempt = 0; attempt < WORKTREE_PATH_MAX_SUFFIX; attempt += 1) {
|
|
922
|
+
const candidate = attempt === 0 ? basePath : `${basePath}-${attempt + 1}`;
|
|
923
|
+
const normalized = path.resolve(candidate);
|
|
924
|
+
if (registered.has(normalized)) continue;
|
|
925
|
+
try {
|
|
926
|
+
await fs.stat(normalized);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
if (isEnoent(error)) {
|
|
929
|
+
return candidate;
|
|
930
|
+
}
|
|
931
|
+
throw error;
|
|
931
932
|
}
|
|
932
|
-
throw error;
|
|
933
933
|
}
|
|
934
|
+
throw new ToolError(
|
|
935
|
+
`could not find an unused worktree path under ${basePath} (tried ${WORKTREE_PATH_MAX_SUFFIX} suffixes)`,
|
|
936
|
+
);
|
|
934
937
|
}
|
|
935
938
|
|
|
936
939
|
function selectPrCloneUrl(originUrl: string | undefined, repo: Pick<GhRepoViewData, "url" | "sshUrl">): string {
|
|
@@ -2939,7 +2942,7 @@ async function checkoutPullRequest(
|
|
|
2939
2942
|
const repoRoot = await requireGitRepoRoot(session.cwd, signal);
|
|
2940
2943
|
const primaryRepoRoot = await requirePrimaryGitRepoRoot(repoRoot, signal);
|
|
2941
2944
|
const localBranch = `pr-${prNumber}`;
|
|
2942
|
-
const worktreePath =
|
|
2945
|
+
const worktreePath = getWorktreeDir(`${prNumber}-${hashPath(primaryRepoRoot)}`);
|
|
2943
2946
|
|
|
2944
2947
|
// Every git mutation against `repoRoot` from here on must run under the
|
|
2945
2948
|
// per-repo lock. Worktrees of the same primary repo share `.git/config`,
|
|
@@ -3003,9 +3006,9 @@ async function checkoutPullRequest(
|
|
|
3003
3006
|
signal,
|
|
3004
3007
|
);
|
|
3005
3008
|
|
|
3006
|
-
|
|
3009
|
+
let finalWorktreePath = existingWorktree?.path ?? worktreePath;
|
|
3007
3010
|
if (!existingWorktree) {
|
|
3008
|
-
await
|
|
3011
|
+
finalWorktreePath = await resolveAvailableWorktreePath(worktreePath, existingWorktrees);
|
|
3009
3012
|
await fs.mkdir(path.dirname(finalWorktreePath), { recursive: true });
|
|
3010
3013
|
await git.worktree.add(repoRoot, finalWorktreePath, localBranch, { signal });
|
|
3011
3014
|
}
|
package/src/utils/git.ts
CHANGED
|
@@ -1157,6 +1157,10 @@ export const worktree = {
|
|
|
1157
1157
|
async list(cwd: string, signal?: AbortSignal): Promise<GitWorktreeEntry[]> {
|
|
1158
1158
|
return parseWorktreeList(await runText(cwd, ["worktree", "list", "--porcelain"], { readOnly: true, signal }));
|
|
1159
1159
|
},
|
|
1160
|
+
|
|
1161
|
+
async prune(cwd: string, signal?: AbortSignal): Promise<void> {
|
|
1162
|
+
await runEffect(cwd, ["worktree", "prune"], { signal });
|
|
1163
|
+
},
|
|
1160
1164
|
};
|
|
1161
1165
|
|
|
1162
1166
|
// ════════════════════════════════════════════════════════════════════════════
|