@oh-my-pi/pi-coding-agent 3.21.0 → 3.25.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 +55 -1
- package/docs/sdk.md +47 -50
- package/examples/custom-tools/README.md +0 -15
- package/examples/hooks/custom-compaction.ts +1 -3
- package/examples/sdk/README.md +6 -10
- package/package.json +5 -5
- package/src/cli/args.ts +9 -6
- package/src/core/agent-session.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +3 -0
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/wrapper.ts +0 -7
- package/src/core/file-mentions.ts +5 -8
- package/src/core/sdk.ts +48 -111
- package/src/core/session-manager.ts +7 -0
- package/src/core/system-prompt.ts +22 -33
- package/src/core/tools/ask.ts +14 -7
- package/src/core/tools/bash-interceptor.ts +4 -4
- package/src/core/tools/bash.ts +19 -9
- package/src/core/tools/complete.ts +131 -0
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit.ts +8 -15
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +7 -18
- package/src/core/tools/git.ts +13 -3
- package/src/core/tools/grep.ts +7 -18
- package/src/core/tools/index.test.ts +188 -0
- package/src/core/tools/index.ts +106 -236
- package/src/core/tools/jtd-to-json-schema.ts +274 -0
- package/src/core/tools/ls.ts +4 -9
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +7 -28
- package/src/core/tools/notebook.ts +3 -5
- package/src/core/tools/output.ts +130 -31
- package/src/core/tools/read.ts +8 -19
- package/src/core/tools/review.ts +0 -18
- package/src/core/tools/rulebook.ts +8 -2
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/artifacts.ts +6 -9
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +306 -257
- package/src/core/tools/task/index.ts +65 -235
- package/src/core/tools/task/name-generator.ts +247 -0
- package/src/core/tools/task/render.ts +158 -19
- package/src/core/tools/task/types.ts +13 -11
- package/src/core/tools/task/worker-protocol.ts +18 -0
- package/src/core/tools/task/worker.ts +270 -0
- package/src/core/tools/web-fetch.ts +4 -36
- package/src/core/tools/web-search/index.ts +2 -1
- package/src/core/tools/web-search/render.ts +1 -4
- package/src/core/tools/write.ts +7 -15
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +4 -4
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/prompts/task.md +14 -57
- package/src/prompts/tools/output.md +4 -3
- package/src/prompts/tools/task.md +70 -0
- 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
package/src/core/tools/ls.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
7
7
|
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
8
8
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
9
9
|
import { untilAborted } from "../utils";
|
|
10
|
+
import type { ToolSession } from "./index";
|
|
10
11
|
import { resolveToCwd } from "./path-utils";
|
|
11
12
|
import {
|
|
12
13
|
formatAge,
|
|
@@ -37,7 +38,7 @@ export interface LsToolDetails {
|
|
|
37
38
|
entryLimitReached?: number;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
export function createLsTool(
|
|
41
|
+
export function createLsTool(session: ToolSession): AgentTool<typeof lsSchema> {
|
|
41
42
|
return {
|
|
42
43
|
name: "ls",
|
|
43
44
|
label: "Ls",
|
|
@@ -49,7 +50,7 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
49
50
|
signal?: AbortSignal,
|
|
50
51
|
) => {
|
|
51
52
|
return untilAborted(signal, async () => {
|
|
52
|
-
const dirPath = resolveToCwd(path || ".", cwd);
|
|
53
|
+
const dirPath = resolveToCwd(path || ".", session.cwd);
|
|
53
54
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
54
55
|
|
|
55
56
|
// Check if path exists
|
|
@@ -159,9 +160,6 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
159
160
|
};
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
/** Default ls tool using process.cwd() - for backwards compatibility */
|
|
163
|
-
export const lsTool = createLsTool(process.cwd());
|
|
164
|
-
|
|
165
163
|
// =============================================================================
|
|
166
164
|
// TUI Renderer
|
|
167
165
|
// =============================================================================
|
|
@@ -271,10 +269,7 @@ export const lsToolRenderer = {
|
|
|
271
269
|
}
|
|
272
270
|
|
|
273
271
|
if (hasTruncation) {
|
|
274
|
-
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
|
275
|
-
"warning",
|
|
276
|
-
`truncated: ${truncationReasons.join(", ")}`,
|
|
277
|
-
)}`;
|
|
272
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("warning", `truncated: ${truncationReasons.join(", ")}`)}`;
|
|
278
273
|
}
|
|
279
274
|
|
|
280
275
|
return new Text(text, 0, 0);
|
|
@@ -7,6 +7,7 @@ import { type Theme, theme } from "../../../modes/interactive/theme/theme";
|
|
|
7
7
|
import lspDescription from "../../../prompts/tools/lsp.md" with { type: "text" };
|
|
8
8
|
import { logger } from "../../logger";
|
|
9
9
|
import { once, untilAborted } from "../../utils";
|
|
10
|
+
import type { ToolSession } from "../index";
|
|
10
11
|
import { resolveToCwd } from "../path-utils";
|
|
11
12
|
import {
|
|
12
13
|
ensureFileOpen,
|
|
@@ -687,7 +688,7 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions
|
|
|
687
688
|
}
|
|
688
689
|
|
|
689
690
|
/** Create an LSP tool */
|
|
690
|
-
export function createLspTool(
|
|
691
|
+
export function createLspTool(session: ToolSession): AgentTool<typeof lspSchema, LspToolDetails, Theme> {
|
|
691
692
|
return {
|
|
692
693
|
name: "lsp",
|
|
693
694
|
label: "LSP",
|
|
@@ -713,7 +714,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
713
714
|
include_declaration,
|
|
714
715
|
} = params;
|
|
715
716
|
|
|
716
|
-
const config = await getConfig(cwd);
|
|
717
|
+
const config = await getConfig(session.cwd);
|
|
717
718
|
|
|
718
719
|
// Status action doesn't need a file
|
|
719
720
|
if (action === "status") {
|
|
@@ -730,7 +731,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
730
731
|
|
|
731
732
|
// Workspace diagnostics - check entire project
|
|
732
733
|
if (action === "workspace_diagnostics") {
|
|
733
|
-
const result = await runWorkspaceDiagnostics(cwd, config);
|
|
734
|
+
const result = await runWorkspaceDiagnostics(session.cwd, config);
|
|
734
735
|
return {
|
|
735
736
|
content: [
|
|
736
737
|
{
|
|
@@ -757,7 +758,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
757
758
|
const allServerNames = new Set<string>();
|
|
758
759
|
|
|
759
760
|
for (const target of targets) {
|
|
760
|
-
const resolved = resolveToCwd(target, cwd);
|
|
761
|
+
const resolved = resolveToCwd(target, session.cwd);
|
|
761
762
|
const servers = getServersForFile(config, resolved);
|
|
762
763
|
if (servers.length === 0) {
|
|
763
764
|
results.push(`${theme.status.error} ${target}: No language server found`);
|
|
@@ -765,7 +766,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
765
766
|
}
|
|
766
767
|
|
|
767
768
|
const uri = fileToUri(resolved);
|
|
768
|
-
const relPath = path.relative(cwd, resolved);
|
|
769
|
+
const relPath = path.relative(session.cwd, resolved);
|
|
769
770
|
const allDiagnostics: Diagnostic[] = [];
|
|
770
771
|
|
|
771
772
|
// Query all applicable servers for this file
|
|
@@ -773,12 +774,12 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
773
774
|
allServerNames.add(serverName);
|
|
774
775
|
try {
|
|
775
776
|
if (serverConfig.createClient) {
|
|
776
|
-
const linterClient = getLinterClient(serverName, serverConfig, cwd);
|
|
777
|
+
const linterClient = getLinterClient(serverName, serverConfig, session.cwd);
|
|
777
778
|
const diagnostics = await linterClient.lint(resolved);
|
|
778
779
|
allDiagnostics.push(...diagnostics);
|
|
779
780
|
continue;
|
|
780
781
|
}
|
|
781
|
-
const client = await getOrCreateClient(serverConfig, cwd);
|
|
782
|
+
const client = await getOrCreateClient(serverConfig, session.cwd);
|
|
782
783
|
await refreshFile(client, resolved);
|
|
783
784
|
const diagnostics = await waitForDiagnostics(client, uri);
|
|
784
785
|
allDiagnostics.push(...diagnostics);
|
|
@@ -847,7 +848,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
847
848
|
};
|
|
848
849
|
}
|
|
849
850
|
|
|
850
|
-
const resolvedFile = file ? resolveToCwd(file, cwd) : null;
|
|
851
|
+
const resolvedFile = file ? resolveToCwd(file, session.cwd) : null;
|
|
851
852
|
const serverInfo = resolvedFile
|
|
852
853
|
? getLspServerForFile(config, resolvedFile)
|
|
853
854
|
: getServerForWorkspaceAction(config, action);
|
|
@@ -862,10 +863,10 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
862
863
|
const [serverName, serverConfig] = serverInfo;
|
|
863
864
|
|
|
864
865
|
try {
|
|
865
|
-
const client = await getOrCreateClient(serverConfig, cwd);
|
|
866
|
+
const client = await getOrCreateClient(serverConfig, session.cwd);
|
|
866
867
|
let targetFile = resolvedFile;
|
|
867
868
|
if (action === "runnables" && !targetFile) {
|
|
868
|
-
targetFile = findFileForServer(cwd, serverConfig);
|
|
869
|
+
targetFile = findFileForServer(session.cwd, serverConfig);
|
|
869
870
|
if (!targetFile) {
|
|
870
871
|
return {
|
|
871
872
|
content: [{ type: "text", text: "Error: no matching files found for runnables" }],
|
|
@@ -914,7 +915,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
914
915
|
output = "No definition found";
|
|
915
916
|
} else {
|
|
916
917
|
output = `Found ${locations.length} definition(s):\n${locations
|
|
917
|
-
.map((loc) => ` ${formatLocation(loc, cwd)}`)
|
|
918
|
+
.map((loc) => ` ${formatLocation(loc, session.cwd)}`)
|
|
918
919
|
.join("\n")}`;
|
|
919
920
|
}
|
|
920
921
|
}
|
|
@@ -931,7 +932,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
931
932
|
if (!result || result.length === 0) {
|
|
932
933
|
output = "No references found";
|
|
933
934
|
} else {
|
|
934
|
-
const lines = result.map((loc) => ` ${formatLocation(loc, cwd)}`);
|
|
935
|
+
const lines = result.map((loc) => ` ${formatLocation(loc, session.cwd)}`);
|
|
935
936
|
output = `Found ${result.length} reference(s):\n${lines.join("\n")}`;
|
|
936
937
|
}
|
|
937
938
|
break;
|
|
@@ -964,7 +965,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
964
965
|
details: { action, serverName, success: false },
|
|
965
966
|
};
|
|
966
967
|
} else {
|
|
967
|
-
const relPath = path.relative(cwd, targetFile);
|
|
968
|
+
const relPath = path.relative(session.cwd, targetFile);
|
|
968
969
|
// Check if hierarchical (DocumentSymbol) or flat (SymbolInformation)
|
|
969
970
|
if ("selectionRange" in result[0]) {
|
|
970
971
|
// Hierarchical
|
|
@@ -998,10 +999,8 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
998
999
|
if (!result || result.length === 0) {
|
|
999
1000
|
output = `No symbols matching "${query}"`;
|
|
1000
1001
|
} else {
|
|
1001
|
-
const lines = result.map((s) => formatSymbolInformation(s, cwd));
|
|
1002
|
-
output = `Found ${result.length} symbol(s) matching "${query}":\n${lines
|
|
1003
|
-
.map((l) => ` ${l}`)
|
|
1004
|
-
.join("\n")}`;
|
|
1002
|
+
const lines = result.map((s) => formatSymbolInformation(s, session.cwd));
|
|
1003
|
+
output = `Found ${result.length} symbol(s) matching "${query}":\n${lines.map((l) => ` ${l}`).join("\n")}`;
|
|
1005
1004
|
}
|
|
1006
1005
|
break;
|
|
1007
1006
|
}
|
|
@@ -1025,10 +1024,10 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1025
1024
|
} else {
|
|
1026
1025
|
const shouldApply = apply !== false;
|
|
1027
1026
|
if (shouldApply) {
|
|
1028
|
-
const applied = await applyWorkspaceEdit(result, cwd);
|
|
1027
|
+
const applied = await applyWorkspaceEdit(result, session.cwd);
|
|
1029
1028
|
output = `Applied rename:\n${applied.map((a) => ` ${a}`).join("\n")}`;
|
|
1030
1029
|
} else {
|
|
1031
|
-
const preview = formatWorkspaceEdit(result, cwd);
|
|
1030
|
+
const preview = formatWorkspaceEdit(result, session.cwd);
|
|
1032
1031
|
output = `Rename preview:\n${preview.map((p) => ` ${p}`).join("\n")}`;
|
|
1033
1032
|
}
|
|
1034
1033
|
}
|
|
@@ -1114,7 +1113,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1114
1113
|
}
|
|
1115
1114
|
|
|
1116
1115
|
if (isCodeAction(resolvedAction) && resolvedAction.edit) {
|
|
1117
|
-
const applied = await applyWorkspaceEdit(resolvedAction.edit, cwd);
|
|
1116
|
+
const applied = await applyWorkspaceEdit(resolvedAction.edit, session.cwd);
|
|
1118
1117
|
output = `Applied "${codeAction.title}":\n${applied.map((a) => ` ${a}`).join("\n")}`;
|
|
1119
1118
|
} else {
|
|
1120
1119
|
const commandPayload = getCommandPayload(resolvedAction);
|
|
@@ -1136,9 +1135,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1136
1135
|
}
|
|
1137
1136
|
return ` [${i}] ${actionItem.title}`;
|
|
1138
1137
|
});
|
|
1139
|
-
output = `Available code actions:\n${lines.join(
|
|
1140
|
-
"\n",
|
|
1141
|
-
)}\n\nUse action_index parameter to apply a specific action.`;
|
|
1138
|
+
output = `Available code actions:\n${lines.join("\n")}\n\nUse action_index parameter to apply a specific action.`;
|
|
1142
1139
|
}
|
|
1143
1140
|
break;
|
|
1144
1141
|
}
|
|
@@ -1169,7 +1166,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1169
1166
|
const lines = calls.map((call) => {
|
|
1170
1167
|
const loc = { uri: call.from.uri, range: call.from.selectionRange };
|
|
1171
1168
|
const detail = call.from.detail ? ` (${call.from.detail})` : "";
|
|
1172
|
-
return ` ${call.from.name}${detail} @ ${formatLocation(loc, cwd)}`;
|
|
1169
|
+
return ` ${call.from.name}${detail} @ ${formatLocation(loc, session.cwd)}`;
|
|
1173
1170
|
});
|
|
1174
1171
|
output = `Found ${calls.length} caller(s) of "${item.name}":\n${lines.join("\n")}`;
|
|
1175
1172
|
}
|
|
@@ -1184,7 +1181,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1184
1181
|
const lines = calls.map((call) => {
|
|
1185
1182
|
const loc = { uri: call.to.uri, range: call.to.selectionRange };
|
|
1186
1183
|
const detail = call.to.detail ? ` (${call.to.detail})` : "";
|
|
1187
|
-
return ` ${call.to.name}${detail} @ ${formatLocation(loc, cwd)}`;
|
|
1184
|
+
return ` ${call.to.name}${detail} @ ${formatLocation(loc, session.cwd)}`;
|
|
1188
1185
|
});
|
|
1189
1186
|
output = `"${item.name}" calls ${calls.length} function(s):\n${lines.join("\n")}`;
|
|
1190
1187
|
}
|
|
@@ -1207,7 +1204,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1207
1204
|
await rustAnalyzer.flycheck(client, resolvedFile ?? undefined);
|
|
1208
1205
|
const collected: Array<{ filePath: string; diagnostic: Diagnostic }> = [];
|
|
1209
1206
|
for (const [diagUri, diags] of client.diagnostics.entries()) {
|
|
1210
|
-
const relPath = path.relative(cwd, uriToFile(diagUri));
|
|
1207
|
+
const relPath = path.relative(session.cwd, uriToFile(diagUri));
|
|
1211
1208
|
for (const diag of diags) {
|
|
1212
1209
|
collected.push({ filePath: relPath, diagnostic: diag });
|
|
1213
1210
|
}
|
|
@@ -1274,13 +1271,13 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1274
1271
|
const result = await rustAnalyzer.ssr(client, query, replacement, !shouldApply);
|
|
1275
1272
|
|
|
1276
1273
|
if (shouldApply) {
|
|
1277
|
-
const applied = await applyWorkspaceEdit(result, cwd);
|
|
1274
|
+
const applied = await applyWorkspaceEdit(result, session.cwd);
|
|
1278
1275
|
output =
|
|
1279
1276
|
applied.length > 0
|
|
1280
1277
|
? `Applied SSR:\n${applied.map((a) => ` ${a}`).join("\n")}`
|
|
1281
1278
|
: "SSR: no matches found";
|
|
1282
1279
|
} else {
|
|
1283
|
-
const preview = formatWorkspaceEdit(result, cwd);
|
|
1280
|
+
const preview = formatWorkspaceEdit(result, session.cwd);
|
|
1284
1281
|
output =
|
|
1285
1282
|
preview.length > 0
|
|
1286
1283
|
? `SSR preview:\n${preview.map((p) => ` ${p}`).join("\n")}`
|
|
@@ -1366,4 +1363,10 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
|
|
|
1366
1363
|
};
|
|
1367
1364
|
}
|
|
1368
1365
|
|
|
1369
|
-
export const lspTool = createLspTool(
|
|
1366
|
+
export const lspTool = createLspTool({
|
|
1367
|
+
cwd: process.cwd(),
|
|
1368
|
+
hasUI: false,
|
|
1369
|
+
rulebookRules: [],
|
|
1370
|
+
getSessionFile: () => null,
|
|
1371
|
+
getSessionSpawns: () => null,
|
|
1372
|
+
});
|
|
@@ -237,10 +237,7 @@ function renderDiagnostics(
|
|
|
237
237
|
}
|
|
238
238
|
const severityColor = severityToColor(item.severity);
|
|
239
239
|
const location = formatDiagnosticLocation(item.file, item.line, item.col, theme);
|
|
240
|
-
output += `\n ${theme.fg("dim", branch)} ${theme.fg(severityColor, location)} ${theme.fg(
|
|
241
|
-
"dim",
|
|
242
|
-
`[${item.severity}]`,
|
|
243
|
-
)}`;
|
|
240
|
+
output += `\n ${theme.fg("dim", branch)} ${theme.fg(severityColor, location)} ${theme.fg("dim", `[${item.severity}]`)}`;
|
|
244
241
|
if (item.message) {
|
|
245
242
|
output += `\n ${theme.fg("dim", detailPrefix)}${theme.fg(
|
|
246
243
|
"muted",
|
|
@@ -274,10 +271,7 @@ function renderDiagnostics(
|
|
|
274
271
|
output += `\n ${theme.fg("dim", branch)} ${theme.fg(severityColor, location)}${message}`;
|
|
275
272
|
}
|
|
276
273
|
if (remaining > 0) {
|
|
277
|
-
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
278
|
-
"muted",
|
|
279
|
-
`${theme.format.ellipsis} ${remaining} more`,
|
|
280
|
-
)}`;
|
|
274
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", `${theme.format.ellipsis} ${remaining} more`)}`;
|
|
281
275
|
}
|
|
282
276
|
|
|
283
277
|
return new Text(output, 0, 0);
|
|
@@ -332,10 +326,7 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
332
326
|
const isLastLoc = li === locsToShow.length - 1 && locs.length <= maxLocsPerFile;
|
|
333
327
|
const locBranch = isLastLoc ? theme.tree.last : theme.tree.branch;
|
|
334
328
|
const locCont = isLastLoc ? " " : `${theme.tree.vertical} `;
|
|
335
|
-
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locBranch)} ${theme.fg(
|
|
336
|
-
"muted",
|
|
337
|
-
`line ${line}, col ${col}`,
|
|
338
|
-
)}`;
|
|
329
|
+
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locBranch)} ${theme.fg("muted", `line ${line}, col ${col}`)}`;
|
|
339
330
|
if (expanded) {
|
|
340
331
|
const context = `at ${file}:${line}:${col}`;
|
|
341
332
|
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locCont)}${theme.fg(
|
|
@@ -354,10 +345,7 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
354
345
|
}
|
|
355
346
|
|
|
356
347
|
if (files.length > maxFiles) {
|
|
357
|
-
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
358
|
-
"muted",
|
|
359
|
-
formatMoreItems(files.length - maxFiles, "file", theme),
|
|
360
|
-
)}`;
|
|
348
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(files.length - maxFiles, "file", theme))}`;
|
|
361
349
|
}
|
|
362
350
|
|
|
363
351
|
return output;
|
|
@@ -463,10 +451,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
463
451
|
)}`;
|
|
464
452
|
}
|
|
465
453
|
if (topLevelCount > 3) {
|
|
466
|
-
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
467
|
-
"muted",
|
|
468
|
-
`${theme.format.ellipsis} ${topLevelCount - 3} more`,
|
|
469
|
-
)}`;
|
|
454
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", `${theme.format.ellipsis} ${topLevelCount - 3} more`)}`;
|
|
470
455
|
}
|
|
471
456
|
|
|
472
457
|
return new Text(output, 0, 0);
|
|
@@ -502,10 +487,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
502
487
|
|
|
503
488
|
const firstLine = lines[0] || "No output";
|
|
504
489
|
const expandHint = formatExpandHint(false, lines.length > 1, theme);
|
|
505
|
-
let output = `${icon} ${theme.fg(
|
|
506
|
-
"dim",
|
|
507
|
-
truncate(firstLine, TRUNCATE_LENGTHS.TITLE, theme.format.ellipsis),
|
|
508
|
-
)}${expandHint}`;
|
|
490
|
+
let output = `${icon} ${theme.fg("dim", truncate(firstLine, TRUNCATE_LENGTHS.TITLE, theme.format.ellipsis))}${expandHint}`;
|
|
509
491
|
|
|
510
492
|
if (lines.length > 1) {
|
|
511
493
|
const previewLines = lines.slice(1, 4);
|
|
@@ -518,10 +500,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
518
500
|
)}`;
|
|
519
501
|
}
|
|
520
502
|
if (lines.length > 4) {
|
|
521
|
-
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
522
|
-
"muted",
|
|
523
|
-
formatMoreItems(lines.length - 4, "line", theme),
|
|
524
|
-
)}`;
|
|
503
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(lines.length - 4, "line", theme))}`;
|
|
525
504
|
}
|
|
526
505
|
}
|
|
527
506
|
|
|
@@ -4,6 +4,7 @@ import { Text } from "@oh-my-pi/pi-tui";
|
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
5
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
6
6
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
7
|
+
import type { ToolSession } from "../sdk";
|
|
7
8
|
import { untilAborted } from "../utils";
|
|
8
9
|
import { resolveToCwd } from "./path-utils";
|
|
9
10
|
import {
|
|
@@ -61,7 +62,7 @@ function splitIntoLines(content: string): string[] {
|
|
|
61
62
|
return content.split("\n").map((line, i, arr) => (i < arr.length - 1 ? `${line}\n` : line));
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
export function createNotebookTool(
|
|
65
|
+
export function createNotebookTool(session: ToolSession): AgentTool<typeof notebookSchema> {
|
|
65
66
|
return {
|
|
66
67
|
name: "notebook",
|
|
67
68
|
label: "Notebook",
|
|
@@ -79,7 +80,7 @@ export function createNotebookTool(cwd: string): AgentTool<typeof notebookSchema
|
|
|
79
80
|
}: { action: string; notebook_path: string; cell_index: number; content?: string; cell_type?: string },
|
|
80
81
|
signal?: AbortSignal,
|
|
81
82
|
) => {
|
|
82
|
-
const absolutePath = resolveToCwd(notebook_path, cwd);
|
|
83
|
+
const absolutePath = resolveToCwd(notebook_path, session.cwd);
|
|
83
84
|
|
|
84
85
|
return untilAborted(signal, async () => {
|
|
85
86
|
// Check if file exists
|
|
@@ -190,9 +191,6 @@ export function createNotebookTool(cwd: string): AgentTool<typeof notebookSchema
|
|
|
190
191
|
};
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
/** Default notebook tool using process.cwd() */
|
|
194
|
-
export const notebookTool = createNotebookTool(process.cwd());
|
|
195
|
-
|
|
196
194
|
// =============================================================================
|
|
197
195
|
// TUI Renderer
|
|
198
196
|
// =============================================================================
|
package/src/core/tools/output.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
14
14
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
15
15
|
import outputDescription from "../../prompts/tools/output.md" with { type: "text" };
|
|
16
16
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
17
|
-
import type {
|
|
17
|
+
import type { ToolSession } from "./index";
|
|
18
18
|
import {
|
|
19
19
|
formatCount,
|
|
20
20
|
formatEmptyMessage,
|
|
@@ -36,6 +36,11 @@ const outputSchema = Type.Object({
|
|
|
36
36
|
description: "Output format: raw (default), json (structured), stripped (no ANSI)",
|
|
37
37
|
}),
|
|
38
38
|
),
|
|
39
|
+
query: Type.Optional(
|
|
40
|
+
Type.String({
|
|
41
|
+
description: "jq-like query for JSON outputs (e.g., .result.items[0].name). Requires JSON output.",
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
39
44
|
offset: Type.Optional(
|
|
40
45
|
Type.Number({
|
|
41
46
|
description: "Line number to start reading from (1-indexed)",
|
|
@@ -70,6 +75,7 @@ interface OutputEntry {
|
|
|
70
75
|
provenance?: OutputProvenance;
|
|
71
76
|
previewLines?: string[];
|
|
72
77
|
range?: OutputRange;
|
|
78
|
+
query?: string;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
export interface OutputToolDetails {
|
|
@@ -83,6 +89,77 @@ function stripAnsi(text: string): string {
|
|
|
83
89
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
function parseQuery(query: string): Array<string | number> {
|
|
93
|
+
let input = query.trim();
|
|
94
|
+
if (!input) return [];
|
|
95
|
+
if (input.startsWith(".")) input = input.slice(1);
|
|
96
|
+
if (!input) return [];
|
|
97
|
+
|
|
98
|
+
const tokens: Array<string | number> = [];
|
|
99
|
+
let i = 0;
|
|
100
|
+
|
|
101
|
+
const isIdentChar = (ch: string) => /[A-Za-z0-9_-]/.test(ch);
|
|
102
|
+
|
|
103
|
+
while (i < input.length) {
|
|
104
|
+
const ch = input[i];
|
|
105
|
+
if (ch === ".") {
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (ch === "[") {
|
|
110
|
+
const closeIndex = input.indexOf("]", i + 1);
|
|
111
|
+
if (closeIndex === -1) {
|
|
112
|
+
throw new Error(`Invalid query: missing ] in ${query}`);
|
|
113
|
+
}
|
|
114
|
+
const raw = input.slice(i + 1, closeIndex).trim();
|
|
115
|
+
if (!raw) {
|
|
116
|
+
throw new Error(`Invalid query: empty [] in ${query}`);
|
|
117
|
+
}
|
|
118
|
+
const quote = raw[0];
|
|
119
|
+
if ((quote === '"' || quote === "'") && raw.endsWith(quote)) {
|
|
120
|
+
let inner = raw.slice(1, -1);
|
|
121
|
+
inner = inner.replace(/\\(["'\\])/g, "$1");
|
|
122
|
+
tokens.push(inner);
|
|
123
|
+
} else if (/^\d+$/.test(raw)) {
|
|
124
|
+
tokens.push(Number(raw));
|
|
125
|
+
} else {
|
|
126
|
+
tokens.push(raw);
|
|
127
|
+
}
|
|
128
|
+
i = closeIndex + 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const start = i;
|
|
133
|
+
while (i < input.length && isIdentChar(input[i])) {
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
if (start === i) {
|
|
137
|
+
throw new Error(`Invalid query: unexpected token '${input[i]}' in ${query}`);
|
|
138
|
+
}
|
|
139
|
+
const ident = input.slice(start, i);
|
|
140
|
+
tokens.push(ident);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return tokens;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function applyQuery(data: unknown, query: string): unknown {
|
|
147
|
+
const tokens = parseQuery(query);
|
|
148
|
+
let current: unknown = data;
|
|
149
|
+
for (const token of tokens) {
|
|
150
|
+
if (current === null || current === undefined) return undefined;
|
|
151
|
+
if (typeof token === "number") {
|
|
152
|
+
if (!Array.isArray(current)) return undefined;
|
|
153
|
+
current = current[token];
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (typeof current !== "object") return undefined;
|
|
157
|
+
const record = current as Record<string, unknown>;
|
|
158
|
+
current = record[token];
|
|
159
|
+
}
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
|
|
86
163
|
/** List available output IDs in artifacts directory */
|
|
87
164
|
function listAvailableOutputs(artifactsDir: string): string[] {
|
|
88
165
|
try {
|
|
@@ -120,10 +197,7 @@ function extractPreviewLines(content: string, maxLines: number): string[] {
|
|
|
120
197
|
return preview;
|
|
121
198
|
}
|
|
122
199
|
|
|
123
|
-
export function createOutputTool(
|
|
124
|
-
_cwd: string,
|
|
125
|
-
sessionContext?: SessionContext,
|
|
126
|
-
): AgentTool<typeof outputSchema, OutputToolDetails> {
|
|
200
|
+
export function createOutputTool(session: ToolSession): AgentTool<typeof outputSchema, OutputToolDetails> {
|
|
127
201
|
return {
|
|
128
202
|
name: "output",
|
|
129
203
|
label: "Output",
|
|
@@ -131,9 +205,15 @@ export function createOutputTool(
|
|
|
131
205
|
parameters: outputSchema,
|
|
132
206
|
execute: async (
|
|
133
207
|
_toolCallId: string,
|
|
134
|
-
params: {
|
|
208
|
+
params: {
|
|
209
|
+
ids: string[];
|
|
210
|
+
format?: "raw" | "json" | "stripped";
|
|
211
|
+
query?: string;
|
|
212
|
+
offset?: number;
|
|
213
|
+
limit?: number;
|
|
214
|
+
},
|
|
135
215
|
): Promise<{ content: TextContent[]; details: OutputToolDetails }> => {
|
|
136
|
-
const sessionFile =
|
|
216
|
+
const sessionFile = session.getSessionFile();
|
|
137
217
|
|
|
138
218
|
if (!sessionFile) {
|
|
139
219
|
return {
|
|
@@ -153,7 +233,15 @@ export function createOutputTool(
|
|
|
153
233
|
const outputs: OutputEntry[] = [];
|
|
154
234
|
const notFound: string[] = [];
|
|
155
235
|
const outputContentById = new Map<string, string>();
|
|
156
|
-
const
|
|
236
|
+
const query = params.query?.trim();
|
|
237
|
+
const wantsQuery = query !== undefined && query.length > 0;
|
|
238
|
+
const format = params.format ?? (wantsQuery ? "json" : "raw");
|
|
239
|
+
|
|
240
|
+
if (wantsQuery && (params.offset !== undefined || params.limit !== undefined)) {
|
|
241
|
+
throw new Error("query cannot be combined with offset/limit");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const queryResults: Array<{ id: string; value: unknown }> = [];
|
|
157
245
|
|
|
158
246
|
for (const id of params.ids) {
|
|
159
247
|
const outputPath = path.join(artifactsDir, `${id}.out.md`);
|
|
@@ -171,7 +259,22 @@ export function createOutputTool(
|
|
|
171
259
|
let selectedContent = rawContent;
|
|
172
260
|
let range: OutputRange | undefined;
|
|
173
261
|
|
|
174
|
-
if (
|
|
262
|
+
if (wantsQuery && query) {
|
|
263
|
+
let jsonValue: unknown;
|
|
264
|
+
try {
|
|
265
|
+
jsonValue = JSON.parse(rawContent);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
268
|
+
throw new Error(`Output ${id} is not valid JSON: ${message}`);
|
|
269
|
+
}
|
|
270
|
+
const value = applyQuery(jsonValue, query);
|
|
271
|
+
queryResults.push({ id, value });
|
|
272
|
+
try {
|
|
273
|
+
selectedContent = JSON.stringify(value, null, 2) ?? "null";
|
|
274
|
+
} catch {
|
|
275
|
+
selectedContent = String(value);
|
|
276
|
+
}
|
|
277
|
+
} else if (params.offset !== undefined || params.limit !== undefined) {
|
|
175
278
|
const startLine = Math.max(1, params.offset ?? 1);
|
|
176
279
|
if (startLine > totalLines) {
|
|
177
280
|
throw new Error(
|
|
@@ -189,11 +292,12 @@ export function createOutputTool(
|
|
|
189
292
|
outputs.push({
|
|
190
293
|
id,
|
|
191
294
|
path: outputPath,
|
|
192
|
-
lineCount: totalLines,
|
|
193
|
-
charCount: totalChars,
|
|
295
|
+
lineCount: wantsQuery ? selectedContent.split("\n").length : totalLines,
|
|
296
|
+
charCount: wantsQuery ? selectedContent.length : totalChars,
|
|
194
297
|
provenance: parseOutputProvenance(id),
|
|
195
298
|
previewLines: extractPreviewLines(selectedContent, 4),
|
|
196
299
|
range,
|
|
300
|
+
query: query,
|
|
197
301
|
});
|
|
198
302
|
}
|
|
199
303
|
|
|
@@ -215,15 +319,17 @@ export function createOutputTool(
|
|
|
215
319
|
let contentText: string;
|
|
216
320
|
|
|
217
321
|
if (format === "json") {
|
|
218
|
-
const jsonData =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
322
|
+
const jsonData = wantsQuery
|
|
323
|
+
? queryResults
|
|
324
|
+
: outputs.map((o) => ({
|
|
325
|
+
id: o.id,
|
|
326
|
+
lineCount: o.lineCount,
|
|
327
|
+
charCount: o.charCount,
|
|
328
|
+
provenance: o.provenance,
|
|
329
|
+
previewLines: o.previewLines,
|
|
330
|
+
range: o.range,
|
|
331
|
+
content: outputContentById.get(o.id) ?? "",
|
|
332
|
+
}));
|
|
227
333
|
contentText = JSON.stringify(jsonData, null, 2);
|
|
228
334
|
} else {
|
|
229
335
|
// raw or stripped
|
|
@@ -253,9 +359,6 @@ export function createOutputTool(
|
|
|
253
359
|
};
|
|
254
360
|
}
|
|
255
361
|
|
|
256
|
-
/** Default output tool using process.cwd() - for backwards compatibility */
|
|
257
|
-
export const outputTool = createOutputTool(process.cwd());
|
|
258
|
-
|
|
259
362
|
// =============================================================================
|
|
260
363
|
// TUI Renderer
|
|
261
364
|
// =============================================================================
|
|
@@ -263,6 +366,7 @@ export const outputTool = createOutputTool(process.cwd());
|
|
|
263
366
|
interface OutputRenderArgs {
|
|
264
367
|
ids: string[];
|
|
265
368
|
format?: "raw" | "json" | "stripped";
|
|
369
|
+
query?: string;
|
|
266
370
|
offset?: number;
|
|
267
371
|
limit?: number;
|
|
268
372
|
}
|
|
@@ -291,6 +395,7 @@ export const outputToolRenderer = {
|
|
|
291
395
|
|
|
292
396
|
const meta: string[] = [];
|
|
293
397
|
if (args.format && args.format !== "raw") meta.push(`format:${args.format}`);
|
|
398
|
+
if (args.query) meta.push(`query:${args.query}`);
|
|
294
399
|
if (args.offset !== undefined) meta.push(`offset:${args.offset}`);
|
|
295
400
|
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
296
401
|
text += formatMeta(meta, uiTheme);
|
|
@@ -309,15 +414,9 @@ export const outputToolRenderer = {
|
|
|
309
414
|
const icon = uiTheme.styledSymbol("status.error", "error");
|
|
310
415
|
let text = `${icon} ${uiTheme.fg("error", `Error: Not found: ${details.notFound.join(", ")}`)}`;
|
|
311
416
|
if (details.availableIds?.length) {
|
|
312
|
-
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
|
313
|
-
"muted",
|
|
314
|
-
`Available: ${details.availableIds.join(", ")}`,
|
|
315
|
-
)}`;
|
|
417
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", `Available: ${details.availableIds.join(", ")}`)}`;
|
|
316
418
|
} else {
|
|
317
|
-
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
|
318
|
-
"muted",
|
|
319
|
-
"No outputs available in current session",
|
|
320
|
-
)}`;
|
|
419
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", "No outputs available in current session")}`;
|
|
321
420
|
}
|
|
322
421
|
return new Text(text, 0, 0);
|
|
323
422
|
}
|