@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12
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 +53 -0
- package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
- package/dist/cli.js +10775 -10762
- package/dist/types/advisor/runtime.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/custom-models.d.ts +31 -0
- package/dist/types/config/model-config-values.d.ts +19 -0
- package/dist/types/config/model-patch.d.ts +93 -0
- package/dist/types/config/model-provider-discovery.d.ts +51 -0
- package/dist/types/config/model-registry.d.ts +9 -52
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/config/settings.d.ts +5 -3
- package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
- package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +33 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/eval/executor-base.d.ts +8 -2
- package/dist/types/eval/kernel-session-registry.d.ts +60 -0
- package/dist/types/export/share.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/lsp/diagnostics.d.ts +96 -0
- package/dist/types/lsp/index.d.ts +7 -128
- package/dist/types/lsp/servers.d.ts +72 -0
- package/dist/types/lsp/tool.d.ts +42 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
- package/dist/types/lsp/writethrough.d.ts +33 -0
- package/dist/types/mcp/transports/header-policy.d.ts +46 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
- package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
- package/dist/types/modes/components/agent-hub.d.ts +2 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/theme/color.d.ts +19 -0
- package/dist/types/modes/theme/loader.d.ts +19 -0
- package/dist/types/modes/theme/schema.d.ts +175 -0
- package/dist/types/modes/theme/symbols.d.ts +28 -0
- package/dist/types/modes/theme/theme-class.d.ts +244 -0
- package/dist/types/modes/theme/theme.d.ts +9 -280
- package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +1 -3
- package/dist/types/secrets/index.d.ts +26 -2
- package/dist/types/secrets/message-transform.d.ts +40 -0
- package/dist/types/secrets/obfuscator.d.ts +0 -108
- package/dist/types/secrets/placeholder-scan.d.ts +95 -0
- package/dist/types/secrets/placeholder.d.ts +94 -0
- package/dist/types/secrets/replacement.d.ts +82 -0
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session-types.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-handoff.d.ts +3 -3
- package/dist/types/session/session-manager.d.ts +32 -0
- package/dist/types/session/session-provider-boundary.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +8 -0
- package/dist/types/session/turn-recovery.d.ts +9 -4
- package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
- package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
- package/dist/types/slash-commands/builtin-control.d.ts +2 -0
- package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
- package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
- package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/slash-commands/builtin-session.d.ts +2 -0
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/tools/gh-common.d.ts +69 -0
- package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
- package/dist/types/tools/gh-pr-diff.d.ts +102 -0
- package/dist/types/tools/gh-run-watch.d.ts +78 -0
- package/dist/types/tools/gh-search.d.ts +61 -0
- package/dist/types/tools/gh-types.d.ts +340 -0
- package/dist/types/tools/gh-view.d.ts +88 -0
- package/dist/types/tools/gh.d.ts +4 -201
- package/dist/types/tools/read-archive.d.ts +16 -0
- package/dist/types/tools/read-format.d.ts +104 -0
- package/dist/types/tools/read-path-resolution.d.ts +19 -0
- package/dist/types/tools/read-pdf-images.d.ts +12 -0
- package/dist/types/tools/read-renderer.d.ts +24 -0
- package/dist/types/tools/read-selector.d.ts +27 -0
- package/dist/types/tools/read-sqlite.d.ts +16 -0
- package/dist/types/tools/read-summary.d.ts +19 -0
- package/dist/types/tools/read.d.ts +1 -23
- package/dist/types/tools/shell-tokenize.d.ts +18 -1
- package/dist/types/utils/changelog.d.ts +0 -5
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +12 -7
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/share.ts +71 -0
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/custom-models.ts +188 -0
- package/src/config/model-config-values.ts +128 -0
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-patch.ts +252 -0
- package/src/config/model-provider-discovery.ts +132 -0
- package/src/config/model-registry.ts +84 -704
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +53 -3
- package/src/debug/raw-sse-buffer.ts +20 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +75 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/edit/hashline/diff.ts +5 -1
- package/src/eval/executor-base.ts +39 -6
- package/src/eval/jl/executor.ts +82 -371
- package/src/eval/kernel-session-registry.ts +398 -0
- package/src/eval/py/executor.ts +53 -261
- package/src/eval/rb/executor.ts +36 -279
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/export/share.ts +2 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +78 -14
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +12 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/lsp/diagnostics.ts +516 -0
- package/src/lsp/index.ts +20 -2819
- package/src/lsp/servers.ts +296 -0
- package/src/lsp/tool.ts +1352 -0
- package/src/lsp/workspace-diagnostics.ts +170 -0
- package/src/lsp/writethrough.ts +561 -0
- package/src/main.ts +3 -2
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +15 -4
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +2 -2
- package/src/modes/components/agent-hub-renderer.ts +3 -7
- package/src/modes/components/agent-hub.ts +5 -0
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +6 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +8 -2
- package/src/modes/controllers/event-controller.ts +15 -15
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/theme/color.ts +133 -0
- package/src/modes/theme/loader.ts +178 -0
- package/src/modes/theme/schema.ts +263 -0
- package/src/modes/theme/symbols.ts +1000 -0
- package/src/modes/theme/theme-class.ts +611 -0
- package/src/modes/theme/theme.ts +27 -2453
- package/src/modes/theme/tui-adapters.ts +279 -0
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/registry/agent-registry.ts +2 -2
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +51 -6
- package/src/secrets/message-transform.ts +287 -0
- package/src/secrets/obfuscator.ts +39 -1303
- package/src/secrets/placeholder-scan.ts +506 -0
- package/src/secrets/placeholder.ts +309 -0
- package/src/secrets/replacement.ts +216 -0
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +146 -21
- package/src/session/indexed-session-storage.ts +7 -2
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-advisors.ts +32 -34
- package/src/session/session-context.ts +3 -5
- package/src/session/session-handoff.ts +39 -16
- package/src/session/session-manager.ts +67 -3
- package/src/session/session-provider-boundary.ts +3 -6
- package/src/session/session-tools.ts +37 -3
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +115 -84
- package/src/slash-commands/builtin-collaboration.ts +504 -0
- package/src/slash-commands/builtin-completions.ts +291 -0
- package/src/slash-commands/builtin-control.ts +78 -0
- package/src/slash-commands/builtin-lifecycle.ts +506 -0
- package/src/slash-commands/builtin-marketplace.ts +567 -0
- package/src/slash-commands/builtin-modes.ts +518 -0
- package/src/slash-commands/builtin-registry.ts +21 -2996
- package/src/slash-commands/builtin-session.ts +592 -0
- package/src/task/executor.ts +17 -14
- package/src/task/index.ts +21 -6
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +10 -11
- package/src/tools/debug.ts +1 -1
- package/src/tools/gh-common.ts +288 -0
- package/src/tools/gh-pr-checkout.ts +679 -0
- package/src/tools/gh-pr-diff.ts +473 -0
- package/src/tools/gh-run-watch.ts +1015 -0
- package/src/tools/gh-search.ts +500 -0
- package/src/tools/gh-types.ts +379 -0
- package/src/tools/gh-view.ts +612 -0
- package/src/tools/gh.ts +109 -3821
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/read-archive.ts +209 -0
- package/src/tools/read-format.ts +593 -0
- package/src/tools/read-path-resolution.ts +36 -0
- package/src/tools/read-pdf-images.ts +250 -0
- package/src/tools/read-renderer.ts +289 -0
- package/src/tools/read-selector.ts +84 -0
- package/src/tools/read-sqlite.ts +215 -0
- package/src/tools/read-summary.ts +199 -0
- package/src/tools/read.ts +82 -1824
- package/src/tools/shell-tokenize.ts +99 -1
- package/src/utils/changelog.ts +1 -1
- package/src/utils/title-generator.ts +3 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/zai.ts +12 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { resolveContainedPathSync } from "../discovery/contained-path";
|
|
3
4
|
import type { Skill } from "../extensibility/skills";
|
|
4
5
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
5
6
|
import { validateRelativePath } from "../internal-urls/skill-protocol";
|
|
@@ -89,6 +90,20 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
|
|
|
89
90
|
if (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {
|
|
90
91
|
throw new ToolError("Path traversal is not allowed in skill:// URLs");
|
|
91
92
|
}
|
|
93
|
+
// Agent Plugin skills (§4.1): the resource must canonically resolve within
|
|
94
|
+
// the plugin root. Fail closed: a dangling or unresolvable path is rejected
|
|
95
|
+
// rather than handed to bash, where writing through it could create the
|
|
96
|
+
// outside target. Symlinks may target other files inside the same package.
|
|
97
|
+
if (skill.containRoot) {
|
|
98
|
+
const contained = resolveContainedPathSync(skill.containRoot, resolvedPath);
|
|
99
|
+
if (contained.status === "outside") {
|
|
100
|
+
throw new ToolError(`skill:// path resolves outside the plugin root: ${url}`);
|
|
101
|
+
}
|
|
102
|
+
if (contained.status === "missing") {
|
|
103
|
+
throw new ToolError(`skill:// path does not exist: ${url}`);
|
|
104
|
+
}
|
|
105
|
+
return contained.realPath;
|
|
106
|
+
}
|
|
92
107
|
|
|
93
108
|
return resolvedPath;
|
|
94
109
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
previewWindowRows,
|
|
50
50
|
replaceTabs,
|
|
51
51
|
} from "./render-utils";
|
|
52
|
-
import { tokenizeShellSegments } from "./shell-tokenize";
|
|
52
|
+
import { extractLeadingCdTarget, tokenizeShellSegments } from "./shell-tokenize";
|
|
53
53
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
54
54
|
import { toolResult } from "./tool-result";
|
|
55
55
|
import { clampTimeout, TOOL_TIMEOUTS } from "./tool-timeouts";
|
|
@@ -960,17 +960,16 @@ export class BashTool implements AgentTool<typeof bashSchemaBase | typeof bashSc
|
|
|
960
960
|
let command = rawCommand;
|
|
961
961
|
const env = normalizeBashEnv(rawEnv);
|
|
962
962
|
|
|
963
|
-
// Extract leading `cd <path> && ...` into cwd when the model ignores the
|
|
964
|
-
//
|
|
965
|
-
//
|
|
963
|
+
// Extract a leading `cd <path> && ...` into cwd when the model ignores the
|
|
964
|
+
// cwd parameter. The scanner captures only a single path token and defers
|
|
965
|
+
// to the shell for anything else (redirects, extra args, shell expansion),
|
|
966
|
+
// so it never absorbs shell syntax like `cd /tmp 2>/dev/null && ...` into
|
|
967
|
+
// the structured cwd. Constrained to a top-level `&&` on the first line.
|
|
966
968
|
if (!cwd) {
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
if (cdMatch && !/[$`(]/.test(cdMatch[1])) {
|
|
972
|
-
cwd = cdMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
973
|
-
command = command.slice(cdMatch[0].length);
|
|
969
|
+
const cd = extractLeadingCdTarget(command);
|
|
970
|
+
if (cd) {
|
|
971
|
+
cwd = cd.path;
|
|
972
|
+
command = cd.rest;
|
|
974
973
|
}
|
|
975
974
|
}
|
|
976
975
|
if (asyncRequested && !this.#asyncEnabled) {
|
package/src/tools/debug.ts
CHANGED
|
@@ -505,7 +505,7 @@ const ADAPTER_UNAVAILABLE_MESSAGES: Readonly<Record<string, string>> = {
|
|
|
505
505
|
dlv: "adapter 'dlv' is not available: install with 'go install github.com/go-delve/delve/cmd/dlv@latest'",
|
|
506
506
|
rdbg: "adapter 'rdbg' is not available: install with 'gem install debug'",
|
|
507
507
|
"js-debug-adapter":
|
|
508
|
-
"adapter 'js-debug-adapter' is not available:
|
|
508
|
+
"adapter 'js-debug-adapter' is not available: download it from https://github.com/microsoft/vscode-js-debug",
|
|
509
509
|
};
|
|
510
510
|
|
|
511
511
|
const ADAPTER_CANONICAL_COMMANDS: Readonly<Record<string, string>> = {
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
3
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import * as git from "../utils/git";
|
|
5
|
+
import type { ToolSession } from ".";
|
|
6
|
+
import type { GhToolDetails } from "./gh";
|
|
7
|
+
import type { GhLabel, GhUser } from "./gh-types";
|
|
8
|
+
import { ToolError } from "./tool-errors";
|
|
9
|
+
import { toolResult } from "./tool-result";
|
|
10
|
+
|
|
11
|
+
export function normalizeText(value: string | null | undefined): string {
|
|
12
|
+
return (value ?? "").replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll("\t", " ").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeBlock(value: string | null | undefined): string {
|
|
16
|
+
return (value ?? "").replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll("\t", " ").trimEnd();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function looksLikeGitHubUrl(value: string | undefined): boolean {
|
|
20
|
+
return value?.startsWith("https://github.com/") ?? false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeOptionalString(value: string | null | undefined): string | undefined {
|
|
24
|
+
const normalized = value?.trim();
|
|
25
|
+
return normalized ? normalized : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function normalizePrIdentifierList(value: string | string[] | undefined): string[] {
|
|
29
|
+
if (value === undefined) return [];
|
|
30
|
+
const raw = typeof value === "string" ? [value] : value;
|
|
31
|
+
const cleaned: string[] = [];
|
|
32
|
+
for (const entry of raw) {
|
|
33
|
+
const trimmed = entry?.trim();
|
|
34
|
+
if (trimmed) cleaned.push(trimmed);
|
|
35
|
+
}
|
|
36
|
+
return cleaned;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function requireNonEmpty(value: string | null | undefined, label: string): string {
|
|
40
|
+
const normalized = normalizeOptionalString(value);
|
|
41
|
+
if (!normalized) {
|
|
42
|
+
throw new ToolError(`${label} must not be empty`);
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function appendRepoFlag(args: string[], repo: string | undefined, identifier?: string): void {
|
|
48
|
+
if (!repo || looksLikeGitHubUrl(identifier)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
args.push("--repo", repo);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const REPO_API_URL_PREFIX = "https://api.github.com/repos/";
|
|
56
|
+
|
|
57
|
+
export const PR_URL_PATTERN = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)(?:\/.*)?$/;
|
|
58
|
+
export const ISSUE_URL_PATTERN = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)(?:\/.*)?$/;
|
|
59
|
+
|
|
60
|
+
export async function requireCurrentGitBranch(cwd: string, signal?: AbortSignal): Promise<string> {
|
|
61
|
+
const branch = await git.branch.current(cwd, signal);
|
|
62
|
+
if (!branch) {
|
|
63
|
+
throw new ToolError("Current git branch is unavailable. Pass `branch` or `run` explicitly.");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return branch;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function requireCurrentGitHead(cwd: string, signal?: AbortSignal): Promise<string> {
|
|
70
|
+
const headSha = await git.head.sha(cwd, signal);
|
|
71
|
+
if (!headSha) {
|
|
72
|
+
throw new ToolError("Current git HEAD is unavailable. Pass `run` explicitly.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return headSha;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function formatAuthor(author: GhUser | null | undefined): string | undefined {
|
|
79
|
+
if (!author) return undefined;
|
|
80
|
+
if (author.login) return `@${author.login}`;
|
|
81
|
+
if (author.name) return author.name;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function formatLabels(labels: GhLabel[] | undefined): string | undefined {
|
|
86
|
+
const names = labels?.map(label => label.name).filter((value): value is string => Boolean(value)) ?? [];
|
|
87
|
+
if (names.length === 0) return undefined;
|
|
88
|
+
return names.join(", ");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function pushLine(lines: string[], label: string, value: string | number | boolean | undefined): void {
|
|
92
|
+
if (value === undefined || value === "") return;
|
|
93
|
+
lines.push(`${label}: ${value}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parsePullRequestUrl(value: string | undefined): { repo?: string; prNumber?: number } {
|
|
97
|
+
const normalized = normalizeOptionalString(value);
|
|
98
|
+
if (!normalized) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const match = normalized.match(PR_URL_PATTERN);
|
|
103
|
+
if (!match) {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
repo: match[1],
|
|
109
|
+
prNumber: Number(match[2]),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parse a digit-only decimal positive integer or return undefined. Rejects
|
|
115
|
+
* `1e2`, `0x10`, `12.0`, leading +/-, or any other shape `Number()` would
|
|
116
|
+
* accept — those would otherwise key the cache against the wrong row.
|
|
117
|
+
*/
|
|
118
|
+
export function parsePositiveDecimalInt(value: string | undefined): number | undefined {
|
|
119
|
+
if (!value || !/^\d+$/.test(value)) return undefined;
|
|
120
|
+
const num = Number(value);
|
|
121
|
+
if (!Number.isSafeInteger(num) || num <= 0) return undefined;
|
|
122
|
+
return num;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function parseIssueUrl(value: string | undefined): { repo?: string; issueNumber?: number } {
|
|
126
|
+
const normalized = normalizeOptionalString(value);
|
|
127
|
+
if (!normalized) return {};
|
|
128
|
+
const match = normalized.match(ISSUE_URL_PATTERN);
|
|
129
|
+
if (!match) return {};
|
|
130
|
+
return {
|
|
131
|
+
repo: match[1],
|
|
132
|
+
issueNumber: Number(match[2]),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function githubRepoSlugEquals(left: string | undefined, right: string): boolean {
|
|
137
|
+
if (left === undefined || left.length !== right.length) return false;
|
|
138
|
+
for (let idx = 0; idx < left.length; idx += 1) {
|
|
139
|
+
let leftCode = left.charCodeAt(idx);
|
|
140
|
+
let rightCode = right.charCodeAt(idx);
|
|
141
|
+
if (leftCode >= 65 && leftCode <= 90) leftCode += 32;
|
|
142
|
+
if (rightCode >= 65 && rightCode <= 90) rightCode += 32;
|
|
143
|
+
if (leftCode !== rightCode) return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function resolveGitHubRepo(
|
|
149
|
+
cwd: string,
|
|
150
|
+
repo: string | undefined,
|
|
151
|
+
runRepo: string | undefined,
|
|
152
|
+
signal?: AbortSignal,
|
|
153
|
+
): Promise<string> {
|
|
154
|
+
if (repo && runRepo && !githubRepoSlugEquals(repo, runRepo)) {
|
|
155
|
+
throw new ToolError("run URL repository does not match the provided repo");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (repo) {
|
|
159
|
+
return repo;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (runRepo) {
|
|
163
|
+
return runRepo;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const resolved = await git.github.text(
|
|
167
|
+
cwd,
|
|
168
|
+
["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
|
|
169
|
+
signal,
|
|
170
|
+
);
|
|
171
|
+
return requireNonEmpty(resolved, "repo");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Process-lifetime cache of `gh repo view --json nameWithOwner` lookups keyed
|
|
176
|
+
* by absolute cwd. Avoids repeated `gh` chatter when the same protocol handler
|
|
177
|
+
* or tool call resolves the default repo many times in a row.
|
|
178
|
+
*
|
|
179
|
+
* The shared lookup is intentionally **not** bound to any caller's
|
|
180
|
+
* AbortSignal. Cancelling one caller would otherwise kill the underlying
|
|
181
|
+
* `gh repo view` for every concurrent waiter on the same cwd. Each caller's
|
|
182
|
+
* signal is honored at the wait point via `untilAborted` instead, so an abort
|
|
183
|
+
* unwinds only that caller.
|
|
184
|
+
*/
|
|
185
|
+
export const DEFAULT_REPO_RESOLVED = new Map<string, string>();
|
|
186
|
+
export const DEFAULT_REPO_INFLIGHT = new Map<string, Promise<string>>();
|
|
187
|
+
|
|
188
|
+
export async function resolveDefaultRepoMemoized(cwd: string, signal?: AbortSignal): Promise<string> {
|
|
189
|
+
const key = path.resolve(cwd);
|
|
190
|
+
const ready = DEFAULT_REPO_RESOLVED.get(key);
|
|
191
|
+
if (ready) return ready;
|
|
192
|
+
let pending = DEFAULT_REPO_INFLIGHT.get(key);
|
|
193
|
+
if (!pending) {
|
|
194
|
+
pending = (async () => {
|
|
195
|
+
// No caller signal: this lookup is shared across every concurrent
|
|
196
|
+
// waiter on the same cwd.
|
|
197
|
+
const resolved = await git.github.text(cwd, [
|
|
198
|
+
"repo",
|
|
199
|
+
"view",
|
|
200
|
+
"--json",
|
|
201
|
+
"nameWithOwner",
|
|
202
|
+
"-q",
|
|
203
|
+
".nameWithOwner",
|
|
204
|
+
]);
|
|
205
|
+
const value = requireNonEmpty(resolved, "repo");
|
|
206
|
+
DEFAULT_REPO_RESOLVED.set(key, value);
|
|
207
|
+
return value;
|
|
208
|
+
})();
|
|
209
|
+
// Drop the in-flight slot on settle so failures don't poison the cache
|
|
210
|
+
// and so a successful resolution survives only in `DEFAULT_REPO_RESOLVED`.
|
|
211
|
+
void pending.then(
|
|
212
|
+
() => DEFAULT_REPO_INFLIGHT.delete(key),
|
|
213
|
+
() => DEFAULT_REPO_INFLIGHT.delete(key),
|
|
214
|
+
);
|
|
215
|
+
DEFAULT_REPO_INFLIGHT.set(key, pending);
|
|
216
|
+
}
|
|
217
|
+
return untilAborted(signal, pending);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Best-effort cached cwd → `owner/repo` resolution that swallows any failure
|
|
222
|
+
* (not a git checkout, no GitHub remote, `gh` unauthenticated, …) into
|
|
223
|
+
* `undefined`. Use where the cwd repo is a convenience fallback, not a safety
|
|
224
|
+
* check.
|
|
225
|
+
*/
|
|
226
|
+
export async function tryResolveCurrentRepo(cwd: string, signal: AbortSignal | undefined): Promise<string | undefined> {
|
|
227
|
+
try {
|
|
228
|
+
return await resolveDefaultRepoMemoized(cwd, signal);
|
|
229
|
+
} catch {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Best-effort fresh cwd → `owner/repo` resolution for safety checks that must
|
|
236
|
+
* reflect the repository currently mounted at `cwd`, not the process-lifetime
|
|
237
|
+
* default-repo cache.
|
|
238
|
+
*/
|
|
239
|
+
export async function tryResolveCurrentRepoFresh(
|
|
240
|
+
cwd: string,
|
|
241
|
+
signal: AbortSignal | undefined,
|
|
242
|
+
): Promise<string | undefined> {
|
|
243
|
+
try {
|
|
244
|
+
return await resolveGitHubRepo(cwd, undefined, undefined, signal);
|
|
245
|
+
} catch {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function saveArtifactText(
|
|
251
|
+
session: ToolSession,
|
|
252
|
+
toolType: string,
|
|
253
|
+
text: string,
|
|
254
|
+
): Promise<string | undefined> {
|
|
255
|
+
const { path: artifactPath, id: artifactId } = (await session.allocateOutputArtifact?.(toolType)) ?? {};
|
|
256
|
+
if (!artifactPath || !artifactId) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await Bun.write(artifactPath, text);
|
|
261
|
+
return artifactId;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function appendArtifactReference(text: string, artifactId: string | undefined, label: string): string {
|
|
265
|
+
if (!artifactId) {
|
|
266
|
+
return text;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return `${text}\n\n${label}: artifact://${artifactId}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function buildTextResult(
|
|
273
|
+
text: string,
|
|
274
|
+
sourceUrl?: string,
|
|
275
|
+
details?: GhToolDetails,
|
|
276
|
+
options?: { artifactId?: string; artifactLabel?: string; useless?: boolean },
|
|
277
|
+
): AgentToolResult<GhToolDetails> {
|
|
278
|
+
const builder = toolResult<GhToolDetails>(details).text(
|
|
279
|
+
appendArtifactReference(text, options?.artifactId, options?.artifactLabel ?? "Saved artifact"),
|
|
280
|
+
);
|
|
281
|
+
if (sourceUrl) {
|
|
282
|
+
builder.sourceUrl(sourceUrl);
|
|
283
|
+
}
|
|
284
|
+
if (options?.useless) {
|
|
285
|
+
builder.useless();
|
|
286
|
+
}
|
|
287
|
+
return builder.done();
|
|
288
|
+
}
|