@oh-my-pi/pi-coding-agent 15.1.3 → 15.1.5
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 +24 -0
- package/dist/types/async/job-manager.d.ts +3 -2
- package/dist/types/main.d.ts +11 -2
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/acp/acp-event-mapper.d.ts +13 -1
- package/dist/types/modes/acp/acp-mode.d.ts +3 -1
- package/dist/types/plan-mode/approved-plan.d.ts +6 -4
- package/dist/types/session/agent-session.d.ts +6 -2
- package/dist/types/session/client-bridge.d.ts +3 -0
- package/dist/types/tools/ast-edit.d.ts +2 -0
- package/dist/types/tools/ast-grep.d.ts +2 -0
- package/dist/types/tools/render-utils.d.ts +13 -3
- package/package.json +7 -7
- package/src/async/job-manager.ts +111 -13
- package/src/cli/update-cli.ts +1 -5
- package/src/eval/js/shared/runtime.ts +82 -2
- package/src/extensibility/typebox.ts +44 -17
- package/src/main.ts +215 -148
- package/src/modes/acp/acp-agent.ts +115 -32
- package/src/modes/acp/acp-client-bridge.ts +2 -1
- package/src/modes/acp/acp-event-mapper.ts +208 -32
- package/src/modes/acp/acp-mode.ts +11 -3
- package/src/modes/components/tree-selector.ts +26 -7
- package/src/plan-mode/approved-plan.ts +21 -9
- package/src/prompts/agents/oracle.md +56 -0
- package/src/prompts/tools/ask.md +4 -3
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +186 -54
- package/src/session/client-bridge.ts +3 -0
- package/src/task/agents.ts +2 -0
- package/src/tools/ast-edit.ts +19 -11
- package/src/tools/ast-grep.ts +14 -10
- package/src/tools/render-utils.ts +26 -12
package/src/tools/ast-edit.ts
CHANGED
|
@@ -18,8 +18,8 @@ import type { OutputMeta } from "./output-meta";
|
|
|
18
18
|
import { resolveToolSearchScope } from "./path-utils";
|
|
19
19
|
import {
|
|
20
20
|
appendParseErrorsBulletList,
|
|
21
|
+
capParseErrors,
|
|
21
22
|
createCachedComponent,
|
|
22
|
-
dedupeParseErrors,
|
|
23
23
|
formatCodeFrameLine,
|
|
24
24
|
formatCount,
|
|
25
25
|
formatEmptyMessage,
|
|
@@ -146,6 +146,8 @@ export interface AstEditToolDetails {
|
|
|
146
146
|
applied: boolean;
|
|
147
147
|
limitReached: boolean;
|
|
148
148
|
parseErrors?: string[];
|
|
149
|
+
/** Total parse error count before {@link PARSE_ERRORS_LIMIT} capping. Omitted when no errors. */
|
|
150
|
+
parseErrorsTotal?: number;
|
|
149
151
|
scopePath?: string;
|
|
150
152
|
files?: string[];
|
|
151
153
|
fileReplacements?: Array<{ path: string; count: number }>;
|
|
@@ -210,7 +212,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
210
212
|
signal,
|
|
211
213
|
});
|
|
212
214
|
|
|
213
|
-
const
|
|
215
|
+
const { errors: cappedParseErrors, total: parseErrorsTotal } = capParseErrors(result.parseErrors);
|
|
214
216
|
const formatPath = (filePath: string): string =>
|
|
215
217
|
formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
|
|
216
218
|
|
|
@@ -237,15 +239,15 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
237
239
|
filesSearched: result.filesSearched,
|
|
238
240
|
applied: result.applied,
|
|
239
241
|
limitReached: result.limitReached,
|
|
240
|
-
...(
|
|
242
|
+
...(cappedParseErrors.length > 0 ? { parseErrors: cappedParseErrors, parseErrorsTotal } : {}),
|
|
241
243
|
scopePath,
|
|
242
244
|
files: fileList,
|
|
243
245
|
fileReplacements: [],
|
|
244
246
|
};
|
|
245
247
|
|
|
246
248
|
if (result.totalReplacements === 0) {
|
|
247
|
-
const parseMessage =
|
|
248
|
-
? `\n${formatParseErrors(
|
|
249
|
+
const parseMessage = cappedParseErrors.length
|
|
250
|
+
? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
|
|
249
251
|
: "";
|
|
250
252
|
return toolResult(baseDetails).text(`No replacements made${parseMessage}`).done();
|
|
251
253
|
}
|
|
@@ -308,8 +310,8 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
308
310
|
if (result.limitReached) {
|
|
309
311
|
outputLines.push("", "Limit reached; narrow paths.");
|
|
310
312
|
}
|
|
311
|
-
if (
|
|
312
|
-
outputLines.push("", ...formatParseErrors(
|
|
313
|
+
if (cappedParseErrors.length) {
|
|
314
|
+
outputLines.push("", ...formatParseErrors(cappedParseErrors, parseErrorsTotal));
|
|
313
315
|
}
|
|
314
316
|
|
|
315
317
|
// Register pending action so `resolve` can apply or discard these previewed changes
|
|
@@ -326,7 +328,9 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
326
328
|
maxFiles,
|
|
327
329
|
failOnParseError: false,
|
|
328
330
|
});
|
|
329
|
-
const
|
|
331
|
+
const { errors: cappedApplyParseErrors, total: applyParseErrorsTotal } = capParseErrors(
|
|
332
|
+
applyResult.parseErrors,
|
|
333
|
+
);
|
|
330
334
|
const { record: recordAppliedFile, list: appliedFileList } = createFileRecorder();
|
|
331
335
|
const appliedFileReplacementCounts = new Map<string, number>();
|
|
332
336
|
for (const fileChange of applyResult.fileChanges) {
|
|
@@ -350,7 +354,9 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
350
354
|
filesSearched: applyResult.filesSearched,
|
|
351
355
|
applied: applyResult.applied,
|
|
352
356
|
limitReached: applyResult.limitReached,
|
|
353
|
-
...(
|
|
357
|
+
...(cappedApplyParseErrors.length > 0
|
|
358
|
+
? { parseErrors: cappedApplyParseErrors, parseErrorsTotal: applyParseErrorsTotal }
|
|
359
|
+
: {}),
|
|
354
360
|
scopePath,
|
|
355
361
|
files: appliedFileList,
|
|
356
362
|
fileReplacements: appliedFileReplacements,
|
|
@@ -441,7 +447,7 @@ export const astEditToolRenderer = {
|
|
|
441
447
|
if (filesSearched > 0) meta.push(`searched ${filesSearched}`);
|
|
442
448
|
const header = renderStatusLine({ icon: "warning", title: "AST Edit", description, meta }, uiTheme);
|
|
443
449
|
const lines = [header, formatEmptyMessage("No replacements made", uiTheme)];
|
|
444
|
-
appendParseErrorsBulletList(lines, details?.parseErrors, uiTheme);
|
|
450
|
+
appendParseErrorsBulletList(lines, details?.parseErrors, uiTheme, details?.parseErrorsTotal);
|
|
445
451
|
return new Text(lines.join("\n"), 0, 0);
|
|
446
452
|
}
|
|
447
453
|
|
|
@@ -470,7 +476,9 @@ export const astEditToolRenderer = {
|
|
|
470
476
|
extraLines.push(uiTheme.fg("warning", "limit reached; narrow path"));
|
|
471
477
|
}
|
|
472
478
|
if (details?.parseErrors?.length) {
|
|
473
|
-
extraLines.push(
|
|
479
|
+
extraLines.push(
|
|
480
|
+
uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors, details.parseErrorsTotal)),
|
|
481
|
+
);
|
|
474
482
|
}
|
|
475
483
|
return createCachedComponent(
|
|
476
484
|
() => options.expanded,
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -18,8 +18,8 @@ import type { OutputMeta } from "./output-meta";
|
|
|
18
18
|
import { resolveToolSearchScope } from "./path-utils";
|
|
19
19
|
import {
|
|
20
20
|
appendParseErrorsBulletList,
|
|
21
|
+
capParseErrors,
|
|
21
22
|
createCachedComponent,
|
|
22
|
-
dedupeParseErrors,
|
|
23
23
|
formatCodeFrameLine,
|
|
24
24
|
formatCount,
|
|
25
25
|
formatEmptyMessage,
|
|
@@ -104,6 +104,8 @@ export interface AstGrepToolDetails {
|
|
|
104
104
|
filesSearched: number;
|
|
105
105
|
limitReached: boolean;
|
|
106
106
|
parseErrors?: string[];
|
|
107
|
+
/** Total parse error count before {@link PARSE_ERRORS_LIMIT} capping. Omitted when no errors. */
|
|
108
|
+
parseErrorsTotal?: number;
|
|
107
109
|
scopePath?: string;
|
|
108
110
|
files?: string[];
|
|
109
111
|
fileMatches?: Array<{ path: string; count: number }>;
|
|
@@ -172,7 +174,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
172
174
|
const parseError = error.match(/^.+: (.+: parse error \(syntax tree contains error nodes\))$/);
|
|
173
175
|
return parseError?.[1] ?? error;
|
|
174
176
|
});
|
|
175
|
-
const
|
|
177
|
+
const { errors: cappedParseErrors, total: parseErrorsTotal } = capParseErrors(normalizedParseErrors);
|
|
176
178
|
const formatPath = (filePath: string): string =>
|
|
177
179
|
formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
|
|
178
180
|
|
|
@@ -193,18 +195,18 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
193
195
|
fileCount: result.filesWithMatches,
|
|
194
196
|
filesSearched: result.filesSearched,
|
|
195
197
|
limitReached: result.limitReached,
|
|
196
|
-
...(
|
|
198
|
+
...(cappedParseErrors.length > 0 ? { parseErrors: cappedParseErrors, parseErrorsTotal } : {}),
|
|
197
199
|
scopePath,
|
|
198
200
|
files: fileList,
|
|
199
201
|
fileMatches: [],
|
|
200
202
|
};
|
|
201
203
|
|
|
202
204
|
if (result.matches.length === 0) {
|
|
203
|
-
const noMatchMessage =
|
|
205
|
+
const noMatchMessage = cappedParseErrors.length
|
|
204
206
|
? "No matches found. Parse issues mean the query may be mis-scoped; narrow `paths` before concluding absence."
|
|
205
207
|
: "No matches found";
|
|
206
|
-
const parseMessage =
|
|
207
|
-
? `\n${formatParseErrors(
|
|
208
|
+
const parseMessage = cappedParseErrors.length
|
|
209
|
+
? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
|
|
208
210
|
: "";
|
|
209
211
|
return toolResult(baseDetails).text(`${noMatchMessage}${parseMessage}`).done();
|
|
210
212
|
}
|
|
@@ -269,8 +271,8 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
269
271
|
if (result.limitReached) {
|
|
270
272
|
outputLines.push("", "Result limit reached; narrow paths or increase limit.");
|
|
271
273
|
}
|
|
272
|
-
if (
|
|
273
|
-
outputLines.push("", ...formatParseErrors(
|
|
274
|
+
if (cappedParseErrors.length) {
|
|
275
|
+
outputLines.push("", ...formatParseErrors(cappedParseErrors, parseErrorsTotal));
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
return toolResult(details).text(outputLines.join("\n")).done();
|
|
@@ -329,7 +331,7 @@ export const astGrepToolRenderer = {
|
|
|
329
331
|
const lines = [header, formatEmptyMessage("No matches found", uiTheme)];
|
|
330
332
|
if (details?.parseErrors?.length) {
|
|
331
333
|
lines.push(uiTheme.fg("warning", "Query may be mis-scoped; narrow `paths` before concluding absence"));
|
|
332
|
-
appendParseErrorsBulletList(lines, details.parseErrors, uiTheme);
|
|
334
|
+
appendParseErrorsBulletList(lines, details.parseErrors, uiTheme, details.parseErrorsTotal);
|
|
333
335
|
}
|
|
334
336
|
return new Text(lines.join("\n"), 0, 0);
|
|
335
337
|
}
|
|
@@ -356,7 +358,9 @@ export const astGrepToolRenderer = {
|
|
|
356
358
|
extraLines.push(uiTheme.fg("warning", "limit reached; narrow paths or increase limit"));
|
|
357
359
|
}
|
|
358
360
|
if (details?.parseErrors?.length) {
|
|
359
|
-
extraLines.push(
|
|
361
|
+
extraLines.push(
|
|
362
|
+
uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors, details.parseErrorsTotal)),
|
|
363
|
+
);
|
|
360
364
|
}
|
|
361
365
|
|
|
362
366
|
return createCachedComponent(
|
|
@@ -633,17 +633,29 @@ export function dedupeParseErrors(errors: string[] | undefined): string[] {
|
|
|
633
633
|
return deduped;
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
export function formatParseErrors(errors: string[]): string[] {
|
|
636
|
+
export function formatParseErrors(errors: string[], total?: number): string[] {
|
|
637
637
|
const deduped = dedupeParseErrors(errors);
|
|
638
638
|
if (deduped.length === 0) return [];
|
|
639
|
+
const fullCount = total ?? deduped.length;
|
|
639
640
|
const capped = deduped.slice(0, PARSE_ERRORS_LIMIT);
|
|
640
|
-
const header =
|
|
641
|
-
deduped.length > PARSE_ERRORS_LIMIT
|
|
642
|
-
? `Parse issues (${PARSE_ERRORS_LIMIT} / ${deduped.length}):`
|
|
643
|
-
: "Parse issues:";
|
|
641
|
+
const header = fullCount > capped.length ? `Parse issues (${capped.length} / ${fullCount}):` : "Parse issues:";
|
|
644
642
|
return [header, ...capped.map(err => `- ${err}`)];
|
|
645
643
|
}
|
|
646
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Cap an upstream parse-error list to {@link PARSE_ERRORS_LIMIT} unique entries,
|
|
647
|
+
* preserving the original deduplicated total. Use this at the source so tool
|
|
648
|
+
* details never carry thousands of per-file parse errors into traces or
|
|
649
|
+
* renderers.
|
|
650
|
+
*/
|
|
651
|
+
export function capParseErrors(
|
|
652
|
+
errors: string[] | undefined,
|
|
653
|
+
limit: number = PARSE_ERRORS_LIMIT,
|
|
654
|
+
): { errors: string[]; total: number } {
|
|
655
|
+
const deduped = dedupeParseErrors(errors);
|
|
656
|
+
return { errors: deduped.slice(0, limit), total: deduped.length };
|
|
657
|
+
}
|
|
658
|
+
|
|
647
659
|
// =============================================================================
|
|
648
660
|
// Renderer helpers shared by search / find / ast tools
|
|
649
661
|
// =============================================================================
|
|
@@ -712,14 +724,16 @@ export function appendParseErrorsBulletList(
|
|
|
712
724
|
lines: string[],
|
|
713
725
|
parseErrors: readonly string[] | undefined,
|
|
714
726
|
theme: Theme,
|
|
727
|
+
total?: number,
|
|
715
728
|
): void {
|
|
716
729
|
if (!parseErrors || parseErrors.length === 0) return;
|
|
730
|
+
const fullCount = total ?? parseErrors.length;
|
|
717
731
|
const capped = parseErrors.slice(0, PARSE_ERRORS_LIMIT);
|
|
718
732
|
for (const err of capped) {
|
|
719
733
|
lines.push(theme.fg("warning", ` - ${err}`));
|
|
720
734
|
}
|
|
721
|
-
if (
|
|
722
|
-
lines.push(theme.fg("dim", ` … ${
|
|
735
|
+
if (fullCount > capped.length) {
|
|
736
|
+
lines.push(theme.fg("dim", ` … ${fullCount - capped.length} more`));
|
|
723
737
|
}
|
|
724
738
|
}
|
|
725
739
|
|
|
@@ -727,11 +741,11 @@ export function appendParseErrorsBulletList(
|
|
|
727
741
|
* Human-readable summary string for the parse-issues count, capped by
|
|
728
742
|
* {@link PARSE_ERRORS_LIMIT}.
|
|
729
743
|
*/
|
|
730
|
-
export function formatParseErrorsCountLabel(parseErrors: readonly string[]): string {
|
|
731
|
-
const
|
|
732
|
-
return
|
|
733
|
-
? `${PARSE_ERRORS_LIMIT} / ${
|
|
734
|
-
: `${
|
|
744
|
+
export function formatParseErrorsCountLabel(parseErrors: readonly string[], total?: number): string {
|
|
745
|
+
const fullCount = total ?? parseErrors.length;
|
|
746
|
+
return fullCount > PARSE_ERRORS_LIMIT
|
|
747
|
+
? `${PARSE_ERRORS_LIMIT} / ${fullCount} parse issues`
|
|
748
|
+
: `${fullCount} parse issue${fullCount !== 1 ? "s" : ""}`;
|
|
735
749
|
}
|
|
736
750
|
|
|
737
751
|
// =============================================================================
|