@oh-my-pi/pi-coding-agent 12.18.3 → 12.19.2
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 +53 -0
- package/package.json +35 -27
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +341 -0
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +3 -17
- package/src/cli/stats-cli.ts +3 -22
- package/src/cli/web-search-cli.ts +8 -16
- package/src/commit/agentic/agent.ts +6 -9
- package/src/commit/agentic/index.ts +44 -50
- package/src/commit/agentic/state.ts +0 -9
- package/src/commit/agentic/tools/propose-commit.ts +1 -30
- package/src/commit/agentic/tools/schemas.ts +31 -0
- package/src/commit/agentic/tools/split-commit.ts +1 -30
- package/src/commit/agentic/validation.ts +1 -18
- package/src/commit/analysis/conventional.ts +3 -50
- package/src/commit/analysis/summary.ts +2 -13
- package/src/commit/changelog/detect.ts +4 -1
- package/src/commit/changelog/generate.ts +2 -25
- package/src/commit/changelog/index.ts +1 -2
- package/src/commit/cli.ts +4 -12
- package/src/commit/map-reduce/reduce-phase.ts +2 -43
- package/src/commit/pipeline.ts +7 -15
- package/src/commit/utils.ts +44 -0
- package/src/config/prompt-templates.ts +1 -81
- package/src/config/settings-schema.ts +20 -1
- package/src/config.ts +2 -3
- package/src/debug/index.ts +1 -6
- package/src/debug/system-info.ts +2 -6
- package/src/discovery/builtin.ts +5 -9
- package/src/discovery/helpers.ts +0 -26
- package/src/discovery/ssh.ts +1 -8
- package/src/exa/company.ts +8 -39
- package/src/exa/factory.ts +64 -0
- package/src/exa/index.ts +0 -16
- package/src/exa/linkedin.ts +8 -39
- package/src/exa/mcp-client.ts +0 -64
- package/src/exa/researcher.ts +17 -59
- package/src/exa/search.ts +30 -154
- package/src/extensibility/custom-tools/loader.ts +3 -41
- package/src/extensibility/extensions/loader.ts +2 -9
- package/src/extensibility/hooks/loader.ts +3 -20
- package/src/extensibility/hooks/runner.ts +3 -19
- package/src/extensibility/plugins/installer.ts +2 -1
- package/src/extensibility/plugins/loader.ts +29 -117
- package/src/extensibility/skills.ts +2 -89
- package/src/extensibility/slash-commands.ts +1 -63
- package/src/extensibility/utils.ts +38 -0
- package/src/index.ts +9 -25
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/jobs-protocol.ts +118 -0
- package/src/ipy/kernel.ts +2 -0
- package/src/lsp/config.ts +1 -5
- package/src/lsp/lspmux.ts +0 -17
- package/src/lsp/utils.ts +2 -24
- package/src/main.ts +16 -24
- package/src/mcp/client.ts +1 -46
- package/src/mcp/render.ts +8 -1
- package/src/mcp/tool-cache.ts +1 -5
- package/src/mcp/transports/http.ts +2 -7
- package/src/mcp/transports/stdio.ts +2 -7
- package/src/modes/components/bash-execution.ts +2 -16
- package/src/modes/components/extensions/inspector-panel.ts +8 -18
- package/src/modes/components/footer.ts +10 -50
- package/src/modes/components/model-selector.ts +2 -21
- package/src/modes/components/python-execution.ts +2 -16
- package/src/modes/components/settings-selector.ts +1 -10
- package/src/modes/components/status-line/segments.ts +8 -25
- package/src/modes/components/status-line.ts +14 -31
- package/src/modes/components/tool-execution.ts +8 -2
- package/src/modes/controllers/command-controller.ts +71 -30
- package/src/modes/controllers/event-controller.ts +34 -4
- package/src/modes/controllers/mcp-command-controller.ts +3 -34
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/controllers/ssh-command-controller.ts +3 -34
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-client.ts +1 -5
- package/src/modes/shared.ts +73 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +26 -2
- package/src/patch/hashline.ts +6 -286
- package/src/patch/index.ts +6 -57
- package/src/patch/normalize.ts +22 -65
- package/src/patch/shared.ts +16 -16
- package/src/prompts/system/custom-system-prompt.md +0 -10
- package/src/prompts/system/system-prompt.md +69 -89
- package/src/prompts/tools/async-result.md +5 -0
- package/src/prompts/tools/bash.md +5 -0
- package/src/prompts/tools/cancel-job.md +7 -0
- package/src/prompts/tools/hashline.md +0 -16
- package/src/prompts/tools/poll-jobs.md +7 -0
- package/src/prompts/tools/task.md +4 -0
- package/src/sdk.ts +70 -6
- package/src/session/agent-session.ts +43 -6
- package/src/session/agent-storage.ts +69 -278
- package/src/session/auth-storage.ts +14 -1430
- package/src/session/session-manager.ts +69 -5
- package/src/session/session-storage.ts +1 -5
- package/src/session/streaming-output.ts +637 -76
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/ssh/connection-manager.ts +4 -12
- package/src/ssh/sshfs-mount.ts +3 -7
- package/src/ssh/utils.ts +8 -0
- package/src/system-prompt.ts +24 -90
- package/src/task/executor.ts +11 -1
- package/src/task/index.ts +258 -13
- package/src/task/parallel.ts +32 -0
- package/src/task/render.ts +15 -7
- package/src/task/types.ts +5 -0
- package/src/tools/ask.ts +4 -7
- package/src/tools/bash-interactive.ts +4 -5
- package/src/tools/bash.ts +125 -41
- package/src/tools/cancel-job.ts +93 -0
- package/src/tools/fetch.ts +7 -27
- package/src/tools/find.ts +3 -3
- package/src/tools/gemini-image.ts +15 -14
- package/src/tools/grep.ts +3 -3
- package/src/tools/index.ts +13 -29
- package/src/tools/json-tree.ts +12 -1
- package/src/tools/jtd-to-json-schema.ts +10 -74
- package/src/tools/jtd-to-typescript.ts +10 -72
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/notebook.ts +4 -9
- package/src/tools/output-meta.ts +52 -26
- package/src/tools/path-utils.ts +13 -7
- package/src/tools/poll-jobs.ts +178 -0
- package/src/tools/python.ts +32 -35
- package/src/tools/read.ts +61 -82
- package/src/tools/render-utils.ts +8 -159
- package/src/tools/ssh.ts +7 -20
- package/src/tools/submit-result.ts +1 -1
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/tool-result.ts +1 -2
- package/src/tools/write.ts +8 -10
- package/src/tui/code-cell.ts +8 -3
- package/src/tui/status-line.ts +4 -4
- package/src/tui/types.ts +0 -1
- package/src/tui/utils.ts +1 -14
- package/src/utils/command-args.ts +76 -0
- package/src/utils/file-mentions.ts +15 -19
- package/src/utils/frontmatter.ts +5 -10
- package/src/utils/shell-snapshot.ts +0 -11
- package/src/utils/title-generator.ts +0 -12
- package/src/web/scrapers/artifacthub.ts +7 -16
- package/src/web/scrapers/arxiv.ts +3 -8
- package/src/web/scrapers/aur.ts +8 -22
- package/src/web/scrapers/biorxiv.ts +5 -14
- package/src/web/scrapers/bluesky.ts +13 -36
- package/src/web/scrapers/brew.ts +5 -10
- package/src/web/scrapers/cheatsh.ts +2 -12
- package/src/web/scrapers/chocolatey.ts +63 -26
- package/src/web/scrapers/choosealicense.ts +3 -18
- package/src/web/scrapers/cisa-kev.ts +4 -18
- package/src/web/scrapers/clojars.ts +6 -33
- package/src/web/scrapers/coingecko.ts +25 -33
- package/src/web/scrapers/crates-io.ts +7 -26
- package/src/web/scrapers/crossref.ts +4 -18
- package/src/web/scrapers/devto.ts +11 -41
- package/src/web/scrapers/discogs.ts +7 -10
- package/src/web/scrapers/discourse.ts +6 -31
- package/src/web/scrapers/dockerhub.ts +12 -35
- package/src/web/scrapers/fdroid.ts +8 -33
- package/src/web/scrapers/firefox-addons.ts +10 -34
- package/src/web/scrapers/flathub.ts +7 -24
- package/src/web/scrapers/github-gist.ts +2 -12
- package/src/web/scrapers/github.ts +9 -47
- package/src/web/scrapers/gitlab.ts +130 -185
- package/src/web/scrapers/go-pkg.ts +12 -22
- package/src/web/scrapers/hackage.ts +88 -43
- package/src/web/scrapers/hackernews.ts +25 -45
- package/src/web/scrapers/hex.ts +19 -36
- package/src/web/scrapers/huggingface.ts +26 -91
- package/src/web/scrapers/iacr.ts +3 -8
- package/src/web/scrapers/jetbrains-marketplace.ts +9 -20
- package/src/web/scrapers/lemmy.ts +5 -23
- package/src/web/scrapers/lobsters.ts +16 -28
- package/src/web/scrapers/mastodon.ts +24 -43
- package/src/web/scrapers/maven.ts +6 -21
- package/src/web/scrapers/mdn.ts +7 -11
- package/src/web/scrapers/metacpan.ts +9 -41
- package/src/web/scrapers/musicbrainz.ts +4 -28
- package/src/web/scrapers/npm.ts +8 -25
- package/src/web/scrapers/nuget.ts +14 -37
- package/src/web/scrapers/nvd.ts +6 -28
- package/src/web/scrapers/ollama.ts +7 -34
- package/src/web/scrapers/open-vsx.ts +5 -19
- package/src/web/scrapers/opencorporates.ts +30 -14
- package/src/web/scrapers/openlibrary.ts +49 -33
- package/src/web/scrapers/orcid.ts +4 -18
- package/src/web/scrapers/osv.ts +7 -24
- package/src/web/scrapers/packagist.ts +9 -24
- package/src/web/scrapers/pub-dev.ts +7 -50
- package/src/web/scrapers/pubmed.ts +54 -21
- package/src/web/scrapers/pypi.ts +8 -26
- package/src/web/scrapers/rawg.ts +11 -19
- package/src/web/scrapers/readthedocs.ts +4 -9
- package/src/web/scrapers/reddit.ts +5 -15
- package/src/web/scrapers/repology.ts +8 -20
- package/src/web/scrapers/rfc.ts +5 -14
- package/src/web/scrapers/rubygems.ts +6 -21
- package/src/web/scrapers/searchcode.ts +8 -36
- package/src/web/scrapers/sec-edgar.ts +4 -18
- package/src/web/scrapers/semantic-scholar.ts +15 -35
- package/src/web/scrapers/snapcraft.ts +5 -19
- package/src/web/scrapers/sourcegraph.ts +5 -43
- package/src/web/scrapers/spdx.ts +4 -18
- package/src/web/scrapers/spotify.ts +4 -23
- package/src/web/scrapers/stackoverflow.ts +8 -13
- package/src/web/scrapers/terraform.ts +9 -37
- package/src/web/scrapers/tldr.ts +3 -7
- package/src/web/scrapers/twitter.ts +3 -7
- package/src/web/scrapers/types.ts +105 -27
- package/src/web/scrapers/utils.ts +97 -103
- package/src/web/scrapers/vimeo.ts +7 -27
- package/src/web/scrapers/vscode-marketplace.ts +8 -17
- package/src/web/scrapers/w3c.ts +6 -14
- package/src/web/scrapers/wikidata.ts +5 -19
- package/src/web/scrapers/wikipedia.ts +2 -12
- package/src/web/scrapers/youtube.ts +5 -34
- package/src/web/search/index.ts +0 -9
- package/src/web/search/providers/anthropic.ts +3 -2
- package/src/web/search/providers/brave.ts +3 -18
- package/src/web/search/providers/exa.ts +1 -12
- package/src/web/search/providers/kimi.ts +5 -44
- package/src/web/search/providers/perplexity.ts +1 -12
- package/src/web/search/providers/synthetic.ts +3 -26
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +9 -50
- package/src/web/search/types.ts +0 -28
- package/src/web/search/utils.ts +17 -0
- package/src/tools/output-utils.ts +0 -63
- package/src/tools/truncate.ts +0 -385
- package/src/web/search/auth.ts +0 -178
package/src/lsp/utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { truncate } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
1
3
|
import path from "node:path";
|
|
2
4
|
import { type Theme, theme } from "../modes/theme/theme";
|
|
3
5
|
import type {
|
|
@@ -523,27 +525,3 @@ export function extractHoverText(
|
|
|
523
525
|
|
|
524
526
|
// =============================================================================
|
|
525
527
|
// General Utilities
|
|
526
|
-
// =============================================================================
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Truncate a string to a maximum length with ellipsis.
|
|
530
|
-
*/
|
|
531
|
-
export function truncate(str: string, maxLength: number): string {
|
|
532
|
-
if (str.length <= maxLength) return str;
|
|
533
|
-
return `${str.slice(0, maxLength - 1)}…`;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Group items by a key function.
|
|
538
|
-
*/
|
|
539
|
-
export function groupBy<T, K extends string | number>(items: T[], keyFn: (item: T) => K): Record<K, T[]> {
|
|
540
|
-
const result = {} as Record<K, T[]>;
|
|
541
|
-
for (const item of items) {
|
|
542
|
-
const key = keyFn(item);
|
|
543
|
-
if (!result[key]) {
|
|
544
|
-
result[key] = [];
|
|
545
|
-
}
|
|
546
|
-
result[key].push(item);
|
|
547
|
-
}
|
|
548
|
-
return result;
|
|
549
|
-
}
|
package/src/main.ts
CHANGED
|
@@ -51,14 +51,6 @@ async function checkForNewVersion(currentVersion: string): Promise<string | unde
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const writeStdout = (message: string): void => {
|
|
55
|
-
process.stdout.write(`${message}\n`);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const writeStderr = (message: string): void => {
|
|
59
|
-
process.stderr.write(`${message}\n`);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
54
|
async function readPipedInput(): Promise<string | undefined> {
|
|
63
55
|
if (process.stdin.isTTY !== false) return undefined;
|
|
64
56
|
try {
|
|
@@ -381,7 +373,7 @@ async function buildSessionOptions(
|
|
|
381
373
|
preferences: modelMatchPreferences,
|
|
382
374
|
});
|
|
383
375
|
if (resolved.warning) {
|
|
384
|
-
|
|
376
|
+
process.stderr.write(`${chalk.yellow(`Warning: ${resolved.warning}`)}\n`);
|
|
385
377
|
}
|
|
386
378
|
if (resolved.error) {
|
|
387
379
|
if (!parsed.provider && !parsed.model.includes(":")) {
|
|
@@ -389,7 +381,7 @@ async function buildSessionOptions(
|
|
|
389
381
|
// (extensions may register additional providers/models via registerProvider)
|
|
390
382
|
options.modelPattern = parsed.model;
|
|
391
383
|
} else {
|
|
392
|
-
|
|
384
|
+
process.stderr.write(`${chalk.red(resolved.error)}\n`);
|
|
393
385
|
process.exit(1);
|
|
394
386
|
}
|
|
395
387
|
} else if (resolved.model) {
|
|
@@ -514,7 +506,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
514
506
|
});
|
|
515
507
|
|
|
516
508
|
if (parsedArgs.version) {
|
|
517
|
-
|
|
509
|
+
process.stdout.write(`${VERSION}\n`);
|
|
518
510
|
process.exit(0);
|
|
519
511
|
}
|
|
520
512
|
|
|
@@ -531,15 +523,15 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
531
523
|
result = await exportFromFile(parsedArgs.export, outputPath);
|
|
532
524
|
} catch (error: unknown) {
|
|
533
525
|
const message = error instanceof Error ? error.message : "Failed to export session";
|
|
534
|
-
|
|
526
|
+
process.stderr.write(`${chalk.red(`Error: ${message}`)}\n`);
|
|
535
527
|
process.exit(1);
|
|
536
528
|
}
|
|
537
|
-
|
|
529
|
+
process.stdout.write(`Exported to: ${result}\n`);
|
|
538
530
|
process.exit(0);
|
|
539
531
|
}
|
|
540
532
|
|
|
541
533
|
if (parsedArgs.mode === "rpc" && parsedArgs.fileArgs.length > 0) {
|
|
542
|
-
|
|
534
|
+
process.stderr.write(`${chalk.red("Error: @file arguments are not supported in RPC mode")}\n`);
|
|
543
535
|
process.exit(1);
|
|
544
536
|
}
|
|
545
537
|
|
|
@@ -614,12 +606,12 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
614
606
|
SessionManager.list(cwd, parsedArgs.sessionDir),
|
|
615
607
|
);
|
|
616
608
|
if (sessions.length === 0) {
|
|
617
|
-
|
|
609
|
+
process.stdout.write(`${chalk.dim("No sessions found")}\n`);
|
|
618
610
|
return;
|
|
619
611
|
}
|
|
620
612
|
const selectedPath = await logger.timeAsync("selectSession", () => selectSession(sessions));
|
|
621
613
|
if (!selectedPath) {
|
|
622
|
-
|
|
614
|
+
process.stdout.write(`${chalk.dim("No session selected")}\n`);
|
|
623
615
|
return;
|
|
624
616
|
}
|
|
625
617
|
sessionManager = await SessionManager.open(selectedPath);
|
|
@@ -635,8 +627,8 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
635
627
|
// Handle CLI --api-key as runtime override (not persisted)
|
|
636
628
|
if (parsedArgs.apiKey) {
|
|
637
629
|
if (!sessionOptions.model && !sessionOptions.modelPattern) {
|
|
638
|
-
|
|
639
|
-
chalk.red("--api-key requires a model to be specified via --model, --provider/--model, or --models")
|
|
630
|
+
process.stderr.write(
|
|
631
|
+
`${chalk.red("--api-key requires a model to be specified via --model, --provider/--model, or --models")}\n`,
|
|
640
632
|
);
|
|
641
633
|
process.exit(1);
|
|
642
634
|
}
|
|
@@ -689,13 +681,13 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
689
681
|
|
|
690
682
|
if (!isInteractive && !session.model) {
|
|
691
683
|
if (modelFallbackMessage) {
|
|
692
|
-
|
|
684
|
+
process.stderr.write(`${chalk.red(modelFallbackMessage)}\n`);
|
|
693
685
|
} else {
|
|
694
|
-
|
|
686
|
+
process.stderr.write(`${chalk.red("No models available.")}\n`);
|
|
695
687
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
688
|
+
process.stderr.write(`${chalk.yellow("\nSet an API key environment variable:")}\n`);
|
|
689
|
+
process.stderr.write(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\n");
|
|
690
|
+
process.stderr.write(`${chalk.yellow(`\nOr create ${ModelsConfigFile.path()}`)}\n`);
|
|
699
691
|
process.exit(1);
|
|
700
692
|
}
|
|
701
693
|
|
|
@@ -728,7 +720,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
728
720
|
return `${scopedModel.model.id}${thinkingStr}`;
|
|
729
721
|
})
|
|
730
722
|
.join(", ");
|
|
731
|
-
|
|
723
|
+
process.stdout.write(`${chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`)}\n`);
|
|
732
724
|
}
|
|
733
725
|
|
|
734
726
|
if ($env.PI_TIMING === "1") {
|
package/src/mcp/client.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles connection initialization, tool listing, and tool calling.
|
|
5
5
|
*/
|
|
6
|
+
import { withTimeout } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import { createHttpTransport } from "./transports/http";
|
|
7
8
|
import { createStdioTransport } from "./transports/stdio";
|
|
8
9
|
import type {
|
|
@@ -34,52 +35,6 @@ const CLIENT_INFO = {
|
|
|
34
35
|
version: "1.0.0",
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
/** Wrap a promise with a timeout and optional abort signal */
|
|
38
|
-
function withTimeout<T>(promise: Promise<T>, ms: number, message: string, signal?: AbortSignal): Promise<T> {
|
|
39
|
-
if (signal?.aborted) {
|
|
40
|
-
const reason = signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
41
|
-
return Promise.reject(reason);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const { promise: wrapped, resolve, reject } = Promise.withResolvers<T>();
|
|
45
|
-
let settled = false;
|
|
46
|
-
const timeoutId = setTimeout(() => {
|
|
47
|
-
if (settled) return;
|
|
48
|
-
settled = true;
|
|
49
|
-
reject(new Error(message));
|
|
50
|
-
}, ms);
|
|
51
|
-
|
|
52
|
-
const onAbort = () => {
|
|
53
|
-
if (settled) return;
|
|
54
|
-
settled = true;
|
|
55
|
-
clearTimeout(timeoutId);
|
|
56
|
-
reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
if (signal) {
|
|
60
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
promise.then(
|
|
64
|
-
value => {
|
|
65
|
-
if (settled) return;
|
|
66
|
-
settled = true;
|
|
67
|
-
clearTimeout(timeoutId);
|
|
68
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
69
|
-
resolve(value);
|
|
70
|
-
},
|
|
71
|
-
error => {
|
|
72
|
-
if (settled) return;
|
|
73
|
-
settled = true;
|
|
74
|
-
clearTimeout(timeoutId);
|
|
75
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
76
|
-
reject(error);
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
return wrapped;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
38
|
/**
|
|
84
39
|
* Create a transport for the given server config.
|
|
85
40
|
*/
|
package/src/mcp/render.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
JSON_TREE_SCALAR_LEN_COLLAPSED,
|
|
18
18
|
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
19
19
|
renderJsonTreeLines,
|
|
20
|
+
stripInternalArgs,
|
|
20
21
|
} from "../tools/json-tree";
|
|
21
22
|
import { formatExpandHint, truncateToWidth } from "../tools/render-utils";
|
|
22
23
|
import { renderStatusLine } from "../tui";
|
|
@@ -57,7 +58,13 @@ export function renderMCPResult(
|
|
|
57
58
|
lines.push(`${theme.fg("dim", "Args")}`);
|
|
58
59
|
const maxDepth = JSON_TREE_MAX_DEPTH_EXPANDED;
|
|
59
60
|
const maxLines = JSON_TREE_MAX_LINES_EXPANDED;
|
|
60
|
-
const tree = renderJsonTreeLines(
|
|
61
|
+
const tree = renderJsonTreeLines(
|
|
62
|
+
stripInternalArgs(args),
|
|
63
|
+
theme,
|
|
64
|
+
maxDepth,
|
|
65
|
+
maxLines,
|
|
66
|
+
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
67
|
+
);
|
|
61
68
|
for (const line of tree.lines) {
|
|
62
69
|
lines.push(line);
|
|
63
70
|
}
|
package/src/mcp/tool-cache.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Stores tool definitions per server in agent.db for fast startup.
|
|
5
5
|
*/
|
|
6
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import type { AgentStorage } from "../session/agent-storage";
|
|
8
8
|
import type { MCPServerConfig, MCPToolDefinition } from "./types";
|
|
9
9
|
|
|
@@ -17,10 +17,6 @@ type MCPToolCachePayload = {
|
|
|
17
17
|
tools: MCPToolDefinition[];
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
21
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
function stableClone(value: unknown): unknown {
|
|
25
21
|
if (Array.isArray(value)) {
|
|
26
22
|
return value.map(item => stableClone(item));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements JSON-RPC 2.0 over HTTP POST with optional SSE streaming.
|
|
5
5
|
* Based on MCP spec 2025-03-26.
|
|
6
6
|
*/
|
|
7
|
-
import { readSseJson } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { readSseJson, Snowflake } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import type {
|
|
9
9
|
JsonRpcMessage,
|
|
10
10
|
JsonRpcResponse,
|
|
@@ -14,11 +14,6 @@ import type {
|
|
|
14
14
|
MCPTransport,
|
|
15
15
|
} from "../../mcp/types";
|
|
16
16
|
|
|
17
|
-
/** Generate unique request ID */
|
|
18
|
-
function generateId(): string {
|
|
19
|
-
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/**
|
|
23
18
|
* HTTP transport for MCP servers.
|
|
24
19
|
* Uses POST for requests, supports SSE responses.
|
|
@@ -112,7 +107,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
112
107
|
throw new Error("Transport not connected");
|
|
113
108
|
}
|
|
114
109
|
|
|
115
|
-
const id =
|
|
110
|
+
const id = Snowflake.next();
|
|
116
111
|
const body = {
|
|
117
112
|
jsonrpc: "2.0" as const,
|
|
118
113
|
id,
|
|
@@ -5,16 +5,11 @@
|
|
|
5
5
|
* Messages are newline-delimited JSON.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readJsonl } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
10
10
|
import { type Subprocess, spawn } from "bun";
|
|
11
11
|
import type { JsonRpcResponse, MCPRequestOptions, MCPStdioServerConfig, MCPTransport } from "../../mcp/types";
|
|
12
12
|
|
|
13
|
-
/** Generate unique request ID */
|
|
14
|
-
function generateId(): string {
|
|
15
|
-
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
/**
|
|
19
14
|
* Stdio transport for MCP servers.
|
|
20
15
|
* Spawns a subprocess and communicates via stdin/stdout.
|
|
@@ -156,7 +151,7 @@ export class StdioTransport implements MCPTransport {
|
|
|
156
151
|
throw new Error("Transport not connected");
|
|
157
152
|
}
|
|
158
153
|
|
|
159
|
-
const id =
|
|
154
|
+
const id = Snowflake.next();
|
|
160
155
|
const request = {
|
|
161
156
|
jsonrpc: "2.0" as const,
|
|
162
157
|
id,
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
6
6
|
import { Container, Loader, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
8
|
-
import type
|
|
9
|
-
import { formatSize } from "../../tools/truncate";
|
|
8
|
+
import { formatTruncationMetaNotice, type TruncationMeta } from "../../tools/output-meta";
|
|
10
9
|
import { DynamicBorder } from "./dynamic-border";
|
|
11
10
|
import { truncateToVisualLines } from "./visual-truncate";
|
|
12
11
|
|
|
@@ -167,20 +166,7 @@ export class BashExecutionComponent extends Container {
|
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
if (this.#truncation) {
|
|
170
|
-
|
|
171
|
-
if (this.#truncation.artifactId) {
|
|
172
|
-
warnings.push(`Full output: artifact://${this.#truncation.artifactId}`);
|
|
173
|
-
}
|
|
174
|
-
if (this.#truncation.truncatedBy === "lines") {
|
|
175
|
-
warnings.push(
|
|
176
|
-
`Truncated: showing ${this.#truncation.outputLines} of ${this.#truncation.totalLines} lines`,
|
|
177
|
-
);
|
|
178
|
-
} else {
|
|
179
|
-
warnings.push(
|
|
180
|
-
`Truncated: ${this.#truncation.outputLines} lines shown (${formatSize(this.#truncation.outputBytes)} limit)`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
statusParts.push(theme.fg("warning", warnings.join(". ")));
|
|
169
|
+
statusParts.push(theme.fg("warning", formatTruncationMetaNotice(this.#truncation)));
|
|
184
170
|
}
|
|
185
171
|
|
|
186
172
|
if (statusParts.length > 0) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import { type Component, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { theme } from "../../../modes/theme/theme";
|
|
9
|
+
import { shortenPath } from "../../../tools/render-utils";
|
|
9
10
|
import type { Extension, ExtensionState } from "./types";
|
|
10
11
|
|
|
11
12
|
export class InspectorPanel implements Component {
|
|
@@ -46,7 +47,13 @@ export class InspectorPanel implements Component {
|
|
|
46
47
|
lines.push(theme.fg("muted", "Origin:"));
|
|
47
48
|
const levelLabel = ext.source.level === "user" ? "User" : ext.source.level === "project" ? "Project" : "Native";
|
|
48
49
|
lines.push(` ${theme.italic(`via ${ext.source.providerName} (${levelLabel})`)}`);
|
|
49
|
-
|
|
50
|
+
const shortened = shortenPath(ext.path, os.homedir());
|
|
51
|
+
// If path is very long, show just the last parts
|
|
52
|
+
const displayPath =
|
|
53
|
+
shortened.length > 40 && shortened.split("/").length > 3
|
|
54
|
+
? `.../${shortened.split("/").slice(-3).join("/")}`
|
|
55
|
+
: shortened;
|
|
56
|
+
lines.push(` ${theme.fg("dim", displayPath)}`);
|
|
50
57
|
lines.push("");
|
|
51
58
|
|
|
52
59
|
// Status badge
|
|
@@ -301,21 +308,4 @@ export class InspectorPanel implements Component {
|
|
|
301
308
|
return theme.fg("warning", `${theme.status.shadowed} Shadowed${shadowedBy ? ` by ${shadowedBy}` : ""}`);
|
|
302
309
|
}
|
|
303
310
|
}
|
|
304
|
-
|
|
305
|
-
#shortenPath(path: string): string {
|
|
306
|
-
const home = os.homedir();
|
|
307
|
-
if (home && path.startsWith(home)) {
|
|
308
|
-
return `~${path.slice(home.length)}`;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// If path is very long, show just the last parts
|
|
312
|
-
if (path.length > 40) {
|
|
313
|
-
const parts = path.split("/");
|
|
314
|
-
if (parts.length > 3) {
|
|
315
|
-
return `.../${parts.slice(-3).join("/")}`;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return path;
|
|
320
|
-
}
|
|
321
311
|
}
|
|
@@ -1,42 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
2
|
import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
4
|
-
import {
|
|
3
|
+
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
5
4
|
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
6
5
|
import { theme } from "../../modes/theme/theme";
|
|
7
6
|
import type { AgentSession } from "../../session/agent-session";
|
|
8
7
|
import { shortenPath } from "../../tools/render-utils";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Sanitize text for display in a single-line status.
|
|
12
|
-
* Removes newlines, tabs, carriage returns, and other control characters.
|
|
13
|
-
*/
|
|
14
|
-
function sanitizeStatusText(text: string): string {
|
|
15
|
-
// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces
|
|
16
|
-
return text
|
|
17
|
-
.replace(/[\r\n\t]/g, " ")
|
|
18
|
-
.replace(/ +/g, " ")
|
|
19
|
-
.trim();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Find the git root by walking up from cwd. Returns path and content of .git/HEAD if found. */
|
|
23
|
-
async function findGitHeadPath(): Promise<{ path: string; content: string } | null> {
|
|
24
|
-
let dir = getProjectDir();
|
|
25
|
-
while (true) {
|
|
26
|
-
const gitHeadPath = path.join(dir, ".git", "HEAD");
|
|
27
|
-
try {
|
|
28
|
-
const content = await Bun.file(gitHeadPath).text();
|
|
29
|
-
return { path: gitHeadPath, content };
|
|
30
|
-
} catch (err) {
|
|
31
|
-
if (!isEnoent(err)) throw err;
|
|
32
|
-
}
|
|
33
|
-
const parent = path.dirname(dir);
|
|
34
|
-
if (parent === dir) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
dir = parent;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
8
|
+
import { findGitHeadPathAsync, sanitizeStatusText } from "../shared";
|
|
40
9
|
|
|
41
10
|
/**
|
|
42
11
|
* Footer component that shows pwd, token stats, and context usage
|
|
@@ -85,7 +54,7 @@ export class FooterComponent implements Component {
|
|
|
85
54
|
this.#gitWatcher = null;
|
|
86
55
|
}
|
|
87
56
|
|
|
88
|
-
|
|
57
|
+
findGitHeadPathAsync().then(result => {
|
|
89
58
|
if (!result) {
|
|
90
59
|
return;
|
|
91
60
|
}
|
|
@@ -130,7 +99,7 @@ export class FooterComponent implements Component {
|
|
|
130
99
|
|
|
131
100
|
// Note: fire-and-forget async call - will return undefined on first call
|
|
132
101
|
// This is acceptable since it's a cached value that will update on next render
|
|
133
|
-
|
|
102
|
+
findGitHeadPathAsync().then(result => {
|
|
134
103
|
if (!result) {
|
|
135
104
|
this.#cachedBranch = null;
|
|
136
105
|
if (this.#onBranchChange) {
|
|
@@ -181,15 +150,6 @@ export class FooterComponent implements Component {
|
|
|
181
150
|
const contextPercentValue = contextUsage?.percent ?? 0;
|
|
182
151
|
const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
183
152
|
|
|
184
|
-
// Format token counts (similar to web-ui)
|
|
185
|
-
const formatTokens = (count: number): string => {
|
|
186
|
-
if (count < 1000) return count.toString();
|
|
187
|
-
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
188
|
-
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
189
|
-
if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
|
|
190
|
-
return `${Math.round(count / 1000000)}M`;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
153
|
// Replace home directory with ~
|
|
194
154
|
let pwd = shortenPath(getProjectDir());
|
|
195
155
|
|
|
@@ -213,10 +173,10 @@ export class FooterComponent implements Component {
|
|
|
213
173
|
|
|
214
174
|
// Build stats line
|
|
215
175
|
const statsParts = [];
|
|
216
|
-
if (totalInput) statsParts.push(`↑${
|
|
217
|
-
if (totalOutput) statsParts.push(`↓${
|
|
218
|
-
if (totalCacheRead) statsParts.push(`R${
|
|
219
|
-
if (totalCacheWrite) statsParts.push(`W${
|
|
176
|
+
if (totalInput) statsParts.push(`↑${formatNumber(totalInput)}`);
|
|
177
|
+
if (totalOutput) statsParts.push(`↓${formatNumber(totalOutput)}`);
|
|
178
|
+
if (totalCacheRead) statsParts.push(`R${formatNumber(totalCacheRead)}`);
|
|
179
|
+
if (totalCacheWrite) statsParts.push(`W${formatNumber(totalCacheWrite)}`);
|
|
220
180
|
|
|
221
181
|
// Show cost with "(sub)" indicator if using OAuth subscription
|
|
222
182
|
const usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;
|
|
@@ -230,8 +190,8 @@ export class FooterComponent implements Component {
|
|
|
230
190
|
const autoIndicator = this.#autoCompactEnabled ? " (auto)" : "";
|
|
231
191
|
const contextPercentDisplay =
|
|
232
192
|
contextPercent === "?"
|
|
233
|
-
? `?/${
|
|
234
|
-
: `${contextPercent}%/${
|
|
193
|
+
? `?/${formatNumber(contextWindow)}${autoIndicator}`
|
|
194
|
+
: `${contextPercent}%/${formatNumber(contextWindow)}${autoIndicator}`;
|
|
235
195
|
if (contextPercentValue > 90) {
|
|
236
196
|
contextPercentStr = theme.fg("error", contextPercentDisplay);
|
|
237
197
|
} else if (contextPercentValue > 70) {
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import { type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
2
|
-
import {
|
|
3
|
-
Container,
|
|
4
|
-
Input,
|
|
5
|
-
matchesKey,
|
|
6
|
-
Spacer,
|
|
7
|
-
type Tab,
|
|
8
|
-
TabBar,
|
|
9
|
-
type TabBarTheme,
|
|
10
|
-
Text,
|
|
11
|
-
type TUI,
|
|
12
|
-
visibleWidth,
|
|
13
|
-
} from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { Container, Input, matchesKey, Spacer, type Tab, TabBar, Text, type TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
14
3
|
import { MODEL_ROLE_IDS, MODEL_ROLES, type ModelRegistry, type ModelRole } from "../../config/model-registry";
|
|
15
4
|
import { parseModelString } from "../../config/model-resolver";
|
|
16
5
|
import type { Settings } from "../../config/settings";
|
|
17
6
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
18
7
|
import { fuzzyFilter } from "../../utils/fuzzy";
|
|
8
|
+
import { getTabBarTheme } from "../shared";
|
|
19
9
|
import { DynamicBorder } from "./dynamic-border";
|
|
20
10
|
|
|
21
11
|
function makeInvertedBadge(label: string, color: ThemeColor): string {
|
|
@@ -44,15 +34,6 @@ const MENU_ACTIONS: MenuAction[] = MODEL_ROLE_IDS.map(role => ({ label: `Set as
|
|
|
44
34
|
|
|
45
35
|
const ALL_TAB = "ALL";
|
|
46
36
|
|
|
47
|
-
function getTabBarTheme(): TabBarTheme {
|
|
48
|
-
return {
|
|
49
|
-
label: (text: string) => theme.bold(theme.fg("accent", text)),
|
|
50
|
-
activeTab: (text: string) => theme.bold(theme.bg("selectedBg", theme.fg("text", text))),
|
|
51
|
-
inactiveTab: (text: string) => theme.fg("muted", text),
|
|
52
|
-
hint: (text: string) => theme.fg("dim", text),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
37
|
/**
|
|
57
38
|
* Component that renders a model selector with provider tabs and context menu.
|
|
58
39
|
* - Tab/Arrow Left/Right: Switch between provider tabs
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
7
7
|
import { Container, Loader, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { getSymbolTheme, highlightCode, theme } from "../../modes/theme/theme";
|
|
9
|
-
import type
|
|
10
|
-
import { formatSize } from "../../tools/truncate";
|
|
9
|
+
import { formatTruncationMetaNotice, type TruncationMeta } from "../../tools/output-meta";
|
|
11
10
|
import { DynamicBorder } from "./dynamic-border";
|
|
12
11
|
import { truncateToVisualLines } from "./visual-truncate";
|
|
13
12
|
|
|
@@ -151,20 +150,7 @@ export class PythonExecutionComponent extends Container {
|
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
if (this.#truncation) {
|
|
154
|
-
|
|
155
|
-
if (this.#truncation.artifactId) {
|
|
156
|
-
warnings.push(`Full output: artifact://${this.#truncation.artifactId}`);
|
|
157
|
-
}
|
|
158
|
-
if (this.#truncation.truncatedBy === "lines") {
|
|
159
|
-
warnings.push(
|
|
160
|
-
`Truncated: showing ${this.#truncation.outputLines} of ${this.#truncation.totalLines} lines`,
|
|
161
|
-
);
|
|
162
|
-
} else {
|
|
163
|
-
warnings.push(
|
|
164
|
-
`Truncated: ${this.#truncation.outputLines} lines shown (${formatSize(this.#truncation.outputBytes)} limit)`,
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
statusParts.push(theme.fg("warning", warnings.join(". ")));
|
|
153
|
+
statusParts.push(theme.fg("warning", formatTruncationMetaNotice(this.#truncation)));
|
|
168
154
|
}
|
|
169
155
|
|
|
170
156
|
if (statusParts.length > 0) {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
Spacer,
|
|
10
10
|
type Tab,
|
|
11
11
|
TabBar,
|
|
12
|
-
type TabBarTheme,
|
|
13
12
|
Text,
|
|
14
13
|
} from "@oh-my-pi/pi-tui";
|
|
15
14
|
import { type SettingPath, settings } from "../../config/settings";
|
|
@@ -21,20 +20,12 @@ import type {
|
|
|
21
20
|
} from "../../config/settings-schema";
|
|
22
21
|
import { SETTING_TABS, TAB_METADATA } from "../../config/settings-schema";
|
|
23
22
|
import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
|
|
23
|
+
import { getTabBarTheme } from "../shared";
|
|
24
24
|
import { DynamicBorder } from "./dynamic-border";
|
|
25
25
|
import { PluginSettingsComponent } from "./plugin-settings";
|
|
26
26
|
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
27
27
|
import { getPreset } from "./status-line/presets";
|
|
28
28
|
|
|
29
|
-
function getTabBarTheme(): TabBarTheme {
|
|
30
|
-
return {
|
|
31
|
-
label: text => theme.bold(theme.fg("accent", text)),
|
|
32
|
-
activeTab: text => theme.bold(theme.bg("selectedBg", theme.fg("text", text))),
|
|
33
|
-
inactiveTab: text => theme.fg("muted", text),
|
|
34
|
-
hint: text => theme.fg("dim", text),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
29
|
/**
|
|
39
30
|
* A submenu component for selecting from a list of options.
|
|
40
31
|
*/
|