@oh-my-pi/pi-coding-agent 17.3.7 → 17.3.8
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 +60 -0
- package/dist/{CHANGELOG-1hmwwt45.md → CHANGELOG-vr9cckb4.md} +60 -0
- package/dist/cli.js +2993 -3001
- package/dist/docs-index.generated.txt +1 -1
- package/dist/{tool-views.generated-jdfmzwmn.js → tool-views.generated-dd2km5r2.js} +19 -19
- package/dist/types/advisor/advise-tool.d.ts +4 -2
- package/dist/types/cli/auth-broker-cli.d.ts +15 -0
- package/dist/types/cli/update-cli.d.ts +8 -0
- package/dist/types/cli-commands.d.ts +10 -2
- package/dist/types/config/settings-schema.d.ts +28 -0
- package/dist/types/config/settings.d.ts +9 -0
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/launch/presence.d.ts +4 -1
- package/dist/types/mcp/oauth-credentials.d.ts +23 -0
- package/dist/types/mcp/oauth-flow.d.ts +11 -0
- package/dist/types/mnemopi/backend.d.ts +12 -0
- package/dist/types/modes/components/tool-execution.d.ts +12 -0
- package/dist/types/modes/controllers/input-controller.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +25 -3
- package/dist/types/modes/types.d.ts +10 -0
- package/dist/types/session/agent-session.d.ts +3 -0
- package/dist/types/session/prewalk.d.ts +4 -0
- package/dist/types/session/session-entries.d.ts +0 -1
- package/dist/types/session/session-manager.d.ts +12 -0
- package/dist/types/session/session-stats.d.ts +13 -1
- package/dist/types/session/skill-title-input.d.ts +13 -0
- package/dist/types/subprocess/worker-client.d.ts +7 -4
- package/dist/types/task/label.d.ts +2 -0
- package/dist/types/task/render.d.ts +2 -0
- package/dist/types/tiny/completion-prompt.d.ts +2 -0
- package/dist/types/tiny/title-client.d.ts +6 -4
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tiny/worker.d.ts +27 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/read-format.d.ts +6 -0
- package/dist/types/tools/read-summary.d.ts +7 -1
- package/dist/types/utils/block-context.d.ts +14 -0
- package/dist/types/utils/fetch-timeout.d.ts +15 -0
- package/dist/types/utils/git.d.ts +25 -1
- package/dist/types/web/search/providers/tinyfish.d.ts +4 -0
- package/package.json +13 -13
- package/src/advisor/advise-tool.ts +5 -3
- package/src/cli/auth-broker-cli.ts +36 -1
- package/src/cli/profile-bootstrap.ts +2 -6
- package/src/cli/update-cli.ts +63 -11
- package/src/cli-commands.ts +61 -7
- package/src/commands/completions.ts +2 -1
- package/src/commit/agentic/index.ts +15 -2
- package/src/commit/git/diff.ts +6 -2
- package/src/config/model-resolver.ts +52 -6
- package/src/config/models-config.ts +2 -2
- package/src/config/settings-schema.ts +33 -0
- package/src/config/settings.ts +159 -30
- package/src/discovery/helpers.ts +45 -2
- package/src/discovery/omp-plugins.ts +2 -1
- package/src/discovery/opencode.ts +56 -3
- package/src/eval/js/process-entry.ts +4 -4
- package/src/export/html/tool-views.generated.js +19 -19
- package/src/extensibility/extensions/runner.ts +3 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +47 -0
- package/src/launch/client.ts +9 -4
- package/src/launch/presence.ts +19 -4
- package/src/lsp/defaults.json +1 -1
- package/src/mcp/manager.ts +41 -20
- package/src/mcp/oauth-credentials.ts +38 -0
- package/src/mcp/oauth-flow.ts +21 -0
- package/src/mcp/tool-bridge.ts +32 -16
- package/src/mnemopi/backend.ts +35 -3
- package/src/modes/components/model-hub.ts +37 -4
- package/src/modes/components/settings-selector.ts +17 -11
- package/src/modes/components/tool-execution.ts +97 -29
- package/src/modes/components/tree-selector.ts +7 -2
- package/src/modes/controllers/event-controller.ts +12 -2
- package/src/modes/controllers/input-controller.ts +64 -27
- package/src/modes/controllers/mcp-command-controller.ts +13 -4
- package/src/modes/interactive-mode.ts +79 -11
- package/src/modes/types.ts +11 -0
- package/src/prompts/system/memory-extraction-system.md +5 -22
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/session/agent-session.ts +52 -6
- package/src/session/messages.ts +6 -0
- package/src/session/prewalk.ts +25 -7
- package/src/session/session-entries.ts +0 -1
- package/src/session/session-maintenance.ts +10 -1
- package/src/session/session-manager.ts +15 -0
- package/src/session/session-stats.ts +24 -3
- package/src/session/settings-stream-fn.ts +7 -0
- package/src/session/skill-title-input.ts +32 -0
- package/src/session/turn-recovery.ts +23 -18
- package/src/subprocess/worker-client.ts +8 -5
- package/src/task/executor.ts +11 -0
- package/src/task/index.ts +2 -0
- package/src/task/label.ts +14 -1
- package/src/task/persisted-revive.ts +13 -0
- package/src/task/render.ts +1 -1
- package/src/task/structured-subagent.ts +5 -2
- package/src/tiny/completion-prompt.ts +16 -0
- package/src/tiny/title-client.ts +15 -6
- package/src/tiny/title-protocol.ts +8 -1
- package/src/tiny/worker.ts +21 -19
- package/src/tools/bash.ts +7 -1
- package/src/tools/read-format.ts +16 -2
- package/src/tools/read-summary.ts +9 -4
- package/src/tools/read.ts +306 -72
- package/src/utils/block-context.ts +15 -1
- package/src/utils/fetch-timeout.ts +33 -0
- package/src/utils/git.ts +54 -11
- package/src/web/search/providers/browser-page.ts +21 -3
- package/src/web/search/providers/tinyfish.ts +26 -0
package/src/utils/git.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface GitCommandResult {
|
|
|
21
21
|
exitCode: number;
|
|
22
22
|
stdout: string;
|
|
23
23
|
stderr: string;
|
|
24
|
+
/** True when stdout or stderr hit {@link GIT_COMMAND_OUTPUT_LIMIT_BYTES} and the captured text is incomplete. */
|
|
25
|
+
truncated: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface GitRepository {
|
|
@@ -66,6 +68,7 @@ export interface DiffOptions {
|
|
|
66
68
|
readonly numstat?: boolean;
|
|
67
69
|
readonly signal?: AbortSignal;
|
|
68
70
|
readonly stat?: boolean;
|
|
71
|
+
readonly requireComplete?: boolean;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export interface StatusOptions {
|
|
@@ -173,6 +176,29 @@ export class GitCommandError extends Error {
|
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
/**
|
|
180
|
+
* A git subprocess produced more output than {@link GIT_COMMAND_OUTPUT_LIMIT_BYTES}
|
|
181
|
+
* and its captured stdout was truncated. Thrown only for callers that opt into
|
|
182
|
+
* completeness via `diff({ requireComplete: true })`, where operating on a partial
|
|
183
|
+
* diff would silently corrupt downstream parsing — e.g. the split-commit builder,
|
|
184
|
+
* which would otherwise throw a misleading "No diff found" for files sorting after
|
|
185
|
+
* a large binary blob whose base85 payload pushed the diff past the cap.
|
|
186
|
+
*/
|
|
187
|
+
export class GitOutputTruncatedError extends Error {
|
|
188
|
+
readonly args: readonly string[];
|
|
189
|
+
readonly result: GitCommandResult;
|
|
190
|
+
|
|
191
|
+
constructor(args: readonly string[], result: GitCommandResult) {
|
|
192
|
+
const limitMiB = Math.round(GIT_COMMAND_OUTPUT_LIMIT_BYTES / (1024 * 1024));
|
|
193
|
+
super(
|
|
194
|
+
`git ${args.join(" ")} produced more than ${limitMiB} MiB of output; the captured result is truncated and incomplete.`,
|
|
195
|
+
);
|
|
196
|
+
this.name = "GitOutputTruncatedError";
|
|
197
|
+
this.args = [...args];
|
|
198
|
+
this.result = result;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
176
202
|
// ════════════════════════════════════════════════════════════════════════════
|
|
177
203
|
// Internal: Core execution
|
|
178
204
|
// ════════════════════════════════════════════════════════════════════════════
|
|
@@ -312,7 +338,10 @@ async function waitForExitWithTimeout(
|
|
|
312
338
|
}
|
|
313
339
|
}
|
|
314
340
|
|
|
315
|
-
async function readCappedText(
|
|
341
|
+
async function readCappedText(
|
|
342
|
+
stream: ReadableStream<Uint8Array>,
|
|
343
|
+
maxBytes: number,
|
|
344
|
+
): Promise<{ text: string; truncated: boolean }> {
|
|
316
345
|
const reader = stream.getReader();
|
|
317
346
|
const decoder = new TextDecoder();
|
|
318
347
|
const chunks: string[] = [];
|
|
@@ -335,7 +364,7 @@ async function readCappedText(stream: ReadableStream<Uint8Array>, maxBytes: numb
|
|
|
335
364
|
}
|
|
336
365
|
chunks.push(decoder.decode());
|
|
337
366
|
if (truncated) chunks.push(GIT_OUTPUT_TRUNCATED_MARKER);
|
|
338
|
-
return chunks.join("");
|
|
367
|
+
return { text: chunks.join(""), truncated };
|
|
339
368
|
} finally {
|
|
340
369
|
reader.releaseLock();
|
|
341
370
|
}
|
|
@@ -372,10 +401,15 @@ async function collectSubprocessResult(
|
|
|
372
401
|
void stdoutPromise.catch(() => undefined);
|
|
373
402
|
void stderrPromise.catch(() => undefined);
|
|
374
403
|
await Promise.all([cancelOutput(stdoutStream), cancelOutput(stderrStream)]);
|
|
375
|
-
return { exitCode: GIT_COMMAND_TIMEOUT_EXIT_CODE, stdout: "", stderr: exit.stderr };
|
|
404
|
+
return { exitCode: GIT_COMMAND_TIMEOUT_EXIT_CODE, stdout: "", stderr: exit.stderr, truncated: false };
|
|
376
405
|
}
|
|
377
406
|
const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
|
|
378
|
-
return {
|
|
407
|
+
return {
|
|
408
|
+
exitCode: exit.exitCode ?? 0,
|
|
409
|
+
stdout: stdout.text,
|
|
410
|
+
stderr: stderr.text,
|
|
411
|
+
truncated: stdout.truncated || stderr.truncated,
|
|
412
|
+
};
|
|
379
413
|
}
|
|
380
414
|
|
|
381
415
|
interface CommandOptions {
|
|
@@ -496,7 +530,7 @@ async function git(cwd: string, args: readonly string[], options: CommandOptions
|
|
|
496
530
|
// A deleted/nonexistent cwd also surfaces as a spawn ENOENT; only blame
|
|
497
531
|
// the binary when the working directory actually exists.
|
|
498
532
|
const stderr = fs.existsSync(cwd) ? "git is not installed." : `working directory does not exist: ${cwd}`;
|
|
499
|
-
return { exitCode: GIT_SPAWN_ENOENT_EXIT_CODE, stdout: "", stderr };
|
|
533
|
+
return { exitCode: GIT_SPAWN_ENOENT_EXIT_CODE, stdout: "", stderr, truncated: false };
|
|
500
534
|
}
|
|
501
535
|
throw err;
|
|
502
536
|
}
|
|
@@ -1240,7 +1274,11 @@ export const diff = Object.assign(
|
|
|
1240
1274
|
if (options.allowFailure) {
|
|
1241
1275
|
return (await git(cwd, args, { env: options.env, readOnly: true, signal: options.signal })).stdout;
|
|
1242
1276
|
}
|
|
1243
|
-
|
|
1277
|
+
const result = await runChecked(cwd, args, { env: options.env, readOnly: true, signal: options.signal });
|
|
1278
|
+
if (options.requireComplete && result.truncated) {
|
|
1279
|
+
throw new GitOutputTruncatedError(args, result);
|
|
1280
|
+
}
|
|
1281
|
+
return result.stdout;
|
|
1244
1282
|
},
|
|
1245
1283
|
{
|
|
1246
1284
|
/** List changed file paths. */
|
|
@@ -2005,12 +2043,17 @@ export const patch = {
|
|
|
2005
2043
|
}
|
|
2006
2044
|
},
|
|
2007
2045
|
|
|
2008
|
-
/**
|
|
2046
|
+
/**
|
|
2047
|
+
* Join patch parts into a single patch string.
|
|
2048
|
+
*
|
|
2049
|
+
* Each part is terminated with a single `\n` if it lacks one, then parts are
|
|
2050
|
+
* concatenated verbatim — matching git's native multi-file diff layout. Parts
|
|
2051
|
+
* are NOT separated by an extra blank line and trailing newlines are NOT
|
|
2052
|
+
* stripped: a `GIT binary patch` block ends in a blank line that
|
|
2053
|
+
* `git apply --binary` requires, and stripping it corrupts the patch (#8899).
|
|
2054
|
+
*/
|
|
2009
2055
|
join(parts: string[]): string {
|
|
2010
|
-
return `${
|
|
2011
|
-
.map(part => (part.endsWith("\n") ? part : `${part}\n`))
|
|
2012
|
-
.join("\n")
|
|
2013
|
-
.replace(/\n+$/, "")}\n`;
|
|
2056
|
+
return parts.map(part => (part.endsWith("\n") ? part : `${part}\n`)).join("");
|
|
2014
2057
|
},
|
|
2015
2058
|
};
|
|
2016
2059
|
|
|
@@ -34,6 +34,13 @@ export interface BrowserFetchOptions {
|
|
|
34
34
|
browser?: BrowserFallbackOptions;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Upper bound on `page.close()` during teardown. A dead CDP session leaves
|
|
39
|
+
* puppeteer's close pending forever; `.catch()` only covers rejection, not a
|
|
40
|
+
* hang, so cleanup needs its own deadline (issue #8865).
|
|
41
|
+
*/
|
|
42
|
+
const PAGE_CLOSE_TIMEOUT_MS = 5_000;
|
|
43
|
+
|
|
37
44
|
async function fetchHtmlPage(url: string, options: BrowserFetchOptions, fetchImpl: FetchImpl): Promise<LoadedHtmlPage> {
|
|
38
45
|
const response = await fetchImpl(url, {
|
|
39
46
|
...options.init,
|
|
@@ -74,8 +81,14 @@ async function browseHtmlPage(
|
|
|
74
81
|
try {
|
|
75
82
|
const activePage = await untilAborted(signal, () => handle.browser.newPage());
|
|
76
83
|
page = activePage;
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
// Viewport and stealth setup talk to the same CDP session as the
|
|
85
|
+
// navigations below but were previously awaited raw. When the shared
|
|
86
|
+
// daemon or the page's target dies mid-setup, those puppeteer calls
|
|
87
|
+
// never settle and the provider promise hangs past
|
|
88
|
+
// SEARCH_HARD_TIMEOUT_MS — the abort signal had no listener at these
|
|
89
|
+
// await points (issue #8865).
|
|
90
|
+
await untilAborted(signal, () => applyViewport(activePage));
|
|
91
|
+
await untilAborted(signal, () => applyStealthPatches(handle.browser, activePage, handle.stealth));
|
|
79
92
|
if (homeUrl) {
|
|
80
93
|
await untilAborted(signal, () =>
|
|
81
94
|
activePage.goto(homeUrl, { waitUntil: "domcontentloaded", timeout: timeoutMs }),
|
|
@@ -102,7 +115,12 @@ async function browseHtmlPage(
|
|
|
102
115
|
}
|
|
103
116
|
throw new Error("Browser fallback exhausted without a response");
|
|
104
117
|
} finally {
|
|
105
|
-
|
|
118
|
+
// Teardown must complete even when the caller's signal already fired
|
|
119
|
+
// (navigating away from a dead session leaves `close()` pending), so
|
|
120
|
+
// bound it with a fresh deadline instead of reusing `signal`.
|
|
121
|
+
if (page) {
|
|
122
|
+
await untilAborted(AbortSignal.timeout(PAGE_CLOSE_TIMEOUT_MS), () => page!.close()).catch(() => undefined);
|
|
123
|
+
}
|
|
106
124
|
await releaseBrowser(handle, { kill: false });
|
|
107
125
|
}
|
|
108
126
|
}
|
|
@@ -35,6 +35,10 @@ export interface TinyFishSearchParams {
|
|
|
35
35
|
page?: number;
|
|
36
36
|
include_domains?: string[];
|
|
37
37
|
exclude_domains?: string[];
|
|
38
|
+
/** ISO 3166-1 alpha-2 region, e.g. `IT`. Geolocates results. */
|
|
39
|
+
location?: string;
|
|
40
|
+
/** ISO 639-1 language, e.g. `it`. */
|
|
41
|
+
language?: string;
|
|
38
42
|
signal?: AbortSignal;
|
|
39
43
|
timeoutMs?: number;
|
|
40
44
|
fetch?: FetchImpl;
|
|
@@ -74,6 +78,12 @@ async function callTinyFishSearch(apiKey: string, params: TinyFishSearchParams):
|
|
|
74
78
|
if (params.exclude_domains?.length) {
|
|
75
79
|
url.searchParams.set("exclude_domains", params.exclude_domains.join(","));
|
|
76
80
|
}
|
|
81
|
+
if (params.location) {
|
|
82
|
+
url.searchParams.set("location", params.location);
|
|
83
|
+
}
|
|
84
|
+
if (params.language) {
|
|
85
|
+
url.searchParams.set("language", params.language);
|
|
86
|
+
}
|
|
77
87
|
if (params.num_results !== undefined) {
|
|
78
88
|
url.searchParams.set("num_results", String(params.num_results));
|
|
79
89
|
}
|
|
@@ -133,6 +143,19 @@ function siteHosts(sites: readonly string[]): string[] {
|
|
|
133
143
|
return [...hosts];
|
|
134
144
|
}
|
|
135
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Derive TinyFish `location` (ISO 3166-1 alpha-2, uppercase) and `language`
|
|
148
|
+
* (ISO 639-1, lowercase) from a parsed `lang:` directive. The region subtag is
|
|
149
|
+
* optional: `lang:it` yields language only, `lang:it-it` yields both. Non-region
|
|
150
|
+
* subtags (e.g. the script in `zh-hans`) never become a location.
|
|
151
|
+
*/
|
|
152
|
+
function tinyFishLocale(lang: string | undefined): { location?: string; language?: string } {
|
|
153
|
+
if (!lang) return {};
|
|
154
|
+
const match = /^([a-z]{2})(?:[-_]([a-z]{2}))?(?:[-_]|$)/.exec(lang.toLowerCase());
|
|
155
|
+
if (!match) return {};
|
|
156
|
+
return { language: match[1], location: match[2]?.toUpperCase() };
|
|
157
|
+
}
|
|
158
|
+
|
|
136
159
|
/** Execute TinyFish web search. */
|
|
137
160
|
export async function searchTinyFish(params: SearchParams): Promise<SearchResponse> {
|
|
138
161
|
const numResults = clampNumResults(params.numSearchResults ?? params.limit, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
@@ -152,6 +175,9 @@ export async function searchTinyFish(params: SearchParams): Promise<SearchRespon
|
|
|
152
175
|
if (includeDomains.length > 0) tinyFishParams.include_domains = includeDomains;
|
|
153
176
|
if (excludeDomains.length > 0) tinyFishParams.exclude_domains = excludeDomains;
|
|
154
177
|
}
|
|
178
|
+
const { location, language } = tinyFishLocale(parsed.lang);
|
|
179
|
+
if (location) tinyFishParams.location = location;
|
|
180
|
+
if (language) tinyFishParams.language = language;
|
|
155
181
|
const keyOrResolver: ApiKey = params.authStorage.resolver("tinyfish", {
|
|
156
182
|
sessionId: params.sessionId,
|
|
157
183
|
});
|