@oh-my-pi/pi-coding-agent 3.20.1 → 3.24.0
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 +107 -8
- package/docs/custom-tools.md +3 -3
- package/docs/extensions.md +226 -220
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +50 -53
- package/examples/custom-tools/README.md +2 -17
- package/examples/extensions/README.md +76 -74
- package/examples/extensions/todo.ts +2 -5
- package/examples/hooks/custom-compaction.ts +2 -4
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +7 -11
- package/package.json +6 -6
- package/src/cli/args.ts +9 -6
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +16 -5
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -8
- package/src/core/file-mentions.ts +5 -8
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +1 -1
- package/src/core/sdk.ts +64 -105
- package/src/core/session-manager.ts +18 -22
- package/src/core/settings-manager.ts +66 -1
- package/src/core/slash-commands.ts +12 -5
- package/src/core/system-prompt.ts +49 -36
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/ask.ts +98 -4
- package/src/core/tools/bash-interceptor.ts +11 -4
- package/src/core/tools/bash.ts +121 -5
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit-diff.ts +73 -24
- package/src/core/tools/edit.ts +221 -34
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +149 -5
- package/src/core/tools/gemini-image.ts +279 -56
- package/src/core/tools/git.ts +17 -3
- package/src/core/tools/grep.ts +185 -5
- package/src/core/tools/index.test.ts +180 -0
- package/src/core/tools/index.ts +96 -242
- package/src/core/tools/ls.ts +133 -5
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +21 -22
- package/src/core/tools/notebook.ts +112 -4
- package/src/core/tools/output.ts +175 -15
- package/src/core/tools/read.ts +127 -25
- package/src/core/tools/render-utils.ts +241 -0
- package/src/core/tools/renderers.ts +40 -828
- package/src/core/tools/review.ts +26 -25
- package/src/core/tools/rulebook.ts +11 -3
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +264 -254
- package/src/core/tools/task/index.ts +48 -208
- package/src/core/tools/task/render.ts +26 -11
- package/src/core/tools/task/types.ts +7 -12
- package/src/core/tools/task/worker-protocol.ts +17 -0
- package/src/core/tools/task/worker.ts +238 -0
- package/src/core/tools/truncate.ts +27 -1
- package/src/core/tools/web-fetch.ts +25 -49
- package/src/core/tools/web-search/index.ts +132 -46
- package/src/core/tools/web-search/providers/anthropic.ts +7 -2
- package/src/core/tools/web-search/providers/exa.ts +2 -1
- package/src/core/tools/web-search/providers/perplexity.ts +6 -1
- package/src/core/tools/web-search/render.ts +6 -4
- package/src/core/tools/web-search/types.ts +13 -0
- package/src/core/tools/write.ts +96 -14
- package/src/core/voice.ts +1 -1
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +5 -5
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +49 -0
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +93 -538
- package/src/modes/interactive/interactive-mode.ts +19 -7
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/task.md +0 -7
- package/src/prompts/tools/gemini-image.md +5 -1
- package/src/prompts/tools/output.md +6 -2
- package/src/prompts/tools/task.md +68 -0
- package/src/prompts/tools/web-fetch.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/utils/image-convert.ts +8 -2
- package/src/utils/image-magick.ts +247 -0
- package/src/utils/image-resize.ts +53 -13
- package/examples/custom-tools/question/index.ts +0 -84
- package/examples/custom-tools/subagent/README.md +0 -172
- package/examples/custom-tools/subagent/agents/planner.md +0 -37
- package/examples/custom-tools/subagent/agents/scout.md +0 -50
- package/examples/custom-tools/subagent/agents/worker.md +0 -24
- package/examples/custom-tools/subagent/agents.ts +0 -156
- package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
- package/examples/custom-tools/subagent/commands/implement.md +0 -10
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
- package/examples/custom-tools/subagent/index.ts +0 -1002
- package/examples/sdk/05-tools.ts +0 -94
- package/examples/sdk/12-full-control.ts +0 -95
- package/src/prompts/browser.md +0 -71
|
@@ -204,6 +204,247 @@ export function formatMoreItems(remaining: number, itemType: string, theme: Them
|
|
|
204
204
|
return `${theme.format.ellipsis} ${safeRemaining} more ${pluralize(itemType, safeRemaining)}`;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
export function formatMeta(meta: string[], theme: Theme): string {
|
|
208
|
+
return meta.length > 0 ? ` ${theme.fg("muted", meta.join(theme.sep.dot))}` : "";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function formatScope(scopePath: string | undefined, theme: Theme): string {
|
|
212
|
+
return scopePath ? ` ${theme.fg("muted", `in ${scopePath}`)}` : "";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function formatTruncationSuffix(truncated: boolean, theme: Theme): string {
|
|
216
|
+
return truncated ? theme.fg("warning", " (truncated)") : "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function formatErrorMessage(message: string | undefined, theme: Theme): string {
|
|
220
|
+
const clean = (message ?? "").replace(/^Error:\s*/, "").trim();
|
|
221
|
+
return `${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${clean || "Unknown error"}`)}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function formatEmptyMessage(message: string, theme: Theme): string {
|
|
225
|
+
return `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", message)}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// =============================================================================
|
|
229
|
+
// Diagnostic Formatting
|
|
230
|
+
// =============================================================================
|
|
231
|
+
|
|
232
|
+
interface ParsedDiagnostic {
|
|
233
|
+
filePath: string;
|
|
234
|
+
line: number;
|
|
235
|
+
col: number;
|
|
236
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
237
|
+
source?: string;
|
|
238
|
+
message: string;
|
|
239
|
+
code?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parseDiagnosticMessage(msg: string): ParsedDiagnostic | null {
|
|
243
|
+
const match = msg.match(/^(.+?):(\d+):(\d+)\s+\[(\w+)\]\s+(?:\[([^\]]+)\]\s+)?(.+?)(?:\s+\(([^)]+)\))?$/);
|
|
244
|
+
if (!match) return null;
|
|
245
|
+
return {
|
|
246
|
+
filePath: match[1],
|
|
247
|
+
line: parseInt(match[2], 10),
|
|
248
|
+
col: parseInt(match[3], 10),
|
|
249
|
+
severity: match[4] as ParsedDiagnostic["severity"],
|
|
250
|
+
source: match[5],
|
|
251
|
+
message: match[6],
|
|
252
|
+
code: match[7],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function formatDiagnostics(
|
|
257
|
+
diag: { errored: boolean; summary: string; messages: string[] },
|
|
258
|
+
expanded: boolean,
|
|
259
|
+
theme: Theme,
|
|
260
|
+
getLangIcon: (filePath: string) => string,
|
|
261
|
+
): string {
|
|
262
|
+
if (diag.messages.length === 0) return "";
|
|
263
|
+
|
|
264
|
+
const byFile = new Map<string, ParsedDiagnostic[]>();
|
|
265
|
+
const unparsed: string[] = [];
|
|
266
|
+
|
|
267
|
+
for (const msg of diag.messages) {
|
|
268
|
+
const parsed = parseDiagnosticMessage(msg);
|
|
269
|
+
if (parsed) {
|
|
270
|
+
const existing = byFile.get(parsed.filePath) ?? [];
|
|
271
|
+
existing.push(parsed);
|
|
272
|
+
byFile.set(parsed.filePath, existing);
|
|
273
|
+
} else {
|
|
274
|
+
unparsed.push(msg);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const headerIcon = diag.errored
|
|
279
|
+
? theme.styledSymbol("status.error", "error")
|
|
280
|
+
: theme.styledSymbol("status.warning", "warning");
|
|
281
|
+
let output = `\n\n${headerIcon} ${theme.fg("toolTitle", "Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
|
|
282
|
+
|
|
283
|
+
const maxDiags = expanded ? diag.messages.length : 5;
|
|
284
|
+
let shown = 0;
|
|
285
|
+
|
|
286
|
+
const files = Array.from(byFile.entries());
|
|
287
|
+
for (let fi = 0; fi < files.length && shown < maxDiags; fi++) {
|
|
288
|
+
const [filePath, diagnostics] = files[fi];
|
|
289
|
+
const isLastFile = fi === files.length - 1 && unparsed.length === 0;
|
|
290
|
+
const fileBranch = isLastFile ? theme.tree.last : theme.tree.branch;
|
|
291
|
+
|
|
292
|
+
const fileIcon = theme.fg("muted", getLangIcon(filePath));
|
|
293
|
+
output += `\n ${theme.fg("dim", fileBranch)} ${fileIcon} ${theme.fg("accent", filePath)}`;
|
|
294
|
+
shown++;
|
|
295
|
+
|
|
296
|
+
for (let di = 0; di < diagnostics.length && shown < maxDiags; di++) {
|
|
297
|
+
const d = diagnostics[di];
|
|
298
|
+
const isLastDiag = di === diagnostics.length - 1;
|
|
299
|
+
const diagBranch = isLastFile
|
|
300
|
+
? isLastDiag
|
|
301
|
+
? ` ${theme.tree.last}`
|
|
302
|
+
: ` ${theme.tree.branch}`
|
|
303
|
+
: isLastDiag
|
|
304
|
+
? ` ${theme.tree.vertical} ${theme.tree.last}`
|
|
305
|
+
: ` ${theme.tree.vertical} ${theme.tree.branch}`;
|
|
306
|
+
|
|
307
|
+
const sevIcon =
|
|
308
|
+
d.severity === "error"
|
|
309
|
+
? theme.styledSymbol("status.error", "error")
|
|
310
|
+
: d.severity === "warning"
|
|
311
|
+
? theme.styledSymbol("status.warning", "warning")
|
|
312
|
+
: theme.styledSymbol("status.info", "muted");
|
|
313
|
+
const location = theme.fg("dim", `:${d.line}:${d.col}`);
|
|
314
|
+
const codeTag = d.code ? theme.fg("dim", ` (${d.code})`) : "";
|
|
315
|
+
const msgColor = d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "toolOutput";
|
|
316
|
+
|
|
317
|
+
output += `\n ${theme.fg("dim", diagBranch)} ${sevIcon}${location} ${theme.fg(msgColor, d.message)}${codeTag}`;
|
|
318
|
+
shown++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
for (const msg of unparsed) {
|
|
323
|
+
if (shown >= maxDiags) break;
|
|
324
|
+
const color = msg.includes("[error]") ? "error" : msg.includes("[warning]") ? "warning" : "dim";
|
|
325
|
+
output += `\n ${theme.fg("dim", theme.tree.branch)} ${theme.fg(color, msg)}`;
|
|
326
|
+
shown++;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (diag.messages.length > shown) {
|
|
330
|
+
const remaining = diag.messages.length - shown;
|
|
331
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", `${theme.format.ellipsis} ${remaining} more`)} ${theme.fg("dim", "(Ctrl+O to expand)")}`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return output;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// =============================================================================
|
|
338
|
+
// Diff Utilities
|
|
339
|
+
// =============================================================================
|
|
340
|
+
|
|
341
|
+
export interface DiffStats {
|
|
342
|
+
added: number;
|
|
343
|
+
removed: number;
|
|
344
|
+
hunks: number;
|
|
345
|
+
lines: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function getDiffStats(diffText: string): DiffStats {
|
|
349
|
+
const lines = diffText ? diffText.split("\n") : [];
|
|
350
|
+
let added = 0;
|
|
351
|
+
let removed = 0;
|
|
352
|
+
let hunks = 0;
|
|
353
|
+
let inHunk = false;
|
|
354
|
+
|
|
355
|
+
for (const line of lines) {
|
|
356
|
+
const isAdded = line.startsWith("+");
|
|
357
|
+
const isRemoved = line.startsWith("-");
|
|
358
|
+
const isChange = isAdded || isRemoved;
|
|
359
|
+
|
|
360
|
+
if (isAdded) added++;
|
|
361
|
+
if (isRemoved) removed++;
|
|
362
|
+
|
|
363
|
+
if (isChange && !inHunk) {
|
|
364
|
+
hunks++;
|
|
365
|
+
inHunk = true;
|
|
366
|
+
} else if (!isChange) {
|
|
367
|
+
inHunk = false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { added, removed, hunks, lines: lines.length };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function formatDiffStats(added: number, removed: number, hunks: number, theme: Theme): string {
|
|
375
|
+
const parts: string[] = [];
|
|
376
|
+
if (added > 0) parts.push(theme.fg("success", `+${added}`));
|
|
377
|
+
if (removed > 0) parts.push(theme.fg("error", `-${removed}`));
|
|
378
|
+
if (hunks > 0) parts.push(theme.fg("dim", `${hunks} hunk${hunks !== 1 ? "s" : ""}`));
|
|
379
|
+
return parts.join(theme.fg("dim", " / "));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function truncateDiffByHunk(
|
|
383
|
+
diffText: string,
|
|
384
|
+
maxHunks: number,
|
|
385
|
+
maxLines: number,
|
|
386
|
+
): { text: string; hiddenHunks: number; hiddenLines: number } {
|
|
387
|
+
const lines = diffText ? diffText.split("\n") : [];
|
|
388
|
+
const totalStats = getDiffStats(diffText);
|
|
389
|
+
const kept: string[] = [];
|
|
390
|
+
let inHunk = false;
|
|
391
|
+
let currentHunks = 0;
|
|
392
|
+
let reachedLimit = false;
|
|
393
|
+
|
|
394
|
+
for (const line of lines) {
|
|
395
|
+
const isChange = line.startsWith("+") || line.startsWith("-");
|
|
396
|
+
if (isChange && !inHunk) {
|
|
397
|
+
currentHunks++;
|
|
398
|
+
inHunk = true;
|
|
399
|
+
}
|
|
400
|
+
if (!isChange) {
|
|
401
|
+
inHunk = false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (currentHunks > maxHunks) {
|
|
405
|
+
reachedLimit = true;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
kept.push(line);
|
|
410
|
+
if (kept.length >= maxLines) {
|
|
411
|
+
reachedLimit = true;
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!reachedLimit) {
|
|
417
|
+
return { text: diffText, hiddenHunks: 0, hiddenLines: 0 };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const keptStats = getDiffStats(kept.join("\n"));
|
|
421
|
+
return {
|
|
422
|
+
text: kept.join("\n"),
|
|
423
|
+
hiddenHunks: Math.max(0, totalStats.hunks - keptStats.hunks),
|
|
424
|
+
hiddenLines: Math.max(0, totalStats.lines - kept.length),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// =============================================================================
|
|
429
|
+
// Path Utilities
|
|
430
|
+
// =============================================================================
|
|
431
|
+
|
|
432
|
+
export function shortenPath(filePath: string, homeDir?: string): string {
|
|
433
|
+
const home = homeDir ?? process.env.HOME ?? process.env.USERPROFILE;
|
|
434
|
+
if (home && filePath.startsWith(home)) {
|
|
435
|
+
return `~${filePath.slice(home.length)}`;
|
|
436
|
+
}
|
|
437
|
+
return filePath;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function wrapBrackets(text: string, theme: Theme): string {
|
|
441
|
+
return `${theme.format.bracketLeft}${text}${theme.format.bracketRight}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function replaceTabs(text: string): string {
|
|
445
|
+
return text.replace(/\t/g, " ");
|
|
446
|
+
}
|
|
447
|
+
|
|
207
448
|
function pluralize(label: string, count: number): string {
|
|
208
449
|
if (count === 1) return label;
|
|
209
450
|
if (/(?:ch|sh|s|x|z)$/i.test(label)) return `${label}es`;
|