@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/commit/cli.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function parseCommitArgs(args: string[]): CommitCommandArgs | undefined {
|
|
|
36
36
|
case "--context": {
|
|
37
37
|
const value = args[i + 1];
|
|
38
38
|
if (!value || value.startsWith("-")) {
|
|
39
|
-
|
|
39
|
+
process.stderr.write(`${chalk.red("Error: --context requires a value")}\n`);
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
result.context = value;
|
|
@@ -46,7 +46,7 @@ export function parseCommitArgs(args: string[]): CommitCommandArgs | undefined {
|
|
|
46
46
|
case "--model": {
|
|
47
47
|
const value = args[i + 1];
|
|
48
48
|
if (!value || value.startsWith("-")) {
|
|
49
|
-
|
|
49
|
+
process.stderr.write(`${chalk.red("Error: --model requires a value")}\n`);
|
|
50
50
|
process.exit(1);
|
|
51
51
|
}
|
|
52
52
|
result.model = value;
|
|
@@ -58,7 +58,7 @@ export function parseCommitArgs(args: string[]): CommitCommandArgs | undefined {
|
|
|
58
58
|
break;
|
|
59
59
|
default:
|
|
60
60
|
if (flag.startsWith("-")) {
|
|
61
|
-
|
|
61
|
+
process.stderr.write(`${chalk.red(`Error: Unknown flag ${flag}`)}\n`);
|
|
62
62
|
process.exit(1);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -81,13 +81,5 @@ export function printCommitHelp(): void {
|
|
|
81
81
|
" --model, -m Override model selection",
|
|
82
82
|
" --help, -h Show this help message",
|
|
83
83
|
];
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function writeStdout(message: string): void {
|
|
88
|
-
process.stdout.write(`${message}\n`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function writeStderr(message: string): void {
|
|
92
|
-
process.stderr.write(`${message}\n`);
|
|
84
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
93
85
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Api, AssistantMessage, Model
|
|
1
|
+
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { type: "text" };
|
|
5
5
|
import reduceUserPrompt from "../../commit/prompts/reduce-user.md" with { type: "text" };
|
|
6
6
|
import type { ChangelogCategory, ConventionalAnalysis, FileObservation } from "../../commit/types";
|
|
7
7
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
8
|
+
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
|
|
8
9
|
|
|
9
10
|
const ReduceTool = {
|
|
10
11
|
name: "create_conventional_analysis",
|
|
@@ -101,45 +102,3 @@ function parseAnalysisResponse(message: AssistantMessage): ConventionalAnalysis
|
|
|
101
102
|
};
|
|
102
103
|
return normalizeAnalysis(parsed);
|
|
103
104
|
}
|
|
104
|
-
|
|
105
|
-
function parseJsonPayload(text: string): unknown {
|
|
106
|
-
const trimmed = text.trim();
|
|
107
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
108
|
-
return JSON.parse(trimmed) as unknown;
|
|
109
|
-
}
|
|
110
|
-
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
111
|
-
if (!match) {
|
|
112
|
-
throw new Error("No JSON payload found in reduce response");
|
|
113
|
-
}
|
|
114
|
-
return JSON.parse(match[0]) as unknown;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function normalizeAnalysis(parsed: {
|
|
118
|
-
type: ConventionalAnalysis["type"];
|
|
119
|
-
scope: string | null;
|
|
120
|
-
details: Array<{ text: string; changelog_category?: ChangelogCategory; user_visible?: boolean }>;
|
|
121
|
-
issue_refs: string[];
|
|
122
|
-
}): ConventionalAnalysis {
|
|
123
|
-
return {
|
|
124
|
-
type: parsed.type,
|
|
125
|
-
scope: parsed.scope?.trim() || null,
|
|
126
|
-
details: parsed.details.map(detail => ({
|
|
127
|
-
text: detail.text.trim(),
|
|
128
|
-
changelogCategory: detail.user_visible ? detail.changelog_category : undefined,
|
|
129
|
-
userVisible: detail.user_visible ?? false,
|
|
130
|
-
})),
|
|
131
|
-
issueRefs: parsed.issue_refs ?? [],
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function extractToolCall(message: AssistantMessage, name: string): ToolCall | undefined {
|
|
136
|
-
return message.content.find(content => content.type === "toolCall" && content.name === name) as ToolCall | undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function extractTextContent(message: AssistantMessage): string {
|
|
140
|
-
return message.content
|
|
141
|
-
.filter(content => content.type === "text")
|
|
142
|
-
.map(content => content.text)
|
|
143
|
-
.join("")
|
|
144
|
-
.trim();
|
|
145
|
-
}
|
package/src/commit/pipeline.ts
CHANGED
|
@@ -61,12 +61,12 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
61
61
|
const git = new ControlledGit(cwd);
|
|
62
62
|
let stagedFiles = await git.getStagedFiles();
|
|
63
63
|
if (stagedFiles.length === 0) {
|
|
64
|
-
|
|
64
|
+
process.stdout.write("No staged changes detected, staging all changes...\n");
|
|
65
65
|
await git.stageAll();
|
|
66
66
|
stagedFiles = await git.getStagedFiles();
|
|
67
67
|
}
|
|
68
68
|
if (stagedFiles.length === 0) {
|
|
69
|
-
|
|
69
|
+
process.stderr.write("No changes to commit.\n");
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -123,16 +123,16 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
123
123
|
const commitMessage = formatCommitMessage(analysis, summary.summary);
|
|
124
124
|
|
|
125
125
|
if (args.dryRun) {
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
process.stdout.write("\nGenerated commit message:\n");
|
|
127
|
+
process.stdout.write(`${commitMessage}\n`);
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
await git.commit(commitMessage);
|
|
132
|
-
|
|
132
|
+
process.stdout.write("Commit created.\n");
|
|
133
133
|
if (args.push) {
|
|
134
134
|
await git.push();
|
|
135
|
-
|
|
135
|
+
process.stdout.write("Pushed to remote.\n");
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -163,7 +163,7 @@ async function generateAnalysis(input: {
|
|
|
163
163
|
maxFileTokens: input.commitSettings.mapReduceMaxFileTokens,
|
|
164
164
|
})
|
|
165
165
|
) {
|
|
166
|
-
|
|
166
|
+
process.stdout.write("Large diff detected, using map-reduce analysis...\n");
|
|
167
167
|
return runMapReduceAnalysis({
|
|
168
168
|
model: input.primaryModel,
|
|
169
169
|
apiKey: input.primaryApiKey,
|
|
@@ -233,11 +233,3 @@ function buildRetryContext(base: string | undefined, errors: string[]): string {
|
|
|
233
233
|
errors: errors.join("; "),
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
|
-
|
|
237
|
-
function writeStdout(message: string): void {
|
|
238
|
-
process.stdout.write(`${message}\n`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function writeStderr(message: string): void {
|
|
242
|
-
process.stderr.write(`${message}\n`);
|
|
243
|
-
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AssistantMessage, ToolCall } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { ChangelogCategory, ConventionalAnalysis } from "./types";
|
|
3
|
+
|
|
4
|
+
export function extractToolCall(message: AssistantMessage, name: string): ToolCall | undefined {
|
|
5
|
+
return message.content.find(content => content.type === "toolCall" && content.name === name) as ToolCall | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function extractTextContent(message: AssistantMessage): string {
|
|
9
|
+
return message.content
|
|
10
|
+
.filter(content => content.type === "text")
|
|
11
|
+
.map(content => content.text)
|
|
12
|
+
.join("")
|
|
13
|
+
.trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseJsonPayload(text: string): unknown {
|
|
17
|
+
const trimmed = text.trim();
|
|
18
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
19
|
+
return JSON.parse(trimmed) as unknown;
|
|
20
|
+
}
|
|
21
|
+
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
22
|
+
if (!match) {
|
|
23
|
+
throw new Error("No JSON payload found in response");
|
|
24
|
+
}
|
|
25
|
+
return JSON.parse(match[0]) as unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function normalizeAnalysis(parsed: {
|
|
29
|
+
type: ConventionalAnalysis["type"];
|
|
30
|
+
scope: string | null;
|
|
31
|
+
details: Array<{ text: string; changelog_category?: ChangelogCategory; user_visible?: boolean }>;
|
|
32
|
+
issue_refs: string[];
|
|
33
|
+
}): ConventionalAnalysis {
|
|
34
|
+
return {
|
|
35
|
+
type: parsed.type,
|
|
36
|
+
scope: parsed.scope?.trim() || null,
|
|
37
|
+
details: parsed.details.map(detail => ({
|
|
38
|
+
text: detail.text.trim(),
|
|
39
|
+
changelogCategory: detail.user_visible ? detail.changelog_category : undefined,
|
|
40
|
+
userVisible: detail.user_visible ?? false,
|
|
41
|
+
})),
|
|
42
|
+
issueRefs: parsed.issue_refs ?? [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -5,6 +5,7 @@ import { getProjectDir, getProjectPromptsDir, getPromptsDir } from "@oh-my-pi/pi
|
|
|
5
5
|
import Handlebars from "handlebars";
|
|
6
6
|
import { computeLineHash } from "../patch/hashline";
|
|
7
7
|
import { jtdToTypeScript } from "../tools/jtd-to-typescript";
|
|
8
|
+
import { parseCommandArgs, substituteArgs } from "../utils/command-args";
|
|
8
9
|
import { parseFrontmatter } from "../utils/frontmatter";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -313,87 +314,6 @@ function optimizePromptLayout(input: string): string {
|
|
|
313
314
|
return s.trim();
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
/**
|
|
317
|
-
* Parse command arguments respecting quoted strings (bash-style)
|
|
318
|
-
* Returns array of arguments
|
|
319
|
-
*/
|
|
320
|
-
export function parseCommandArgs(argsString: string): string[] {
|
|
321
|
-
const args: string[] = [];
|
|
322
|
-
let current = "";
|
|
323
|
-
let inQuote: string | null = null;
|
|
324
|
-
|
|
325
|
-
for (let i = 0; i < argsString.length; i++) {
|
|
326
|
-
const char = argsString[i];
|
|
327
|
-
|
|
328
|
-
if (inQuote) {
|
|
329
|
-
if (char === inQuote) {
|
|
330
|
-
inQuote = null;
|
|
331
|
-
} else {
|
|
332
|
-
current += char;
|
|
333
|
-
}
|
|
334
|
-
} else if (char === '"' || char === "'") {
|
|
335
|
-
inQuote = char;
|
|
336
|
-
} else if (char === " " || char === "\t") {
|
|
337
|
-
if (current) {
|
|
338
|
-
args.push(current);
|
|
339
|
-
current = "";
|
|
340
|
-
}
|
|
341
|
-
} else {
|
|
342
|
-
current += char;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (current) {
|
|
347
|
-
args.push(current);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return args;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Substitute argument placeholders in template content
|
|
355
|
-
* Supports $1, $2, ... for positional args, $@ and $ARGUMENTS for all args
|
|
356
|
-
*
|
|
357
|
-
* Note: Replacement happens on the template string only. Argument values
|
|
358
|
-
* containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
|
|
359
|
-
*/
|
|
360
|
-
export function substituteArgs(content: string, args: string[]): string {
|
|
361
|
-
let result = content;
|
|
362
|
-
|
|
363
|
-
// Replace $1, $2, etc. with positional args FIRST (before wildcards)
|
|
364
|
-
// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted
|
|
365
|
-
result = result.replace(/\$(\d+)/g, (_, num) => {
|
|
366
|
-
const index = parseInt(num, 10) - 1;
|
|
367
|
-
return args[index] ?? "";
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
result = result.replace(/\$@\[(\d+)(?::(\d*)?)?\]/g, (_, startRaw: string, lengthRaw?: string) => {
|
|
371
|
-
const start = Number.parseInt(startRaw, 10);
|
|
372
|
-
if (!Number.isFinite(start) || start < 1) return "";
|
|
373
|
-
const startIndex = start - 1;
|
|
374
|
-
if (startIndex >= args.length) return "";
|
|
375
|
-
|
|
376
|
-
if (lengthRaw === undefined || lengthRaw === "") {
|
|
377
|
-
return args.slice(startIndex).join(" ");
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const length = Number.parseInt(lengthRaw, 10);
|
|
381
|
-
if (!Number.isFinite(length) || length <= 0) return "";
|
|
382
|
-
return args.slice(startIndex, startIndex + length).join(" ");
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
// Pre-compute all args joined (optimization)
|
|
386
|
-
const allArgs = args.join(" ");
|
|
387
|
-
|
|
388
|
-
// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)
|
|
389
|
-
result = result.replace(/\$ARGUMENTS/g, allArgs);
|
|
390
|
-
|
|
391
|
-
// Replace $@ with all args joined (existing syntax)
|
|
392
|
-
result = result.replace(/\$@/g, allArgs);
|
|
393
|
-
|
|
394
|
-
return result;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
317
|
/**
|
|
398
318
|
* Recursively scan a directory for .md files (and symlinks to .md files) and load them as prompt templates
|
|
399
319
|
*/
|
|
@@ -403,7 +403,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
403
403
|
// ─────────────────────────────────────────────────────────────────────────
|
|
404
404
|
"todo.reminders": {
|
|
405
405
|
type: "boolean",
|
|
406
|
-
default:
|
|
406
|
+
default: true,
|
|
407
407
|
ui: { tab: "agent", label: "Todo reminders", description: "Remind agent to complete todos before stopping" },
|
|
408
408
|
},
|
|
409
409
|
"todo.reminders.max": {
|
|
@@ -511,6 +511,25 @@ export const SETTINGS_SCHEMA = {
|
|
|
511
511
|
description: "Ask the agent to describe the intent of each tool call before executing it",
|
|
512
512
|
},
|
|
513
513
|
},
|
|
514
|
+
"async.enabled": {
|
|
515
|
+
type: "boolean",
|
|
516
|
+
default: false,
|
|
517
|
+
ui: {
|
|
518
|
+
tab: "tools",
|
|
519
|
+
label: "Async execution",
|
|
520
|
+
description: "Enable async bash commands and background task execution",
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
"async.maxJobs": {
|
|
524
|
+
type: "number",
|
|
525
|
+
default: 15,
|
|
526
|
+
ui: {
|
|
527
|
+
tab: "tools",
|
|
528
|
+
label: "Async max jobs",
|
|
529
|
+
description: "Maximum concurrent background jobs (1-100)",
|
|
530
|
+
submenu: true,
|
|
531
|
+
},
|
|
532
|
+
},
|
|
514
533
|
|
|
515
534
|
// ─────────────────────────────────────────────────────────────────────────
|
|
516
535
|
// Task tool settings
|
package/src/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { TSchema } from "@sinclair/typebox";
|
|
|
7
7
|
import { Value } from "@sinclair/typebox/value";
|
|
8
8
|
import { Ajv, type ErrorObject, type ValidateFunction } from "ajv";
|
|
9
9
|
import { JSONC, YAML } from "bun";
|
|
10
|
+
import { expandTilde } from "./tools/path-utils";
|
|
10
11
|
|
|
11
12
|
const priorityList = [
|
|
12
13
|
{ dir: CONFIG_DIR_NAME, globalAgentDir: `${CONFIG_DIR_NAME}/agent` },
|
|
@@ -27,9 +28,7 @@ export function getPackageDir(): string {
|
|
|
27
28
|
// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
|
|
28
29
|
const envDir = process.env.PI_PACKAGE_DIR;
|
|
29
30
|
if (envDir) {
|
|
30
|
-
|
|
31
|
-
if (envDir.startsWith("~/")) return os.homedir() + envDir.slice(1);
|
|
32
|
-
return envDir;
|
|
31
|
+
return expandTilde(envDir);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
let dir = import.meta.dir;
|
package/src/debug/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { getSessionsDir } from "@oh-my-pi/pi-utils/dirs";
|
|
|
11
11
|
import { DynamicBorder } from "../modes/components/dynamic-border";
|
|
12
12
|
import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme";
|
|
13
13
|
import type { InteractiveModeContext } from "../modes/types";
|
|
14
|
+
import { formatBytes } from "../tools/render-utils";
|
|
14
15
|
import { openPath } from "../utils/open";
|
|
15
16
|
import { DebugLogViewerComponent } from "./log-viewer";
|
|
16
17
|
import { generateHeapSnapshotData, type ProfilerSession, startCpuProfile } from "./profiler";
|
|
@@ -417,12 +418,6 @@ export class DebugSelectorComponent extends Container {
|
|
|
417
418
|
}
|
|
418
419
|
}
|
|
419
420
|
|
|
420
|
-
function formatBytes(bytes: number): string {
|
|
421
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
422
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
423
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
421
|
/**
|
|
427
422
|
* Show the debug selector.
|
|
428
423
|
*/
|
package/src/debug/system-info.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System information collection for debug reports.
|
|
3
3
|
*/
|
|
4
|
+
|
|
4
5
|
import * as os from "node:os";
|
|
6
|
+
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
5
7
|
import { getProjectDir, VERSION } from "@oh-my-pi/pi-utils/dirs";
|
|
6
8
|
|
|
7
9
|
export interface SystemInfo {
|
|
@@ -71,12 +73,6 @@ export async function collectSystemInfo(): Promise<SystemInfo> {
|
|
|
71
73
|
};
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
/** Format bytes to human-readable string */
|
|
75
|
-
function formatBytes(bytes: number): string {
|
|
76
|
-
const gb = bytes / (1024 * 1024 * 1024);
|
|
77
|
-
return `${gb.toFixed(1)} GB`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
/** Format system info for display */
|
|
81
77
|
export function formatSystemInfo(info: SystemInfo): string {
|
|
82
78
|
const lines = [
|
package/src/discovery/builtin.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { type SlashCommand, slashCommandCapability } from "../capability/slash-c
|
|
|
21
21
|
import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
|
|
22
22
|
import { type CustomTool, toolCapability } from "../capability/tool";
|
|
23
23
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
24
|
+
import { expandTilde } from "../tools/path-utils";
|
|
24
25
|
import { parseFrontmatter } from "../utils/frontmatter";
|
|
25
26
|
import {
|
|
26
27
|
buildRuleFromMarkdown,
|
|
@@ -363,16 +364,11 @@ async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<Extens
|
|
|
363
364
|
const warnings: string[] = [];
|
|
364
365
|
|
|
365
366
|
const resolveExtensionPath = (rawPath: string): string => {
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
const expanded = expandTilde(rawPath, ctx.home);
|
|
368
|
+
if (path.isAbsolute(expanded)) {
|
|
369
|
+
return expanded;
|
|
368
370
|
}
|
|
369
|
-
|
|
370
|
-
return path.join(ctx.home, rawPath.slice(1));
|
|
371
|
-
}
|
|
372
|
-
if (path.isAbsolute(rawPath)) {
|
|
373
|
-
return rawPath;
|
|
374
|
-
}
|
|
375
|
-
return path.resolve(ctx.cwd, rawPath);
|
|
371
|
+
return path.resolve(ctx.cwd, expanded);
|
|
376
372
|
};
|
|
377
373
|
|
|
378
374
|
const createExtensionModule = (extPath: string, level: "user" | "project"): ExtensionModule => ({
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for discovery providers.
|
|
3
|
-
*/
|
|
4
|
-
import * as os from "node:os";
|
|
5
1
|
import * as path from "node:path";
|
|
6
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
3
|
import { FileType, glob } from "@oh-my-pi/pi-natives";
|
|
@@ -14,28 +10,6 @@ import { parseFrontmatter } from "../utils/frontmatter";
|
|
|
14
10
|
import type { IgnoreMatcher } from "../utils/ignore-files";
|
|
15
11
|
|
|
16
12
|
const VALID_THINKING_LEVELS: readonly string[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
17
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Normalize unicode spaces to regular spaces.
|
|
21
|
-
*/
|
|
22
|
-
export function normalizeUnicodeSpaces(str: string): string {
|
|
23
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Expand ~ to home directory and normalize unicode spaces.
|
|
28
|
-
*/
|
|
29
|
-
export function expandPath(p: string): string {
|
|
30
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
31
|
-
if (normalized.startsWith("~/")) {
|
|
32
|
-
return path.join(os.homedir(), normalized.slice(2));
|
|
33
|
-
}
|
|
34
|
-
if (normalized.startsWith("~")) {
|
|
35
|
-
return path.join(os.homedir(), normalized.slice(1));
|
|
36
|
-
}
|
|
37
|
-
return normalized;
|
|
38
|
-
}
|
|
39
13
|
|
|
40
14
|
/**
|
|
41
15
|
* Standard paths for each config source.
|
package/src/discovery/ssh.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { registerProvider } from "../capability";
|
|
|
10
10
|
import { readFile } from "../capability/fs";
|
|
11
11
|
import { type SSHHost, sshCapability } from "../capability/ssh";
|
|
12
12
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
13
|
+
import { expandTilde } from "../tools/path-utils";
|
|
13
14
|
import { createSourceMeta, expandEnvVarsDeep, parseJSON } from "./helpers";
|
|
14
15
|
|
|
15
16
|
const PROVIDER_ID = "ssh-json";
|
|
@@ -30,14 +31,6 @@ interface SSHConfigFile {
|
|
|
30
31
|
>;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function expandTilde(value: string, home: string): string {
|
|
34
|
-
if (value === "~") return home;
|
|
35
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
36
|
-
return `${home}${value.slice(1)}`;
|
|
37
|
-
}
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
function parsePort(value: number | string | undefined): number | undefined {
|
|
42
35
|
if (value === undefined) return undefined;
|
|
43
36
|
if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
|
package/src/exa/company.ts
CHANGED
|
@@ -5,53 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
8
|
-
import {
|
|
8
|
+
import { createExaTool } from "./factory";
|
|
9
9
|
import type { ExaRenderDetails } from "./types";
|
|
10
10
|
|
|
11
11
|
/** exa_company - Company research */
|
|
12
|
-
export const companyTool: CustomTool<any, ExaRenderDetails> =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
export const companyTool: CustomTool<any, ExaRenderDetails> = createExaTool(
|
|
13
|
+
"exa_company",
|
|
14
|
+
"Exa Company",
|
|
15
|
+
`Research companies using Exa's comprehensive data sources.
|
|
16
16
|
|
|
17
17
|
Returns detailed company information including overview, news, financials, and key people.
|
|
18
18
|
|
|
19
19
|
Parameters:
|
|
20
20
|
- company_name: Name of the company to research (e.g., "OpenAI", "Google", "Y Combinator")`,
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Type.Object({
|
|
23
23
|
company_name: Type.String({ description: "Name of the company to research" }),
|
|
24
24
|
}),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const apiKey = await findApiKey();
|
|
29
|
-
if (!apiKey) {
|
|
30
|
-
return {
|
|
31
|
-
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
32
|
-
details: { error: "EXA_API_KEY not found", toolName: "exa_company" },
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const response = await callExaTool("company_research", params, apiKey);
|
|
36
|
-
|
|
37
|
-
if (isSearchResponse(response)) {
|
|
38
|
-
const formatted = formatSearchResults(response);
|
|
39
|
-
return {
|
|
40
|
-
content: [{ type: "text" as const, text: formatted }],
|
|
41
|
-
details: { response, toolName: "exa_company" },
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
47
|
-
details: { raw: response, toolName: "exa_company" },
|
|
48
|
-
};
|
|
49
|
-
} catch (error) {
|
|
50
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
-
return {
|
|
52
|
-
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
53
|
-
details: { error: message, toolName: "exa_company" },
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
};
|
|
25
|
+
"company_research",
|
|
26
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared factory for creating Exa tools with consistent error handling and response formatting.
|
|
3
|
+
*/
|
|
4
|
+
import type { TObject, TProperties } from "@sinclair/typebox";
|
|
5
|
+
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
6
|
+
import { callExaTool, findApiKey, formatSearchResults, isSearchResponse } from "./mcp-client";
|
|
7
|
+
import type { ExaRenderDetails } from "./types";
|
|
8
|
+
|
|
9
|
+
/** Creates an Exa tool with standardized API key handling, error wrapping, and optional search response formatting. */
|
|
10
|
+
export function createExaTool(
|
|
11
|
+
name: string,
|
|
12
|
+
label: string,
|
|
13
|
+
description: string,
|
|
14
|
+
parameters: TObject<TProperties>,
|
|
15
|
+
mcpToolName: string,
|
|
16
|
+
options?: {
|
|
17
|
+
/** When true, checks isSearchResponse and formats with formatSearchResults. Default: true */
|
|
18
|
+
formatResponse?: boolean;
|
|
19
|
+
/** Transform params before passing to callExaTool */
|
|
20
|
+
transformParams?: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
21
|
+
},
|
|
22
|
+
): CustomTool<any, ExaRenderDetails> {
|
|
23
|
+
const formatResponse = options?.formatResponse ?? true;
|
|
24
|
+
const transformParams = options?.transformParams;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
label,
|
|
29
|
+
description,
|
|
30
|
+
parameters,
|
|
31
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
32
|
+
try {
|
|
33
|
+
const apiKey = await findApiKey();
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
37
|
+
details: { error: "EXA_API_KEY not found", toolName: name },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const args = transformParams ? transformParams(params as Record<string, unknown>) : params;
|
|
41
|
+
const response = await callExaTool(mcpToolName, args, apiKey);
|
|
42
|
+
|
|
43
|
+
if (formatResponse && isSearchResponse(response)) {
|
|
44
|
+
const formatted = formatSearchResults(response);
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text" as const, text: formatted }],
|
|
47
|
+
details: { response, toolName: name },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
53
|
+
details: { raw: response, toolName: name },
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
59
|
+
details: { error: message, toolName: name },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/exa/index.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* - 2 researcher tools (start, poll)
|
|
9
9
|
* - 14 websets tools (CRUD, items, search, enrichment, monitor)
|
|
10
10
|
*/
|
|
11
|
-
import type { ExaSettings } from "../config/settings";
|
|
12
11
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
13
12
|
import { companyTool } from "./company";
|
|
14
13
|
import { linkedinTool } from "./linkedin";
|
|
@@ -26,21 +25,6 @@ export const exaTools: CustomTool<any, ExaRenderDetails>[] = [
|
|
|
26
25
|
...websetsTools,
|
|
27
26
|
];
|
|
28
27
|
|
|
29
|
-
/** Get Exa tools filtered by settings */
|
|
30
|
-
export function getExaTools(settings: Required<ExaSettings>): CustomTool<any, ExaRenderDetails>[] {
|
|
31
|
-
if (!settings.enabled) return [];
|
|
32
|
-
|
|
33
|
-
const tools: CustomTool<any, ExaRenderDetails>[] = [];
|
|
34
|
-
|
|
35
|
-
if (settings.enableSearch) tools.push(...searchTools);
|
|
36
|
-
if (settings.enableLinkedin) tools.push(linkedinTool);
|
|
37
|
-
if (settings.enableCompany) tools.push(companyTool);
|
|
38
|
-
if (settings.enableResearcher) tools.push(...researcherTools);
|
|
39
|
-
if (settings.enableWebsets) tools.push(...websetsTools);
|
|
40
|
-
|
|
41
|
-
return tools;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
28
|
export { companyTool } from "./company";
|
|
45
29
|
export { linkedinTool } from "./linkedin";
|
|
46
30
|
export {
|