@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.
Files changed (247) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
  3. package/dist/cli.js +10775 -10762
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/capability/mcp.d.ts +11 -0
  6. package/dist/types/capability/skill.d.ts +6 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/custom-models.d.ts +31 -0
  14. package/dist/types/config/model-config-values.d.ts +19 -0
  15. package/dist/types/config/model-patch.d.ts +93 -0
  16. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  17. package/dist/types/config/model-registry.d.ts +9 -52
  18. package/dist/types/config/settings-schema.d.ts +1 -34
  19. package/dist/types/config/settings.d.ts +5 -3
  20. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  21. package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
  22. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  23. package/dist/types/discovery/contained-path.d.ts +33 -0
  24. package/dist/types/discovery/index.d.ts +1 -0
  25. package/dist/types/eval/executor-base.d.ts +8 -2
  26. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  27. package/dist/types/export/share.d.ts +1 -1
  28. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +7 -0
  30. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -2
  32. package/dist/types/extensibility/skills.d.ts +5 -0
  33. package/dist/types/lsp/diagnostics.d.ts +96 -0
  34. package/dist/types/lsp/index.d.ts +7 -128
  35. package/dist/types/lsp/servers.d.ts +72 -0
  36. package/dist/types/lsp/tool.d.ts +42 -0
  37. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  38. package/dist/types/lsp/writethrough.d.ts +33 -0
  39. package/dist/types/mcp/transports/header-policy.d.ts +46 -0
  40. package/dist/types/mcp/types.d.ts +15 -0
  41. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  42. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  43. package/dist/types/modes/components/agent-hub.d.ts +2 -0
  44. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  45. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  49. package/dist/types/modes/theme/color.d.ts +19 -0
  50. package/dist/types/modes/theme/loader.d.ts +19 -0
  51. package/dist/types/modes/theme/schema.d.ts +175 -0
  52. package/dist/types/modes/theme/symbols.d.ts +28 -0
  53. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  54. package/dist/types/modes/theme/theme.d.ts +9 -280
  55. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  56. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  57. package/dist/types/registry/agent-registry.d.ts +1 -3
  58. package/dist/types/secrets/index.d.ts +26 -2
  59. package/dist/types/secrets/message-transform.d.ts +40 -0
  60. package/dist/types/secrets/obfuscator.d.ts +0 -108
  61. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  62. package/dist/types/secrets/placeholder.d.ts +94 -0
  63. package/dist/types/secrets/replacement.d.ts +82 -0
  64. package/dist/types/session/agent-session-events.d.ts +2 -2
  65. package/dist/types/session/agent-session-types.d.ts +6 -0
  66. package/dist/types/session/agent-session.d.ts +2 -2
  67. package/dist/types/session/messages.d.ts +1 -0
  68. package/dist/types/session/prewalk.d.ts +3 -1
  69. package/dist/types/session/session-handoff.d.ts +3 -3
  70. package/dist/types/session/session-manager.d.ts +32 -0
  71. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  72. package/dist/types/session/session-tools.d.ts +8 -0
  73. package/dist/types/session/turn-recovery.d.ts +9 -4
  74. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  75. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  76. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  77. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  78. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  79. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  80. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  81. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  82. package/dist/types/task/index.d.ts +2 -0
  83. package/dist/types/tools/gh-common.d.ts +69 -0
  84. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  85. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  86. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  87. package/dist/types/tools/gh-search.d.ts +61 -0
  88. package/dist/types/tools/gh-types.d.ts +340 -0
  89. package/dist/types/tools/gh-view.d.ts +88 -0
  90. package/dist/types/tools/gh.d.ts +4 -201
  91. package/dist/types/tools/read-archive.d.ts +16 -0
  92. package/dist/types/tools/read-format.d.ts +104 -0
  93. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  94. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  95. package/dist/types/tools/read-renderer.d.ts +24 -0
  96. package/dist/types/tools/read-selector.d.ts +27 -0
  97. package/dist/types/tools/read-sqlite.d.ts +16 -0
  98. package/dist/types/tools/read-summary.d.ts +19 -0
  99. package/dist/types/tools/read.d.ts +1 -23
  100. package/dist/types/tools/shell-tokenize.d.ts +18 -1
  101. package/dist/types/utils/changelog.d.ts +0 -5
  102. package/package.json +13 -13
  103. package/scripts/legacy-pi-virtual-module.ts +4 -1
  104. package/src/advisor/runtime.ts +12 -7
  105. package/src/capability/mcp.ts +11 -0
  106. package/src/capability/skill.ts +6 -0
  107. package/src/cli/command-help.ts +4 -0
  108. package/src/cli/gallery-cli.ts +1 -1
  109. package/src/cli-commands.ts +5 -0
  110. package/src/commands/commit.ts +16 -3
  111. package/src/commands/share.ts +71 -0
  112. package/src/commit/agentic/index.ts +39 -21
  113. package/src/commit/execute.ts +56 -0
  114. package/src/commit/index.ts +2 -1
  115. package/src/commit/pipeline.ts +20 -7
  116. package/src/config/custom-models.ts +188 -0
  117. package/src/config/model-config-values.ts +128 -0
  118. package/src/config/model-discovery.ts +1 -1
  119. package/src/config/model-patch.ts +252 -0
  120. package/src/config/model-provider-discovery.ts +132 -0
  121. package/src/config/model-registry.ts +84 -704
  122. package/src/config/settings-schema.ts +2 -33
  123. package/src/config/settings.ts +53 -3
  124. package/src/debug/raw-sse-buffer.ts +20 -0
  125. package/src/discovery/agent-plugin-format.ts +551 -0
  126. package/src/discovery/agent-plugins.ts +341 -0
  127. package/src/discovery/claude-plugins.ts +21 -5
  128. package/src/discovery/contained-path.ts +75 -0
  129. package/src/discovery/helpers.ts +23 -9
  130. package/src/discovery/index.ts +1 -0
  131. package/src/discovery/omp-plugins.ts +20 -7
  132. package/src/edit/hashline/diff.ts +5 -1
  133. package/src/eval/executor-base.ts +39 -6
  134. package/src/eval/jl/executor.ts +82 -371
  135. package/src/eval/kernel-session-registry.ts +398 -0
  136. package/src/eval/py/executor.ts +53 -261
  137. package/src/eval/rb/executor.ts +36 -279
  138. package/src/exec/non-interactive-env.ts +1 -0
  139. package/src/export/share.ts +2 -1
  140. package/src/extensibility/custom-tools/types.ts +2 -2
  141. package/src/extensibility/extensions/loader.ts +78 -14
  142. package/src/extensibility/extensions/runner.ts +6 -0
  143. package/src/extensibility/extensions/types.ts +12 -0
  144. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  145. package/src/extensibility/shared-events.ts +3 -2
  146. package/src/extensibility/skills.ts +9 -0
  147. package/src/internal-urls/skill-protocol.ts +14 -0
  148. package/src/internal-urls/vault-protocol.ts +2 -2
  149. package/src/launch/client.ts +11 -1
  150. package/src/launch/protocol.ts +7 -2
  151. package/src/lsp/diagnostics.ts +516 -0
  152. package/src/lsp/index.ts +20 -2819
  153. package/src/lsp/servers.ts +296 -0
  154. package/src/lsp/tool.ts +1352 -0
  155. package/src/lsp/workspace-diagnostics.ts +170 -0
  156. package/src/lsp/writethrough.ts +561 -0
  157. package/src/main.ts +3 -2
  158. package/src/mcp/config.ts +3 -0
  159. package/src/mcp/manager.ts +12 -9
  160. package/src/mcp/transports/header-policy.ts +95 -0
  161. package/src/mcp/transports/http.ts +35 -52
  162. package/src/mcp/transports/sse.ts +20 -27
  163. package/src/mcp/types.ts +15 -0
  164. package/src/modes/acp/acp-agent.ts +15 -4
  165. package/src/modes/components/agent-dashboard.ts +2 -0
  166. package/src/modes/components/agent-hub-projection.ts +2 -2
  167. package/src/modes/components/agent-hub-renderer.ts +3 -7
  168. package/src/modes/components/agent-hub.ts +5 -0
  169. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  170. package/src/modes/components/chat-transcript-builder.ts +3 -0
  171. package/src/modes/components/custom-editor.ts +0 -9
  172. package/src/modes/components/status-line/component.ts +1 -0
  173. package/src/modes/components/status-line/segments.ts +6 -4
  174. package/src/modes/components/status-line/types.ts +2 -0
  175. package/src/modes/components/tool-execution.ts +13 -16
  176. package/src/modes/components/tree-selector.ts +62 -3
  177. package/src/modes/controllers/command-controller.ts +8 -2
  178. package/src/modes/controllers/event-controller.ts +15 -15
  179. package/src/modes/controllers/input-controller.ts +20 -2
  180. package/src/modes/controllers/selector-controller.ts +1 -0
  181. package/src/modes/theme/color.ts +133 -0
  182. package/src/modes/theme/loader.ts +178 -0
  183. package/src/modes/theme/schema.ts +263 -0
  184. package/src/modes/theme/symbols.ts +1000 -0
  185. package/src/modes/theme/theme-class.ts +611 -0
  186. package/src/modes/theme/theme.ts +27 -2453
  187. package/src/modes/theme/tui-adapters.ts +279 -0
  188. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  189. package/src/modes/utils/ui-helpers.ts +1 -0
  190. package/src/registry/agent-registry.ts +2 -2
  191. package/src/sdk.ts +6 -49
  192. package/src/secrets/index.ts +51 -6
  193. package/src/secrets/message-transform.ts +287 -0
  194. package/src/secrets/obfuscator.ts +39 -1303
  195. package/src/secrets/placeholder-scan.ts +506 -0
  196. package/src/secrets/placeholder.ts +309 -0
  197. package/src/secrets/replacement.ts +216 -0
  198. package/src/session/agent-session-events.ts +2 -2
  199. package/src/session/agent-session-types.ts +6 -0
  200. package/src/session/agent-session.ts +146 -21
  201. package/src/session/indexed-session-storage.ts +7 -2
  202. package/src/session/messages.ts +1 -0
  203. package/src/session/prewalk.ts +57 -17
  204. package/src/session/session-advisors.ts +32 -34
  205. package/src/session/session-context.ts +3 -5
  206. package/src/session/session-handoff.ts +39 -16
  207. package/src/session/session-manager.ts +67 -3
  208. package/src/session/session-provider-boundary.ts +3 -6
  209. package/src/session/session-tools.ts +37 -3
  210. package/src/session/todo-tracker.ts +11 -1
  211. package/src/session/turn-recovery.ts +115 -84
  212. package/src/slash-commands/builtin-collaboration.ts +504 -0
  213. package/src/slash-commands/builtin-completions.ts +291 -0
  214. package/src/slash-commands/builtin-control.ts +78 -0
  215. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  216. package/src/slash-commands/builtin-marketplace.ts +567 -0
  217. package/src/slash-commands/builtin-modes.ts +518 -0
  218. package/src/slash-commands/builtin-registry.ts +21 -2996
  219. package/src/slash-commands/builtin-session.ts +592 -0
  220. package/src/task/executor.ts +17 -14
  221. package/src/task/index.ts +21 -6
  222. package/src/tools/bash-skill-urls.ts +15 -0
  223. package/src/tools/bash.ts +10 -11
  224. package/src/tools/debug.ts +1 -1
  225. package/src/tools/gh-common.ts +288 -0
  226. package/src/tools/gh-pr-checkout.ts +679 -0
  227. package/src/tools/gh-pr-diff.ts +473 -0
  228. package/src/tools/gh-run-watch.ts +1015 -0
  229. package/src/tools/gh-search.ts +500 -0
  230. package/src/tools/gh-types.ts +379 -0
  231. package/src/tools/gh-view.ts +612 -0
  232. package/src/tools/gh.ts +109 -3821
  233. package/src/tools/jtd-to-json-schema.ts +123 -21
  234. package/src/tools/read-archive.ts +209 -0
  235. package/src/tools/read-format.ts +593 -0
  236. package/src/tools/read-path-resolution.ts +36 -0
  237. package/src/tools/read-pdf-images.ts +250 -0
  238. package/src/tools/read-renderer.ts +289 -0
  239. package/src/tools/read-selector.ts +84 -0
  240. package/src/tools/read-sqlite.ts +215 -0
  241. package/src/tools/read-summary.ts +199 -0
  242. package/src/tools/read.ts +82 -1824
  243. package/src/tools/shell-tokenize.ts +99 -1
  244. package/src/utils/changelog.ts +1 -1
  245. package/src/utils/title-generator.ts +3 -1
  246. package/src/web/search/providers/exa.ts +1 -1
  247. 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
+ }