@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
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import * as git from "../utils/git";
|
|
3
|
+
import type { ToolSession } from ".";
|
|
4
|
+
import type { GhToolDetails } from "./gh";
|
|
5
|
+
import {
|
|
6
|
+
buildTextResult,
|
|
7
|
+
formatAuthor,
|
|
8
|
+
formatLabels,
|
|
9
|
+
normalizeOptionalString,
|
|
10
|
+
normalizeText,
|
|
11
|
+
pushLine,
|
|
12
|
+
REPO_API_URL_PREFIX,
|
|
13
|
+
requireNonEmpty,
|
|
14
|
+
tryResolveCurrentRepo,
|
|
15
|
+
} from "./gh-common";
|
|
16
|
+
import { formatShortSha } from "./gh-format";
|
|
17
|
+
import type {
|
|
18
|
+
GhApiLabel,
|
|
19
|
+
GhApiSearchCodeItem,
|
|
20
|
+
GhApiSearchCommitItem,
|
|
21
|
+
GhApiSearchIssueItem,
|
|
22
|
+
GhApiSearchRepoItem,
|
|
23
|
+
GhApiSearchResponse,
|
|
24
|
+
GhApiUser,
|
|
25
|
+
GhLabel,
|
|
26
|
+
GhSearchCodeResult,
|
|
27
|
+
GhSearchCommitResult,
|
|
28
|
+
GhSearchRepoResult,
|
|
29
|
+
GhSearchResult,
|
|
30
|
+
GhUser,
|
|
31
|
+
GithubInput,
|
|
32
|
+
} from "./gh-types";
|
|
33
|
+
import { ToolError } from "./tool-errors";
|
|
34
|
+
|
|
35
|
+
export const SEARCH_LIMIT_DEFAULT = 10;
|
|
36
|
+
export const SEARCH_LIMIT_MAX = 50;
|
|
37
|
+
export const FILE_PREVIEW_LIMIT = 50;
|
|
38
|
+
|
|
39
|
+
export function resolveSearchLimit(value: number | undefined): number {
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
return SEARCH_LIMIT_DEFAULT;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
45
|
+
throw new ToolError("limit must be a positive number");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Math.min(Math.floor(value), SEARCH_LIMIT_MAX);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const RELATIVE_DURATION_PATTERN = /^(\d+)\s*(m|h|d|w|mo|y)$/i;
|
|
52
|
+
export const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
53
|
+
export const FIXED_UNIT_MS: Record<string, number> = {
|
|
54
|
+
m: 60_000,
|
|
55
|
+
h: 3_600_000,
|
|
56
|
+
d: 86_400_000,
|
|
57
|
+
w: 7 * 86_400_000,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a search date bound to a GitHub-search-compatible literal. Returns
|
|
62
|
+
* either a `YYYY-MM-DD` date (relative durations and date-only inputs) or a
|
|
63
|
+
* full ISO 8601 datetime string (datetime inputs), so the caller can drop it
|
|
64
|
+
* straight into a qualifier like `created:>=<value>`.
|
|
65
|
+
*/
|
|
66
|
+
export function parseSearchDateBound(raw: string, now: Date = new Date()): string {
|
|
67
|
+
const trimmed = raw.trim();
|
|
68
|
+
if (!trimmed) {
|
|
69
|
+
throw new ToolError("date bound must not be empty");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const relMatch = trimmed.match(RELATIVE_DURATION_PATTERN);
|
|
73
|
+
if (relMatch) {
|
|
74
|
+
const count = Number(relMatch[1]);
|
|
75
|
+
const unit = relMatch[2].toLowerCase();
|
|
76
|
+
const fixedMs = FIXED_UNIT_MS[unit];
|
|
77
|
+
let bound: Date;
|
|
78
|
+
if (fixedMs !== undefined) {
|
|
79
|
+
bound = new Date(now.getTime() - count * fixedMs);
|
|
80
|
+
} else {
|
|
81
|
+
bound = new Date(now);
|
|
82
|
+
if (unit === "mo") {
|
|
83
|
+
bound.setUTCMonth(bound.getUTCMonth() - count);
|
|
84
|
+
} else {
|
|
85
|
+
bound.setUTCFullYear(bound.getUTCFullYear() - count);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return bound.toISOString().slice(0, 10);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (ISO_DATE_PATTERN.test(trimmed)) {
|
|
92
|
+
return trimmed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const parsedMs = Date.parse(trimmed);
|
|
96
|
+
if (!Number.isNaN(parsedMs)) {
|
|
97
|
+
// GitHub search qualifiers accept seconds precision only
|
|
98
|
+
// (`YYYY-MM-DDTHH:MM:SSZ`); strip the milliseconds toISOString emits.
|
|
99
|
+
return new Date(parsedMs).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
throw new ToolError(
|
|
103
|
+
`invalid date bound: ${raw}. Expected a relative duration like "3d", "12h", "2w", an ISO date "YYYY-MM-DD", or an ISO datetime.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build the GitHub-search qualifier (e.g. `created:>=2026-05-09`) for the
|
|
109
|
+
* provided bounds, or `undefined` if neither bound is set.
|
|
110
|
+
*/
|
|
111
|
+
export function buildSearchDateQualifier(
|
|
112
|
+
field: string,
|
|
113
|
+
since: string | undefined,
|
|
114
|
+
until: string | undefined,
|
|
115
|
+
now?: Date,
|
|
116
|
+
): string | undefined {
|
|
117
|
+
const sinceVal = since ? parseSearchDateBound(since, now) : undefined;
|
|
118
|
+
const untilVal = until ? parseSearchDateBound(until, now) : undefined;
|
|
119
|
+
if (sinceVal && untilVal) {
|
|
120
|
+
return `${field}:${sinceVal}..${untilVal}`;
|
|
121
|
+
}
|
|
122
|
+
if (sinceVal) {
|
|
123
|
+
return `${field}:>=${sinceVal}`;
|
|
124
|
+
}
|
|
125
|
+
if (untilVal) {
|
|
126
|
+
return `${field}:<=${untilVal}`;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveSearchDateField(
|
|
132
|
+
command: "issues" | "prs" | "commits" | "repos",
|
|
133
|
+
requested: "created" | "updated" | undefined,
|
|
134
|
+
): string {
|
|
135
|
+
if (command === "commits") {
|
|
136
|
+
return "committer-date";
|
|
137
|
+
}
|
|
138
|
+
const dateField = requested ?? "created";
|
|
139
|
+
if (command === "repos" && dateField === "updated") {
|
|
140
|
+
return "pushed";
|
|
141
|
+
}
|
|
142
|
+
return dateField;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function composeSearchQuery(parts: ReadonlyArray<string | undefined>): string {
|
|
146
|
+
const cleaned: string[] = [];
|
|
147
|
+
for (const part of parts) {
|
|
148
|
+
const trimmed = part?.trim();
|
|
149
|
+
if (trimmed) cleaned.push(trimmed);
|
|
150
|
+
}
|
|
151
|
+
if (cleaned.length === 0) {
|
|
152
|
+
throw new ToolError("query is required (or pass since/until to filter by date)");
|
|
153
|
+
}
|
|
154
|
+
return cleaned.join(" ");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildGhApiSearchArgs(
|
|
158
|
+
endpoint: "issues" | "code" | "commits" | "repositories",
|
|
159
|
+
query: string,
|
|
160
|
+
limit: number,
|
|
161
|
+
extraHeaders?: ReadonlyArray<string>,
|
|
162
|
+
): string[] {
|
|
163
|
+
const args = ["api", "-X", "GET", `/search/${endpoint}`, "-f", `q=${query}`, "-F", `per_page=${limit}`];
|
|
164
|
+
for (const header of extraHeaders ?? []) {
|
|
165
|
+
args.push("-H", header);
|
|
166
|
+
}
|
|
167
|
+
return args;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function repoFromRepositoryUrl(value: string | undefined): string | undefined {
|
|
171
|
+
if (!value?.startsWith(REPO_API_URL_PREFIX)) return undefined;
|
|
172
|
+
return value.slice(REPO_API_URL_PREFIX.length);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function apiUserToGhUser(user: GhApiUser | null | undefined): GhUser | undefined {
|
|
176
|
+
if (!user) return undefined;
|
|
177
|
+
const login = user.login ?? undefined;
|
|
178
|
+
const name = user.name ?? undefined;
|
|
179
|
+
if (login === undefined && name === undefined) return undefined;
|
|
180
|
+
return { login, name };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function apiLabelsToGhLabels(labels: GhApiLabel[] | undefined): GhLabel[] {
|
|
184
|
+
return labels?.map(label => ({ name: label.name })) ?? [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function apiIssueToSearchResult(item: GhApiSearchIssueItem): GhSearchResult {
|
|
188
|
+
const merged = Boolean(item.pull_request?.merged_at);
|
|
189
|
+
return {
|
|
190
|
+
author: apiUserToGhUser(item.user) ?? null,
|
|
191
|
+
createdAt: item.created_at,
|
|
192
|
+
labels: apiLabelsToGhLabels(item.labels),
|
|
193
|
+
number: item.number,
|
|
194
|
+
repository: { nameWithOwner: repoFromRepositoryUrl(item.repository_url) },
|
|
195
|
+
state: merged ? "merged" : item.state,
|
|
196
|
+
title: item.title,
|
|
197
|
+
updatedAt: item.updated_at,
|
|
198
|
+
url: item.html_url,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function apiCodeToSearchResult(item: GhApiSearchCodeItem): GhSearchCodeResult {
|
|
203
|
+
return {
|
|
204
|
+
path: item.path,
|
|
205
|
+
repository: { nameWithOwner: item.repository?.full_name },
|
|
206
|
+
sha: item.sha,
|
|
207
|
+
textMatches: item.text_matches?.map(match => ({ fragment: match.fragment, property: match.property })),
|
|
208
|
+
url: item.html_url,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function apiCommitToSearchResult(item: GhApiSearchCommitItem): GhSearchCommitResult {
|
|
213
|
+
return {
|
|
214
|
+
author: apiUserToGhUser(item.author) ?? null,
|
|
215
|
+
commit: item.commit
|
|
216
|
+
? {
|
|
217
|
+
author: item.commit.author ?? null,
|
|
218
|
+
committer: item.commit.committer ?? null,
|
|
219
|
+
message: item.commit.message,
|
|
220
|
+
}
|
|
221
|
+
: null,
|
|
222
|
+
committer: apiUserToGhUser(item.committer) ?? null,
|
|
223
|
+
id: item.node_id,
|
|
224
|
+
repository: { nameWithOwner: item.repository?.full_name },
|
|
225
|
+
sha: item.sha,
|
|
226
|
+
url: item.html_url,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function apiRepoToSearchResult(item: GhApiSearchRepoItem): GhSearchRepoResult {
|
|
231
|
+
return {
|
|
232
|
+
createdAt: item.created_at,
|
|
233
|
+
description: item.description,
|
|
234
|
+
forksCount: item.forks_count,
|
|
235
|
+
fullName: item.full_name,
|
|
236
|
+
isArchived: item.archived,
|
|
237
|
+
isFork: item.fork,
|
|
238
|
+
isPrivate: item.private,
|
|
239
|
+
language: item.language,
|
|
240
|
+
openIssuesCount: item.open_issues_count,
|
|
241
|
+
owner: apiUserToGhUser(item.owner) ?? null,
|
|
242
|
+
stargazersCount: item.stargazers_count,
|
|
243
|
+
updatedAt: item.updated_at,
|
|
244
|
+
url: item.html_url,
|
|
245
|
+
visibility: item.visibility ?? null,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Matches search-query qualifiers that already scope to a repository, org, or
|
|
251
|
+
* user. When present, callers should avoid layering a default `repo:<current>`
|
|
252
|
+
* on top — the user has already expressed an explicit scope.
|
|
253
|
+
*
|
|
254
|
+
* Only the leading `repo:`/`org:`/`user:`/`owner:` token is treated as a
|
|
255
|
+
* scope marker; arbitrary substrings (e.g. inside quoted text) are ignored.
|
|
256
|
+
*/
|
|
257
|
+
export const REPO_SCOPE_QUALIFIER_PATTERN = /(?:^|\s)-?(?:repo|org|user|owner):\S/i;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Resolve the effective `repo:` scope for a search op. Returns the explicit
|
|
261
|
+
* `repo` when set, `undefined` when the query already carries a scoping
|
|
262
|
+
* qualifier, and otherwise the current checkout's `owner/repo` via
|
|
263
|
+
* `resolveDefaultRepoMemoized`. Resolution failures (no git/gh context, no
|
|
264
|
+
* configured remote) silently fall back to `undefined` so the search proceeds
|
|
265
|
+
* across all of GitHub instead of throwing.
|
|
266
|
+
*/
|
|
267
|
+
export async function resolveSearchRepoScope(
|
|
268
|
+
cwd: string,
|
|
269
|
+
repo: string | undefined,
|
|
270
|
+
query: string | undefined,
|
|
271
|
+
signal: AbortSignal | undefined,
|
|
272
|
+
): Promise<string | undefined> {
|
|
273
|
+
if (repo) return repo;
|
|
274
|
+
if (query && REPO_SCOPE_QUALIFIER_PATTERN.test(query)) return undefined;
|
|
275
|
+
return tryResolveCurrentRepo(cwd, signal);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function formatSearchResults(
|
|
279
|
+
kind: "issues" | "pull requests",
|
|
280
|
+
query: string,
|
|
281
|
+
repo: string | undefined,
|
|
282
|
+
items: GhSearchResult[],
|
|
283
|
+
): string {
|
|
284
|
+
const lines: string[] = [`# GitHub ${kind} search`, "", `Query: ${query}`];
|
|
285
|
+
pushLine(lines, "Repository", repo);
|
|
286
|
+
pushLine(lines, "Results", items.length);
|
|
287
|
+
|
|
288
|
+
if (items.length === 0) {
|
|
289
|
+
lines.push("");
|
|
290
|
+
lines.push(`No ${kind} found.`);
|
|
291
|
+
return lines.join("\n").trim();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const item of items) {
|
|
295
|
+
lines.push("");
|
|
296
|
+
lines.push(`- #${item.number ?? "?"} ${item.title ?? "Untitled"}`);
|
|
297
|
+
pushLine(lines, " Repo", item.repository?.nameWithOwner);
|
|
298
|
+
pushLine(lines, " State", item.state);
|
|
299
|
+
pushLine(lines, " Author", formatAuthor(item.author));
|
|
300
|
+
pushLine(lines, " Labels", formatLabels(item.labels));
|
|
301
|
+
pushLine(lines, " Created", item.createdAt);
|
|
302
|
+
pushLine(lines, " Updated", item.updatedAt);
|
|
303
|
+
pushLine(lines, " URL", item.url);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return lines.join("\n").trim();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function formatSearchCodeResults(query: string, repo: string | undefined, items: GhSearchCodeResult[]): string {
|
|
310
|
+
const lines: string[] = [`# GitHub code search`, "", `Query: ${query}`];
|
|
311
|
+
pushLine(lines, "Repository", repo);
|
|
312
|
+
pushLine(lines, "Results", items.length);
|
|
313
|
+
|
|
314
|
+
if (items.length === 0) {
|
|
315
|
+
lines.push("");
|
|
316
|
+
lines.push("No code matches found.");
|
|
317
|
+
return lines.join("\n").trim();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const item of items) {
|
|
321
|
+
lines.push("");
|
|
322
|
+
lines.push(`- ${item.path ?? "(unknown path)"}`);
|
|
323
|
+
pushLine(lines, " Repo", item.repository?.nameWithOwner);
|
|
324
|
+
pushLine(lines, " Commit", formatShortSha(item.sha));
|
|
325
|
+
pushLine(lines, " URL", item.url);
|
|
326
|
+
const fragment = item.textMatches?.find(match => match.fragment)?.fragment;
|
|
327
|
+
if (fragment) {
|
|
328
|
+
pushLine(lines, " Match", normalizeText(fragment).split("\n", 1)[0]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return lines.join("\n").trim();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function formatSearchCommitMessage(message: string | undefined): string | undefined {
|
|
336
|
+
if (!message) return undefined;
|
|
337
|
+
const firstLine = normalizeText(message).split("\n", 1)[0];
|
|
338
|
+
return firstLine || undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function formatSearchCommitsResults(
|
|
342
|
+
query: string,
|
|
343
|
+
repo: string | undefined,
|
|
344
|
+
items: GhSearchCommitResult[],
|
|
345
|
+
): string {
|
|
346
|
+
const lines: string[] = [`# GitHub commits search`, "", `Query: ${query}`];
|
|
347
|
+
pushLine(lines, "Repository", repo);
|
|
348
|
+
pushLine(lines, "Results", items.length);
|
|
349
|
+
|
|
350
|
+
if (items.length === 0) {
|
|
351
|
+
lines.push("");
|
|
352
|
+
lines.push("No commits found.");
|
|
353
|
+
return lines.join("\n").trim();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
for (const item of items) {
|
|
357
|
+
lines.push("");
|
|
358
|
+
const sha = formatShortSha(item.sha) ?? "(unknown sha)";
|
|
359
|
+
const subject = formatSearchCommitMessage(item.commit?.message) ?? "(no commit message)";
|
|
360
|
+
lines.push(`- ${sha} ${subject}`);
|
|
361
|
+
pushLine(lines, " Repo", item.repository?.nameWithOwner);
|
|
362
|
+
pushLine(lines, " Author", formatAuthor(item.author) ?? item.commit?.author?.name);
|
|
363
|
+
pushLine(lines, " Date", item.commit?.author?.date ?? item.commit?.committer?.date);
|
|
364
|
+
pushLine(lines, " URL", item.url);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return lines.join("\n").trim();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function formatSearchReposResults(query: string, items: GhSearchRepoResult[]): string {
|
|
371
|
+
const lines: string[] = [`# GitHub repositories search`, "", `Query: ${query}`];
|
|
372
|
+
pushLine(lines, "Results", items.length);
|
|
373
|
+
|
|
374
|
+
if (items.length === 0) {
|
|
375
|
+
lines.push("");
|
|
376
|
+
lines.push("No repositories found.");
|
|
377
|
+
return lines.join("\n").trim();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const item of items) {
|
|
381
|
+
lines.push("");
|
|
382
|
+
lines.push(`- ${item.fullName ?? "(unknown repository)"}`);
|
|
383
|
+
const description = normalizeText(item.description).split("\n", 1)[0];
|
|
384
|
+
if (description) {
|
|
385
|
+
pushLine(lines, " Description", description);
|
|
386
|
+
}
|
|
387
|
+
pushLine(lines, " Language", item.language ?? undefined);
|
|
388
|
+
pushLine(lines, " Stars", item.stargazersCount);
|
|
389
|
+
pushLine(lines, " Forks", item.forksCount);
|
|
390
|
+
pushLine(lines, " Open issues", item.openIssuesCount);
|
|
391
|
+
pushLine(lines, " Visibility", item.visibility ?? undefined);
|
|
392
|
+
pushLine(lines, " Archived", item.isArchived);
|
|
393
|
+
pushLine(lines, " Fork", item.isFork);
|
|
394
|
+
pushLine(lines, " Updated", item.updatedAt);
|
|
395
|
+
pushLine(lines, " URL", item.url);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return lines.join("\n").trim();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export async function executeSearchIssues(
|
|
402
|
+
session: ToolSession,
|
|
403
|
+
params: GithubInput,
|
|
404
|
+
signal: AbortSignal | undefined,
|
|
405
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
406
|
+
const limit = resolveSearchLimit(params.limit);
|
|
407
|
+
const dateField = resolveSearchDateField("issues", params.dateField);
|
|
408
|
+
const dateQualifier = buildSearchDateQualifier(dateField, params.since, params.until);
|
|
409
|
+
const displayQuery = composeSearchQuery([params.query, dateQualifier]);
|
|
410
|
+
const repo = await resolveSearchRepoScope(session.cwd, normalizeOptionalString(params.repo), displayQuery, signal);
|
|
411
|
+
const apiQuery = composeSearchQuery([displayQuery, repo ? `repo:${repo}` : undefined, "is:issue"]);
|
|
412
|
+
const args = buildGhApiSearchArgs("issues", apiQuery, limit);
|
|
413
|
+
|
|
414
|
+
const response = await git.github.json<GhApiSearchResponse<GhApiSearchIssueItem>>(session.cwd, args, signal);
|
|
415
|
+
const items = (response.items ?? []).map(apiIssueToSearchResult);
|
|
416
|
+
return buildTextResult(formatSearchResults("issues", displayQuery, repo, items), undefined, undefined, {
|
|
417
|
+
useless: items.length === 0,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export async function executeSearchPrs(
|
|
422
|
+
session: ToolSession,
|
|
423
|
+
params: GithubInput,
|
|
424
|
+
signal: AbortSignal | undefined,
|
|
425
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
426
|
+
const limit = resolveSearchLimit(params.limit);
|
|
427
|
+
const dateField = resolveSearchDateField("prs", params.dateField);
|
|
428
|
+
const dateQualifier = buildSearchDateQualifier(dateField, params.since, params.until);
|
|
429
|
+
const displayQuery = composeSearchQuery([params.query, dateQualifier]);
|
|
430
|
+
const repo = await resolveSearchRepoScope(session.cwd, normalizeOptionalString(params.repo), displayQuery, signal);
|
|
431
|
+
const apiQuery = composeSearchQuery([displayQuery, repo ? `repo:${repo}` : undefined, "is:pr"]);
|
|
432
|
+
const args = buildGhApiSearchArgs("issues", apiQuery, limit);
|
|
433
|
+
|
|
434
|
+
const response = await git.github.json<GhApiSearchResponse<GhApiSearchIssueItem>>(session.cwd, args, signal);
|
|
435
|
+
const items = (response.items ?? []).map(apiIssueToSearchResult);
|
|
436
|
+
return buildTextResult(formatSearchResults("pull requests", displayQuery, repo, items), undefined, undefined, {
|
|
437
|
+
useless: items.length === 0,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export async function executeSearchCode(
|
|
442
|
+
session: ToolSession,
|
|
443
|
+
params: GithubInput,
|
|
444
|
+
signal: AbortSignal | undefined,
|
|
445
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
446
|
+
const query = requireNonEmpty(params.query, "query");
|
|
447
|
+
const since = normalizeOptionalString(params.since);
|
|
448
|
+
const until = normalizeOptionalString(params.until);
|
|
449
|
+
if (since !== undefined || until !== undefined) {
|
|
450
|
+
throw new ToolError("search_code does not support since/until; GitHub code search has no date qualifier.");
|
|
451
|
+
}
|
|
452
|
+
const limit = resolveSearchLimit(params.limit);
|
|
453
|
+
const repo = await resolveSearchRepoScope(session.cwd, normalizeOptionalString(params.repo), query, signal);
|
|
454
|
+
const apiQuery = composeSearchQuery([query, repo ? `repo:${repo}` : undefined]);
|
|
455
|
+
const args = buildGhApiSearchArgs("code", apiQuery, limit, ["Accept: application/vnd.github.text-match+json"]);
|
|
456
|
+
|
|
457
|
+
const response = await git.github.json<GhApiSearchResponse<GhApiSearchCodeItem>>(session.cwd, args, signal);
|
|
458
|
+
const items = (response.items ?? []).map(apiCodeToSearchResult);
|
|
459
|
+
return buildTextResult(formatSearchCodeResults(query, repo, items), undefined, undefined, {
|
|
460
|
+
useless: items.length === 0,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export async function executeSearchCommits(
|
|
465
|
+
session: ToolSession,
|
|
466
|
+
params: GithubInput,
|
|
467
|
+
signal: AbortSignal | undefined,
|
|
468
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
469
|
+
const limit = resolveSearchLimit(params.limit);
|
|
470
|
+
const dateField = resolveSearchDateField("commits", params.dateField);
|
|
471
|
+
const dateQualifier = buildSearchDateQualifier(dateField, params.since, params.until);
|
|
472
|
+
const displayQuery = composeSearchQuery([params.query, dateQualifier]);
|
|
473
|
+
const repo = await resolveSearchRepoScope(session.cwd, normalizeOptionalString(params.repo), displayQuery, signal);
|
|
474
|
+
const apiQuery = composeSearchQuery([displayQuery, repo ? `repo:${repo}` : undefined]);
|
|
475
|
+
const args = buildGhApiSearchArgs("commits", apiQuery, limit);
|
|
476
|
+
|
|
477
|
+
const response = await git.github.json<GhApiSearchResponse<GhApiSearchCommitItem>>(session.cwd, args, signal);
|
|
478
|
+
const items = (response.items ?? []).map(apiCommitToSearchResult);
|
|
479
|
+
return buildTextResult(formatSearchCommitsResults(displayQuery, repo, items), undefined, undefined, {
|
|
480
|
+
useless: items.length === 0,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export async function executeSearchRepos(
|
|
485
|
+
session: ToolSession,
|
|
486
|
+
params: GithubInput,
|
|
487
|
+
signal: AbortSignal | undefined,
|
|
488
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
489
|
+
const limit = resolveSearchLimit(params.limit);
|
|
490
|
+
const dateField = resolveSearchDateField("repos", params.dateField);
|
|
491
|
+
const dateQualifier = buildSearchDateQualifier(dateField, params.since, params.until);
|
|
492
|
+
const query = composeSearchQuery([params.query, dateQualifier]);
|
|
493
|
+
const args = buildGhApiSearchArgs("repositories", query, limit);
|
|
494
|
+
|
|
495
|
+
const response = await git.github.json<GhApiSearchResponse<GhApiSearchRepoItem>>(session.cwd, args, signal);
|
|
496
|
+
const items = (response.items ?? []).map(apiRepoToSearchResult);
|
|
497
|
+
return buildTextResult(formatSearchReposResults(query, items), undefined, undefined, {
|
|
498
|
+
useless: items.length === 0,
|
|
499
|
+
});
|
|
500
|
+
}
|