@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.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 +94 -1
- package/examples/custom-tools/README.md +11 -7
- package/examples/custom-tools/hello/index.ts +2 -2
- package/examples/extensions/README.md +19 -8
- package/examples/extensions/api-demo.ts +15 -19
- package/examples/extensions/hello.ts +5 -6
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/extensions/reload-runtime.ts +4 -3
- package/examples/extensions/with-deps/index.ts +4 -3
- package/examples/sdk/06-extensions.ts +4 -2
- package/package.json +8 -18
- package/src/autoresearch/tools/init-experiment.ts +38 -41
- package/src/autoresearch/tools/log-experiment.ts +32 -41
- package/src/autoresearch/tools/run-experiment.ts +3 -3
- package/src/autoresearch/tools/update-notes.ts +11 -11
- package/src/commands/commit.ts +10 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -4
- package/src/commit/agentic/tools/git-file-diff.ts +4 -4
- package/src/commit/agentic/tools/git-hunk.ts +5 -5
- package/src/commit/agentic/tools/git-overview.ts +4 -4
- package/src/commit/agentic/tools/propose-changelog.ts +13 -13
- package/src/commit/agentic/tools/propose-commit.ts +6 -6
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/schemas.ts +28 -28
- package/src/commit/agentic/tools/split-commit.ts +22 -21
- package/src/commit/analysis/summary.ts +4 -4
- package/src/commit/changelog/generate.ts +7 -11
- package/src/commit/shared-llm.ts +22 -34
- package/src/config/config-file.ts +35 -13
- package/src/config/model-registry.ts +40 -191
- package/src/config/models-config-schema.ts +166 -0
- package/src/config/settings-schema.ts +29 -0
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/edit/index.ts +2 -2
- package/src/edit/modes/apply-patch.ts +7 -6
- package/src/edit/modes/patch.ts +18 -25
- package/src/edit/modes/replace.ts +18 -20
- package/src/eval/js/shared/rewrite-imports.ts +131 -10
- package/src/eval/py/executor.ts +233 -623
- package/src/eval/py/kernel.ts +27 -2
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/exa/factory.ts +5 -4
- package/src/exa/mcp-client.ts +1 -1
- package/src/exa/researcher.ts +9 -20
- package/src/exa/search.ts +26 -52
- package/src/exa/types.ts +1 -1
- package/src/exa/websets.ts +54 -53
- package/src/exec/bash-executor.ts +2 -1
- package/src/extensibility/custom-commands/loader.ts +5 -3
- package/src/extensibility/custom-commands/types.ts +4 -2
- package/src/extensibility/custom-tools/loader.ts +5 -3
- package/src/extensibility/custom-tools/types.ts +7 -6
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/extensions/loader.ts +7 -3
- package/src/extensibility/extensions/types.ts +9 -5
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/hooks/loader.ts +3 -1
- package/src/extensibility/hooks/tool-wrapper.ts +1 -1
- package/src/extensibility/hooks/types.ts +4 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/typebox.ts +391 -0
- package/src/goals/tools/goal-tool.ts +6 -12
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/hashline/types.ts +4 -4
- package/src/hindsight/state.ts +2 -2
- package/src/index.ts +0 -2
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/lsp/types.ts +30 -38
- package/src/mcp/manager.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -1
- package/src/modes/acp/acp-agent.ts +248 -50
- package/src/modes/components/session-observer-overlay.ts +12 -1
- package/src/modes/components/status-line/segments.ts +39 -4
- package/src/modes/controllers/command-controller.ts +27 -2
- package/src/modes/controllers/event-controller.ts +3 -4
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/rpc/host-tools.ts +1 -1
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-mode.ts +27 -1
- package/src/modes/rpc/rpc-types.ts +58 -1
- package/src/modes/runtime-init.ts +2 -1
- package/src/modes/theme/defaults/dark-poimandres.json +1 -0
- package/src/modes/theme/defaults/light-poimandres.json +1 -0
- package/src/modes/theme/theme.ts +117 -117
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/hashline.md +22 -26
- package/src/prompts/tools/read.md +55 -37
- package/src/sdk.ts +31 -8
- package/src/session/agent-session.ts +74 -104
- package/src/session/messages.ts +16 -51
- package/src/session/session-manager.ts +22 -2
- package/src/session/streaming-output.ts +16 -6
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +210 -87
- package/src/task/index.ts +15 -11
- package/src/task/render.ts +32 -5
- package/src/task/types.ts +54 -39
- package/src/tools/ask.ts +12 -12
- package/src/tools/ast-edit.ts +11 -15
- package/src/tools/ast-grep.ts +9 -10
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash.ts +48 -38
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser.ts +39 -53
- package/src/tools/calculator.ts +12 -11
- package/src/tools/checkpoint.ts +7 -7
- package/src/tools/debug.ts +40 -43
- package/src/tools/eval.ts +16 -10
- package/src/tools/find.ts +10 -13
- package/src/tools/gh.ts +108 -132
- package/src/tools/hindsight-recall.ts +4 -6
- package/src/tools/hindsight-reflect.ts +5 -5
- package/src/tools/hindsight-retain.ts +15 -17
- package/src/tools/image-gen.ts +31 -81
- package/src/tools/index.ts +4 -1
- package/src/tools/inspect-image.ts +8 -9
- package/src/tools/irc.ts +15 -27
- package/src/tools/job.ts +30 -28
- package/src/tools/output-meta.ts +26 -0
- package/src/tools/read.ts +39 -12
- package/src/tools/recipe/index.ts +7 -9
- package/src/tools/render-mermaid.ts +12 -12
- package/src/tools/report-tool-issue.ts +4 -4
- package/src/tools/resolve.ts +11 -11
- package/src/tools/review.ts +14 -26
- package/src/tools/search-tool-bm25.ts +7 -9
- package/src/tools/search.ts +19 -22
- package/src/tools/ssh.ts +10 -9
- package/src/tools/todo-write.ts +26 -34
- package/src/tools/vim.ts +10 -26
- package/src/tools/write.ts +25 -5
- package/src/tools/yield.ts +100 -54
- package/src/web/search/index.ts +9 -24
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +5 -0
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
- package/src/prompts/compaction/branch-summary-context.md +0 -5
- package/src/prompts/compaction/branch-summary-preamble.md +0 -2
- package/src/prompts/compaction/branch-summary.md +0 -30
- package/src/prompts/compaction/compaction-short-summary.md +0 -9
- package/src/prompts/compaction/compaction-summary-context.md +0 -5
- package/src/prompts/compaction/compaction-summary.md +0 -38
- package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
- package/src/prompts/compaction/compaction-update-summary.md +0 -45
- package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
- package/src/prompts/system/file-operations.md +0 -10
- package/src/prompts/system/handoff-document.md +0 -49
- package/src/prompts/system/summarization-system.md +0 -3
- package/src/session/compaction/branch-summarization.ts +0 -324
- package/src/session/compaction/compaction.ts +0 -1420
- package/src/session/compaction/errors.ts +0 -31
- package/src/session/compaction/index.ts +0 -8
- package/src/session/compaction/pruning.ts +0 -91
- package/src/session/compaction/utils.ts +0 -184
package/src/task/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import
|
|
4
|
+
import * as z from "zod/v4";
|
|
5
5
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
6
6
|
import type { NestedRepoPatch } from "./worktree";
|
|
7
7
|
|
|
@@ -63,65 +63,65 @@ const assignmentDescriptionForContextDisabled =
|
|
|
63
63
|
"Complete per-task instructions the subagent executes. Must follow the Target/Change/Edge Cases/Acceptance structure, and include any background that would otherwise live in `context` since shared context is disabled in this mode.";
|
|
64
64
|
|
|
65
65
|
const createTaskItemSchema = (contextEnabled: boolean) =>
|
|
66
|
-
|
|
67
|
-
id:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
description: "Short one-liner for UI display only — not seen by the subagent",
|
|
73
|
-
}),
|
|
74
|
-
assignment: Type.String({
|
|
75
|
-
description: contextEnabled ? assignmentDescriptionForContextEnabled : assignmentDescriptionForContextDisabled,
|
|
76
|
-
}),
|
|
66
|
+
z.object({
|
|
67
|
+
id: z.string().max(48).describe("CamelCase identifier, max 48 chars"),
|
|
68
|
+
description: z.string().describe("Short one-liner for UI display only — not seen by the subagent"),
|
|
69
|
+
assignment: z
|
|
70
|
+
.string()
|
|
71
|
+
.describe(contextEnabled ? assignmentDescriptionForContextEnabled : assignmentDescriptionForContextDisabled),
|
|
77
72
|
});
|
|
78
73
|
|
|
79
74
|
/** Single task item for parallel execution (default shape with context enabled). */
|
|
80
75
|
export const taskItemSchema = createTaskItemSchema(true);
|
|
81
|
-
export type TaskItem =
|
|
76
|
+
export type TaskItem = z.infer<typeof taskItemSchema>;
|
|
82
77
|
|
|
83
78
|
const createTaskSchema = (options: { isolationEnabled: boolean; simpleMode: TaskSimpleMode }) => {
|
|
84
79
|
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(options.simpleMode);
|
|
85
80
|
const itemSchema = createTaskItemSchema(contextEnabled);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
81
|
+
|
|
82
|
+
let schema = z.object({
|
|
83
|
+
agent: z.string().describe("Agent type for all tasks in this batch"),
|
|
84
|
+
tasks: z
|
|
85
|
+
.array(itemSchema)
|
|
86
|
+
.describe(
|
|
87
|
+
contextEnabled
|
|
88
|
+
? "Tasks to execute in parallel. Each must be small-scoped (3-5 files max) and self-contained given context + assignment."
|
|
89
|
+
: "Tasks to execute in parallel. Each must be small-scoped (3-5 files max) and fully self-contained inside assignment because shared context is disabled.",
|
|
90
|
+
),
|
|
91
|
+
});
|
|
94
92
|
|
|
95
93
|
if (contextEnabled) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
schema = schema.extend({
|
|
95
|
+
context: z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe(
|
|
99
99
|
"Shared background prepended to every task's assignment. Put goal, non-goals, constraints, conventions, reference paths, API contracts, and global acceptance commands here once — instead of duplicating across assignments.",
|
|
100
|
-
|
|
101
|
-
);
|
|
100
|
+
),
|
|
101
|
+
});
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
if (customSchemaEnabled) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
schema = schema.extend({
|
|
106
|
+
schema: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe(
|
|
108
110
|
"JSON-encoded JTD schema defining expected response structure. Output format belongs here — never in context or assignment.",
|
|
109
|
-
|
|
110
|
-
);
|
|
111
|
+
),
|
|
112
|
+
});
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
if (options.isolationEnabled) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}),
|
|
120
|
-
),
|
|
116
|
+
schema = schema.extend({
|
|
117
|
+
isolated: z
|
|
118
|
+
.boolean()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Run in isolated environment; returns patches. Use when tasks edit overlapping files."),
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
return
|
|
124
|
+
return schema;
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
export const taskSchema = createTaskSchema({ isolationEnabled: true, simpleMode: "default" });
|
|
@@ -141,6 +141,8 @@ const ALL_TASK_SCHEMAS = [
|
|
|
141
141
|
|
|
142
142
|
type DynamicTaskSchema = (typeof ALL_TASK_SCHEMAS)[number];
|
|
143
143
|
export type TaskSchema = typeof taskSchema;
|
|
144
|
+
/** Active task tool parameter schema for the current simple-mode / isolation flags */
|
|
145
|
+
export type TaskToolSchemaInstance = DynamicTaskSchema;
|
|
144
146
|
|
|
145
147
|
export function getTaskSchema(options: { isolationEnabled: boolean; simpleMode: TaskSimpleMode }): DynamicTaskSchema {
|
|
146
148
|
switch (options.simpleMode) {
|
|
@@ -219,6 +221,15 @@ export interface AgentProgress {
|
|
|
219
221
|
toolCount: number;
|
|
220
222
|
/** Cumulative input + output + cacheWrite tokens across all turns. Excludes cacheRead (re-reads cached context every turn, making cumulative sum misleading). */
|
|
221
223
|
tokens: number;
|
|
224
|
+
/**
|
|
225
|
+
* Current per-turn context size: latest assistant message's `usage.totalTokens`.
|
|
226
|
+
* This is the number to compare against `contextWindow` — what compaction
|
|
227
|
+
* decides on, what the user typically reads as "how full is the context".
|
|
228
|
+
* Distinct from `tokens`, which is a lifetime billing-volume counter.
|
|
229
|
+
*/
|
|
230
|
+
contextTokens?: number;
|
|
231
|
+
/** Model's context window in tokens, when known. Lets the UI render `<curr>/<window>` gauges. */
|
|
232
|
+
contextWindow?: number;
|
|
222
233
|
/** Cumulative billing cost in USD, accumulated incrementally from message_end events. */
|
|
223
234
|
cost: number;
|
|
224
235
|
durationMs: number;
|
|
@@ -244,6 +255,10 @@ export interface SingleResult {
|
|
|
244
255
|
durationMs: number;
|
|
245
256
|
/** Cumulative input + output + cacheWrite tokens across all turns. Excludes cacheRead (re-reads cached context every turn, making cumulative sum misleading). */
|
|
246
257
|
tokens: number;
|
|
258
|
+
/** Latest per-turn context size at task completion. See `AgentProgress.contextTokens`. */
|
|
259
|
+
contextTokens?: number;
|
|
260
|
+
/** Model's context window in tokens, when known. */
|
|
261
|
+
contextWindow?: number;
|
|
247
262
|
modelOverride?: string | string[];
|
|
248
263
|
error?: string;
|
|
249
264
|
aborted?: boolean;
|
package/src/tools/ask.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
19
|
import { type Component, Container, Markdown, renderInlineMarkdown, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
20
20
|
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
21
|
-
import
|
|
21
|
+
import * as z from "zod/v4";
|
|
22
22
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
23
23
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
24
24
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
@@ -31,23 +31,23 @@ import { ToolAbortError } from "./tool-errors";
|
|
|
31
31
|
// Types
|
|
32
32
|
// =============================================================================
|
|
33
33
|
|
|
34
|
-
const OptionItem =
|
|
35
|
-
label:
|
|
34
|
+
const OptionItem = z.object({
|
|
35
|
+
label: z.string().describe("display label"),
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
const QuestionItem =
|
|
39
|
-
id:
|
|
40
|
-
question:
|
|
41
|
-
options:
|
|
42
|
-
multi:
|
|
43
|
-
recommended:
|
|
38
|
+
const QuestionItem = z.object({
|
|
39
|
+
id: z.string().describe("question id"),
|
|
40
|
+
question: z.string().describe("question text"),
|
|
41
|
+
options: z.array(OptionItem).describe("available options"),
|
|
42
|
+
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
43
|
+
recommended: z.number().describe("recommended option index").optional(),
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
const askSchema =
|
|
47
|
-
questions:
|
|
46
|
+
const askSchema = z.object({
|
|
47
|
+
questions: z.array(QuestionItem).min(1).describe("questions to ask"),
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
export type AskToolInput =
|
|
50
|
+
export type AskToolInput = z.infer<typeof askSchema>;
|
|
51
51
|
|
|
52
52
|
/** Result for a single question */
|
|
53
53
|
export interface QuestionResult {
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type AstReplaceChange, type AstReplaceFileChange, astEdit } from "@oh-m
|
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
|
-
import
|
|
7
|
+
import * as z from "zod/v4";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import { computeLineHash, HL_BODY_SEP } from "../hashline/hash";
|
|
10
10
|
import type { Theme } from "../modes/theme/theme";
|
|
@@ -33,21 +33,17 @@ import { queueResolveHandler } from "./resolve";
|
|
|
33
33
|
import { ToolError } from "./tool-errors";
|
|
34
34
|
import { toolResult } from "./tool-result";
|
|
35
35
|
|
|
36
|
-
const astEditOpSchema =
|
|
37
|
-
pat:
|
|
38
|
-
out:
|
|
36
|
+
const astEditOpSchema = z.object({
|
|
37
|
+
pat: z.string().describe("ast pattern"),
|
|
38
|
+
out: z.string().describe("replacement template"),
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
const astEditSchema =
|
|
42
|
-
ops:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
minItems: 1,
|
|
48
|
-
description: "files, directories, globs, or internal URLs to rewrite",
|
|
49
|
-
examples: [["src/"], ["src/foo.ts"], ["src/**/*.ts"], ["src/", "packages/"]],
|
|
50
|
-
}),
|
|
41
|
+
const astEditSchema = z.object({
|
|
42
|
+
ops: z.array(astEditOpSchema).min(1).describe("rewrite ops"),
|
|
43
|
+
paths: z
|
|
44
|
+
.array(z.string().describe("file, directory, glob, or internal URL to rewrite"))
|
|
45
|
+
.min(1)
|
|
46
|
+
.describe("files, directories, globs, or internal URLs to rewrite"),
|
|
51
47
|
});
|
|
52
48
|
|
|
53
49
|
interface AstEditCallOptions {
|
|
@@ -174,7 +170,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
174
170
|
|
|
175
171
|
async execute(
|
|
176
172
|
_toolCallId: string,
|
|
177
|
-
params:
|
|
173
|
+
params: z.infer<typeof astEditSchema>,
|
|
178
174
|
signal?: AbortSignal,
|
|
179
175
|
_onUpdate?: AgentToolUpdateCallback<AstEditToolDetails>,
|
|
180
176
|
_context?: AgentToolContext,
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type AstFindMatch, astGrep } from "@oh-my-pi/pi-natives";
|
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
|
-
import
|
|
7
|
+
import * as z from "zod/v4";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import type { Theme } from "../modes/theme/theme";
|
|
10
10
|
import astGrepDescription from "../prompts/tools/ast-grep.md" with { type: "text" };
|
|
@@ -32,14 +32,13 @@ import {
|
|
|
32
32
|
import { ToolError } from "./tool-errors";
|
|
33
33
|
import { toolResult } from "./tool-result";
|
|
34
34
|
|
|
35
|
-
const astGrepSchema =
|
|
36
|
-
pat:
|
|
37
|
-
paths:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
skip: Type.Optional(Type.Number({ description: "matches to skip", default: 0 })),
|
|
35
|
+
const astGrepSchema = z.object({
|
|
36
|
+
pat: z.string().describe("ast pattern"),
|
|
37
|
+
paths: z
|
|
38
|
+
.array(z.string().describe("file, directory, glob, or internal URL to search"))
|
|
39
|
+
.min(1)
|
|
40
|
+
.describe("files, directories, globs, or internal URLs to search"),
|
|
41
|
+
skip: z.number().default(0).describe("matches to skip").optional(),
|
|
43
42
|
});
|
|
44
43
|
|
|
45
44
|
async function runMultiTargetAstGrep(
|
|
@@ -129,7 +128,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
129
128
|
|
|
130
129
|
async execute(
|
|
131
130
|
_toolCallId: string,
|
|
132
|
-
params:
|
|
131
|
+
params: z.infer<typeof astGrepSchema>,
|
|
133
132
|
signal?: AbortSignal,
|
|
134
133
|
_onUpdate?: AgentToolUpdateCallback<AstGrepToolDetails>,
|
|
135
134
|
_context?: AgentToolContext,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative transforms applied to a bash command before execution.
|
|
3
|
+
*
|
|
4
|
+
* Two fixups are applied, each anchored to the end of a top-level segment
|
|
5
|
+
* (segments split on `;`, `&&`, `||`, and background `&`):
|
|
6
|
+
*
|
|
7
|
+
* 1. Trailing `| head [args]` / `| tail [args]` (and the `|&` variant) — these
|
|
8
|
+
* pipes exist purely to limit output length. The harness already truncates
|
|
9
|
+
* bash output and exposes the full result via an artifact, so they only
|
|
10
|
+
* hide content the agent wanted.
|
|
11
|
+
*
|
|
12
|
+
* 2. A redundant trailing `2>&1` left on a segment that has no remaining pipe
|
|
13
|
+
* or other redirect. The harness already merges stderr into stdout, so the
|
|
14
|
+
* duplication is purely cosmetic — and often a leftover after fixup (1)
|
|
15
|
+
* drops a downstream pipe.
|
|
16
|
+
*
|
|
17
|
+
* The heavy lifting (tokenization, quoting, heredoc handling, command
|
|
18
|
+
* substitution, nested compound commands) lives in Rust under
|
|
19
|
+
* `pi_shell::fixup`, driven by the real `brush-parser` AST. This module is a
|
|
20
|
+
* thin sync wrapper plus user-facing notice formatting.
|
|
21
|
+
*/
|
|
22
|
+
import { applyBashFixups as nativeApplyBashFixups } from "@oh-my-pi/pi-natives";
|
|
23
|
+
|
|
24
|
+
export interface BashFixupResult {
|
|
25
|
+
/** Possibly-rewritten command. */
|
|
26
|
+
command: string;
|
|
27
|
+
/** Substrings that were stripped, in the order they were removed. */
|
|
28
|
+
stripped: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Apply both fixups to a bash command. On any parse failure, multi-line input,
|
|
33
|
+
* or no-op transform, returns the input verbatim with `stripped: []`.
|
|
34
|
+
*/
|
|
35
|
+
export function applyBashFixups(command: string): BashFixupResult {
|
|
36
|
+
return nativeApplyBashFixups(command);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Human-readable notice for the fixups that fired. Mirrors the shape of
|
|
41
|
+
* `formatTimeoutClampNotice` so it can ride alongside the other bash notices.
|
|
42
|
+
*/
|
|
43
|
+
export function formatBashFixupNotice(stripped: readonly string[]): string | undefined {
|
|
44
|
+
if (!stripped.length) return undefined;
|
|
45
|
+
const quoted = stripped.map(s => `\`${s}\``).join(", ");
|
|
46
|
+
return `<system-warning>Stripped redundant ${quoted} — bash output is already truncated and stderr is already merged into stdout. NEVER use these patterns.</system-warning>`;
|
|
47
|
+
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { ImageProtocol, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { $env, getProjectDir, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
6
|
-
import
|
|
6
|
+
import * as z from "zod/v4";
|
|
7
7
|
import { AsyncJobManager } from "../async";
|
|
8
8
|
import { type BashResult, executeBash } from "../exec/bash-executor";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -17,10 +17,11 @@ import { renderStatusLine } from "../tui";
|
|
|
17
17
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
18
18
|
import { getSixelLineMask } from "../utils/sixel";
|
|
19
19
|
import type { ToolSession } from ".";
|
|
20
|
+
import { applyBashFixups, formatBashFixupNotice } from "./bash-command-fixup";
|
|
20
21
|
import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
|
|
21
22
|
import { checkBashInterception } from "./bash-interceptor";
|
|
22
23
|
import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
|
|
23
|
-
import { formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
24
|
+
import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
|
|
24
25
|
import { resolveToCwd } from "./path-utils";
|
|
25
26
|
import { formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
26
27
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
@@ -43,30 +44,16 @@ async function saveBashOriginalArtifact(session: ToolSession, originalText: stri
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const bashSchemaBase =
|
|
47
|
-
command:
|
|
48
|
-
env:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
),
|
|
53
|
-
timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 300 })),
|
|
54
|
-
cwd: Type.Optional(Type.String({ description: "working directory", examples: ["src/", "/tmp"] })),
|
|
55
|
-
|
|
56
|
-
pty: Type.Optional(
|
|
57
|
-
Type.Boolean({
|
|
58
|
-
description: "run in pty mode",
|
|
59
|
-
}),
|
|
60
|
-
),
|
|
47
|
+
const bashSchemaBase = z.object({
|
|
48
|
+
command: z.string().describe("command to execute"),
|
|
49
|
+
env: z.record(z.string().regex(BASH_ENV_NAME_PATTERN), z.string()).optional().describe("extra env vars"),
|
|
50
|
+
timeout: z.number().default(300).describe("timeout in seconds").optional(),
|
|
51
|
+
cwd: z.string().describe("working directory").optional(),
|
|
52
|
+
pty: z.boolean().describe("run in pty mode").optional(),
|
|
61
53
|
});
|
|
62
54
|
|
|
63
|
-
const bashSchemaWithAsync =
|
|
64
|
-
|
|
65
|
-
async: Type.Optional(
|
|
66
|
-
Type.Boolean({
|
|
67
|
-
description: "run in background",
|
|
68
|
-
}),
|
|
69
|
-
),
|
|
55
|
+
const bashSchemaWithAsync = bashSchemaBase.extend({
|
|
56
|
+
async: z.boolean().describe("run in background").optional(),
|
|
70
57
|
});
|
|
71
58
|
|
|
72
59
|
type BashToolSchema = typeof bashSchemaBase | typeof bashSchemaWithAsync;
|
|
@@ -245,6 +232,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
245
232
|
readonly #asyncEnabled: boolean;
|
|
246
233
|
readonly #autoBackgroundEnabled: boolean;
|
|
247
234
|
readonly #autoBackgroundThresholdMs: number;
|
|
235
|
+
#bashFixupNoticeEmitted = false;
|
|
248
236
|
|
|
249
237
|
constructor(private readonly session: ToolSession) {
|
|
250
238
|
this.#asyncEnabled = this.session.settings.get("async.enabled");
|
|
@@ -291,7 +279,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
291
279
|
#buildCompletedResult(
|
|
292
280
|
result: BashResult | BashInteractiveResult,
|
|
293
281
|
timeoutSec: number,
|
|
294
|
-
options: { requestedTimeoutSec?: number; notices?: string[]; terminalId?: string } = {},
|
|
282
|
+
options: { requestedTimeoutSec?: number; notices?: readonly string[]; terminalId?: string } = {},
|
|
295
283
|
): AgentToolResult<BashToolDetails> {
|
|
296
284
|
const outputLines = [this.#formatResultOutput(result)];
|
|
297
285
|
const notices = options.notices?.filter(Boolean) ?? [];
|
|
@@ -314,7 +302,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
314
302
|
label: string,
|
|
315
303
|
previewText: string,
|
|
316
304
|
timeoutSec: number,
|
|
317
|
-
options: { requestedTimeoutSec?: number; notices?: string[] } = {},
|
|
305
|
+
options: { requestedTimeoutSec?: number; notices?: readonly string[] } = {},
|
|
318
306
|
): AgentToolResult<BashToolDetails> {
|
|
319
307
|
const details: BashToolDetails = {
|
|
320
308
|
timeoutSeconds: timeoutSec,
|
|
@@ -352,7 +340,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
352
340
|
timeoutMs: number;
|
|
353
341
|
timeoutSec: number;
|
|
354
342
|
requestedTimeoutSec?: number;
|
|
355
|
-
|
|
343
|
+
notices?: readonly string[];
|
|
356
344
|
|
|
357
345
|
resolvedEnv?: Record<string, string>;
|
|
358
346
|
onUpdate?: AgentToolUpdateCallback<BashToolDetails>;
|
|
@@ -392,7 +380,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
392
380
|
});
|
|
393
381
|
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
394
382
|
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
395
|
-
notices:
|
|
383
|
+
notices: options.notices ?? [],
|
|
396
384
|
});
|
|
397
385
|
const finalText = this.#extractTextResult(finalResult);
|
|
398
386
|
latestText = finalText;
|
|
@@ -483,6 +471,18 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
483
471
|
let command = rawCommand;
|
|
484
472
|
const env = normalizeBashEnv(rawEnv);
|
|
485
473
|
|
|
474
|
+
// Apply conservative bash fixups (strip trailing `| head|tail` and redundant
|
|
475
|
+
// `2>&1`). The helper is single-line only and refuses anything that could
|
|
476
|
+
// change semantics.
|
|
477
|
+
let bashFixups: string[] = [];
|
|
478
|
+
if (this.session.settings.get("bash.stripTrailingHeadTail")) {
|
|
479
|
+
const fixup = applyBashFixups(command);
|
|
480
|
+
if (fixup.stripped.length > 0) {
|
|
481
|
+
command = fixup.command;
|
|
482
|
+
bashFixups = fixup.stripped;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
486
|
// Extract leading `cd <path> && ...` into cwd when the model ignores the cwd parameter.
|
|
487
487
|
// Constrained to a single line so a `&&` that sits on a later line of a multiline
|
|
488
488
|
// script can't pull the entire script into the "cwd" capture.
|
|
@@ -558,7 +558,14 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
558
558
|
const requestedTimeoutSec = rawTimeout;
|
|
559
559
|
const timeoutSec = clampTimeout("bash", requestedTimeoutSec);
|
|
560
560
|
const timeoutMs = timeoutSec * 1000;
|
|
561
|
+
const pendingNotices: string[] = [];
|
|
561
562
|
const timeoutClampNotice = formatTimeoutClampNotice(requestedTimeoutSec, timeoutSec);
|
|
563
|
+
if (timeoutClampNotice) pendingNotices.push(timeoutClampNotice);
|
|
564
|
+
const bashFixupNotice = this.#bashFixupNoticeEmitted ? undefined : formatBashFixupNotice(bashFixups);
|
|
565
|
+
if (bashFixupNotice) {
|
|
566
|
+
pendingNotices.push(bashFixupNotice);
|
|
567
|
+
this.#bashFixupNoticeEmitted = true;
|
|
568
|
+
}
|
|
562
569
|
|
|
563
570
|
if (asyncRequested) {
|
|
564
571
|
if (!AsyncJobManager.instance()) {
|
|
@@ -570,7 +577,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
570
577
|
timeoutMs,
|
|
571
578
|
timeoutSec,
|
|
572
579
|
requestedTimeoutSec,
|
|
573
|
-
|
|
580
|
+
notices: pendingNotices,
|
|
574
581
|
|
|
575
582
|
resolvedEnv,
|
|
576
583
|
onUpdate,
|
|
@@ -578,7 +585,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
578
585
|
});
|
|
579
586
|
return this.#buildBackgroundStartResult(job.jobId, job.label, "", timeoutSec, {
|
|
580
587
|
requestedTimeoutSec,
|
|
581
|
-
notices:
|
|
588
|
+
notices: pendingNotices,
|
|
582
589
|
});
|
|
583
590
|
}
|
|
584
591
|
|
|
@@ -592,7 +599,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
592
599
|
timeoutMs,
|
|
593
600
|
timeoutSec,
|
|
594
601
|
requestedTimeoutSec,
|
|
595
|
-
|
|
602
|
+
notices: pendingNotices,
|
|
596
603
|
|
|
597
604
|
resolvedEnv,
|
|
598
605
|
onUpdate,
|
|
@@ -601,7 +608,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
601
608
|
if (startBackgrounded) {
|
|
602
609
|
return this.#buildBackgroundStartResult(job.jobId, job.label, "", timeoutSec, {
|
|
603
610
|
requestedTimeoutSec,
|
|
604
|
-
notices:
|
|
611
|
+
notices: pendingNotices,
|
|
605
612
|
});
|
|
606
613
|
}
|
|
607
614
|
const waitResult = await this.#waitForManagedBashJob(job, autoBackgroundWaitMs, signal);
|
|
@@ -621,7 +628,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
621
628
|
job.setBackgrounded(true);
|
|
622
629
|
return this.#buildBackgroundStartResult(job.jobId, job.label, job.getLatestText(), timeoutSec, {
|
|
623
630
|
requestedTimeoutSec,
|
|
624
|
-
notices:
|
|
631
|
+
notices: pendingNotices,
|
|
625
632
|
});
|
|
626
633
|
}
|
|
627
634
|
|
|
@@ -722,7 +729,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
722
729
|
};
|
|
723
730
|
return this.#buildCompletedResult(timedOutResult, timeoutSec, {
|
|
724
731
|
requestedTimeoutSec,
|
|
725
|
-
notices:
|
|
732
|
+
notices: pendingNotices,
|
|
726
733
|
terminalId: handle.terminalId,
|
|
727
734
|
});
|
|
728
735
|
}
|
|
@@ -778,7 +785,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
778
785
|
|
|
779
786
|
const bridgeNotices: string[] = [];
|
|
780
787
|
if (finalOutput.truncated) bridgeNotices.push("(output truncated)");
|
|
781
|
-
|
|
788
|
+
for (const notice of pendingNotices) bridgeNotices.push(notice);
|
|
782
789
|
|
|
783
790
|
return this.#buildCompletedResult(bridgeResult, timeoutSec, {
|
|
784
791
|
requestedTimeoutSec,
|
|
@@ -833,7 +840,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
833
840
|
}
|
|
834
841
|
return this.#buildCompletedResult(result, timeoutSec, {
|
|
835
842
|
requestedTimeoutSec,
|
|
836
|
-
notices:
|
|
843
|
+
notices: pendingNotices,
|
|
837
844
|
});
|
|
838
845
|
}
|
|
839
846
|
}
|
|
@@ -960,8 +967,11 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
960
967
|
const expanded = renderContext?.expanded ?? options.expanded;
|
|
961
968
|
const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
|
|
962
969
|
|
|
963
|
-
// Get output from context (preferred) or fall back to result content
|
|
964
|
-
|
|
970
|
+
// Get output from context (preferred) or fall back to result content.
|
|
971
|
+
// Strip the LLM-facing notice appended by wrappedExecute so we don't
|
|
972
|
+
// double-print it alongside the styled warning line below.
|
|
973
|
+
const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
974
|
+
const output = stripOutputNotice(rawOutput, details?.meta);
|
|
965
975
|
const displayOutput = output.trimEnd();
|
|
966
976
|
const showingFullOutput = expanded && renderContext?.isFullOutput === true;
|
|
967
977
|
|
|
@@ -11,7 +11,7 @@ import type { RenderResultOptions } from "../../extensibility/custom-tools/types
|
|
|
11
11
|
import type { Theme } from "../../modes/theme/theme";
|
|
12
12
|
import { Hasher, renderCodeCell, renderStatusLine } from "../../tui";
|
|
13
13
|
import type { BrowserToolDetails } from "../browser";
|
|
14
|
-
import { formatStyledTruncationWarning } from "../output-meta";
|
|
14
|
+
import { formatStyledTruncationWarning, stripOutputNotice } from "../output-meta";
|
|
15
15
|
import { replaceTabs, shortenPath } from "../render-utils";
|
|
16
16
|
|
|
17
17
|
const BROWSER_DEFAULT_PREVIEW_LINES = 10;
|
|
@@ -195,7 +195,7 @@ export const browserToolRenderer = {
|
|
|
195
195
|
const details = result.details;
|
|
196
196
|
const action = details?.action ?? argsObj.action;
|
|
197
197
|
const isError = result.isError === true;
|
|
198
|
-
const output = extractTextOutput(result.content);
|
|
198
|
+
const output = stripOutputNotice(extractTextOutput(result.content), details?.meta);
|
|
199
199
|
|
|
200
200
|
if (action === "run") {
|
|
201
201
|
let component = renderRunCell(argsObj, details, options, output, isError, theme);
|