@oh-my-pi/pi-coding-agent 17.2.11 → 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 (152) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/{CHANGELOG-d9xenpn9.md → CHANGELOG-k9ghy5sn.md} +13 -0
  3. package/dist/cli.js +10795 -10791
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/config/custom-models.d.ts +31 -0
  6. package/dist/types/config/model-config-values.d.ts +19 -0
  7. package/dist/types/config/model-patch.d.ts +93 -0
  8. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  9. package/dist/types/config/model-registry.d.ts +5 -52
  10. package/dist/types/config/settings.d.ts +5 -3
  11. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  12. package/dist/types/discovery/agent-plugin-format.d.ts +6 -13
  13. package/dist/types/discovery/contained-path.d.ts +2 -3
  14. package/dist/types/eval/executor-base.d.ts +8 -2
  15. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  16. package/dist/types/export/share.d.ts +1 -1
  17. package/dist/types/lsp/diagnostics.d.ts +96 -0
  18. package/dist/types/lsp/index.d.ts +7 -128
  19. package/dist/types/lsp/servers.d.ts +72 -0
  20. package/dist/types/lsp/tool.d.ts +42 -0
  21. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  22. package/dist/types/lsp/writethrough.d.ts +33 -0
  23. package/dist/types/mcp/transports/header-policy.d.ts +2 -1
  24. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  25. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  26. package/dist/types/modes/theme/color.d.ts +19 -0
  27. package/dist/types/modes/theme/loader.d.ts +19 -0
  28. package/dist/types/modes/theme/schema.d.ts +175 -0
  29. package/dist/types/modes/theme/symbols.d.ts +28 -0
  30. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  31. package/dist/types/modes/theme/theme.d.ts +9 -280
  32. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  33. package/dist/types/registry/agent-registry.d.ts +1 -3
  34. package/dist/types/secrets/index.d.ts +3 -1
  35. package/dist/types/secrets/message-transform.d.ts +40 -0
  36. package/dist/types/secrets/obfuscator.d.ts +0 -108
  37. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  38. package/dist/types/secrets/placeholder.d.ts +94 -0
  39. package/dist/types/secrets/replacement.d.ts +82 -0
  40. package/dist/types/session/agent-session-types.d.ts +6 -0
  41. package/dist/types/session/agent-session.d.ts +1 -1
  42. package/dist/types/session/session-handoff.d.ts +3 -3
  43. package/dist/types/session/session-manager.d.ts +32 -0
  44. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  45. package/dist/types/session/turn-recovery.d.ts +2 -2
  46. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  47. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  48. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  49. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  50. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  51. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  52. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  53. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  54. package/dist/types/tools/gh-common.d.ts +69 -0
  55. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  56. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  57. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  58. package/dist/types/tools/gh-search.d.ts +61 -0
  59. package/dist/types/tools/gh-types.d.ts +340 -0
  60. package/dist/types/tools/gh-view.d.ts +88 -0
  61. package/dist/types/tools/gh.d.ts +4 -201
  62. package/dist/types/tools/read-archive.d.ts +16 -0
  63. package/dist/types/tools/read-format.d.ts +104 -0
  64. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  65. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  66. package/dist/types/tools/read-renderer.d.ts +24 -0
  67. package/dist/types/tools/read-selector.d.ts +27 -0
  68. package/dist/types/tools/read-sqlite.d.ts +16 -0
  69. package/dist/types/tools/read-summary.d.ts +19 -0
  70. package/dist/types/tools/read.d.ts +1 -23
  71. package/dist/types/tools/shell-tokenize.d.ts +2 -1
  72. package/dist/types/utils/changelog.d.ts +0 -5
  73. package/package.json +13 -13
  74. package/src/advisor/runtime.ts +12 -7
  75. package/src/config/custom-models.ts +188 -0
  76. package/src/config/model-config-values.ts +128 -0
  77. package/src/config/model-patch.ts +252 -0
  78. package/src/config/model-provider-discovery.ts +132 -0
  79. package/src/config/model-registry.ts +64 -704
  80. package/src/config/settings.ts +7 -3
  81. package/src/debug/raw-sse-buffer.ts +20 -0
  82. package/src/discovery/agent-plugin-format.ts +7 -7
  83. package/src/discovery/contained-path.ts +2 -5
  84. package/src/edit/hashline/diff.ts +5 -1
  85. package/src/eval/executor-base.ts +39 -6
  86. package/src/eval/jl/executor.ts +82 -371
  87. package/src/eval/kernel-session-registry.ts +398 -0
  88. package/src/eval/py/executor.ts +53 -261
  89. package/src/eval/rb/executor.ts +36 -279
  90. package/src/export/share.ts +2 -1
  91. package/src/lsp/diagnostics.ts +516 -0
  92. package/src/lsp/index.ts +20 -2819
  93. package/src/lsp/servers.ts +296 -0
  94. package/src/lsp/tool.ts +1352 -0
  95. package/src/lsp/workspace-diagnostics.ts +170 -0
  96. package/src/lsp/writethrough.ts +561 -0
  97. package/src/mcp/transports/header-policy.ts +1 -1
  98. package/src/modes/components/agent-hub-projection.ts +2 -2
  99. package/src/modes/components/agent-hub-renderer.ts +3 -7
  100. package/src/modes/controllers/command-controller.ts +4 -1
  101. package/src/modes/theme/color.ts +133 -0
  102. package/src/modes/theme/loader.ts +178 -0
  103. package/src/modes/theme/schema.ts +263 -0
  104. package/src/modes/theme/symbols.ts +1000 -0
  105. package/src/modes/theme/theme-class.ts +611 -0
  106. package/src/modes/theme/theme.ts +27 -2453
  107. package/src/modes/theme/tui-adapters.ts +279 -0
  108. package/src/registry/agent-registry.ts +2 -2
  109. package/src/secrets/index.ts +6 -12
  110. package/src/secrets/message-transform.ts +287 -0
  111. package/src/secrets/obfuscator.ts +39 -1303
  112. package/src/secrets/placeholder-scan.ts +506 -0
  113. package/src/secrets/placeholder.ts +309 -0
  114. package/src/secrets/replacement.ts +216 -0
  115. package/src/session/agent-session-types.ts +6 -0
  116. package/src/session/agent-session.ts +113 -7
  117. package/src/session/indexed-session-storage.ts +7 -2
  118. package/src/session/session-advisors.ts +32 -34
  119. package/src/session/session-handoff.ts +39 -20
  120. package/src/session/session-manager.ts +67 -3
  121. package/src/session/session-provider-boundary.ts +3 -6
  122. package/src/session/turn-recovery.ts +21 -12
  123. package/src/slash-commands/builtin-collaboration.ts +504 -0
  124. package/src/slash-commands/builtin-completions.ts +291 -0
  125. package/src/slash-commands/builtin-control.ts +78 -0
  126. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  127. package/src/slash-commands/builtin-marketplace.ts +567 -0
  128. package/src/slash-commands/builtin-modes.ts +518 -0
  129. package/src/slash-commands/builtin-registry.ts +21 -3000
  130. package/src/slash-commands/builtin-session.ts +592 -0
  131. package/src/task/executor.ts +17 -14
  132. package/src/tools/gh-common.ts +288 -0
  133. package/src/tools/gh-pr-checkout.ts +679 -0
  134. package/src/tools/gh-pr-diff.ts +473 -0
  135. package/src/tools/gh-run-watch.ts +1015 -0
  136. package/src/tools/gh-search.ts +500 -0
  137. package/src/tools/gh-types.ts +379 -0
  138. package/src/tools/gh-view.ts +612 -0
  139. package/src/tools/gh.ts +109 -3821
  140. package/src/tools/read-archive.ts +209 -0
  141. package/src/tools/read-format.ts +593 -0
  142. package/src/tools/read-path-resolution.ts +36 -0
  143. package/src/tools/read-pdf-images.ts +250 -0
  144. package/src/tools/read-renderer.ts +289 -0
  145. package/src/tools/read-selector.ts +84 -0
  146. package/src/tools/read-sqlite.ts +215 -0
  147. package/src/tools/read-summary.ts +199 -0
  148. package/src/tools/read.ts +77 -1820
  149. package/src/tools/shell-tokenize.ts +1 -1
  150. package/src/utils/changelog.ts +1 -1
  151. package/src/utils/title-generator.ts +3 -1
  152. package/src/web/search/providers/zai.ts +12 -3
@@ -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
+ }