@oh-my-pi/pi-coding-agent 14.7.3 → 14.7.5
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 +35 -0
- package/package.json +7 -7
- package/src/cli/read-cli.ts +1 -2
- package/src/cli.ts +7 -1
- package/src/commands/read.ts +2 -7
- package/src/config/settings-schema.ts +0 -5
- package/src/edit/modes/hashline.ts +40 -19
- package/src/edit/modes/patch.ts +7 -5
- package/src/edit/modes/replace.ts +6 -2
- package/src/edit/notebook.ts +222 -0
- package/src/edit/read-file.ts +7 -0
- package/src/edit/renderer.ts +4 -3
- package/src/edit/streaming.ts +49 -7
- package/src/modes/components/diff.ts +54 -7
- package/src/modes/interactive-mode.ts +32 -4
- package/src/modes/loop-limit.ts +140 -0
- package/src/modes/types.ts +3 -1
- package/src/prompts/agents/designer.md +1 -2
- package/src/prompts/agents/explore.md +2 -5
- package/src/prompts/agents/init.md +1 -4
- package/src/prompts/agents/librarian.md +1 -3
- package/src/prompts/agents/plan.md +7 -8
- package/src/prompts/agents/reviewer.md +1 -2
- package/src/prompts/ci-green-request.md +10 -10
- package/src/prompts/commands/orchestrate.md +48 -0
- package/src/prompts/memories/consolidation.md +10 -10
- package/src/prompts/memories/read-path.md +6 -6
- package/src/prompts/system/agent-creation-architect.md +54 -44
- package/src/prompts/system/custom-system-prompt.md +3 -5
- package/src/prompts/system/eager-todo.md +4 -4
- package/src/prompts/system/handoff-document.md +7 -4
- package/src/prompts/system/plan-mode-active.md +7 -3
- package/src/prompts/system/plan-mode-approved.md +5 -5
- package/src/prompts/system/summarization-system.md +2 -2
- package/src/prompts/system/system-prompt.md +53 -65
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/web-search.md +16 -19
- package/src/prompts/tools/bash.md +8 -8
- package/src/prompts/tools/browser.md +4 -4
- package/src/prompts/tools/debug.md +3 -1
- package/src/prompts/tools/eval.md +13 -9
- package/src/prompts/tools/hashline.md +4 -2
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/reflect.md +3 -3
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/resolve.md +2 -2
- package/src/prompts/tools/retain.md +3 -2
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +3 -4
- package/src/prompts/tools/task.md +1 -1
- package/src/slash-commands/builtin-registry.ts +4 -2
- package/src/task/commands.ts +5 -1
- package/src/tools/fetch.ts +6 -7
- package/src/tools/index.ts +0 -4
- package/src/tools/read.ts +18 -7
- package/src/tools/renderers.ts +0 -2
- package/src/tools/write.ts +41 -26
- package/src/tools/notebook.ts +0 -286
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Drives a real Chromium tab with full puppeteer access via JS execution.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- For
|
|
4
|
+
- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL — reader-mode text without spinning up a browser. Use this tool when you need JS execution, authentication, or interactive actions.
|
|
5
5
|
- Three actions only:
|
|
6
6
|
- `open` — acquire (or reuse) a named tab. `name` defaults to `"main"`. Optional `url` navigates after the tab is ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang (default: leave dialogs unhandled — page hangs until caller wires `page.on('dialog', …)`).
|
|
7
7
|
- `close` — release a tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate the process tree (default leaves it running).
|
|
8
|
-
- `run` — execute JS against an existing tab.
|
|
8
|
+
- `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The function's return value is JSON-stringified into the tool result; multiple `display(value)` calls accumulate text/images.
|
|
9
9
|
- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.
|
|
10
10
|
- Browser kinds, selected by the `app` field on `open`:
|
|
11
11
|
- default (no `app`) → headless Chromium with stealth patches.
|
|
12
12
|
- `app.path` → spawn an absolute binary (Electron/CDP). If a running instance already exposes a CDP port, it is reused; otherwise stale instances are killed and a fresh one is spawned. No stealth patches — never tamper with a real desktop app.
|
|
13
13
|
- `app.cdp_url` → connect to an existing CDP endpoint (e.g. `http://127.0.0.1:9222`).
|
|
14
14
|
- `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick a BrowserWindow when the app exposes several.
|
|
15
|
-
- Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when you need anything they don't cover.
|
|
15
|
+
- Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when you need anything they don't cover.
|
|
16
16
|
- `tab.goto(url, { waitUntil? })` — clears the element cache and navigates.
|
|
17
17
|
- `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot. Returns `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Element ids are stable until the next observe/goto.
|
|
18
18
|
- `tab.id(n)` — resolves an element id from the most recent observe to a real `ElementHandle` you can `.click()`, `.type()`, etc.
|
|
@@ -66,5 +66,5 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
|
|
|
66
66
|
</examples>
|
|
67
67
|
|
|
68
68
|
<output>
|
|
69
|
-
Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.
|
|
69
|
+
- Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.
|
|
70
70
|
</output>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
Provides debugger access through the Debug Adapter Protocol (DAP).
|
|
1
|
+
Provides debugger access through the Debug Adapter Protocol (DAP).
|
|
2
|
+
Use for launching or attaching debuggers, setting breakpoints, stepping through execution, inspecting threads/stack/variables, evaluating expressions, capturing output, and interrupting hung programs.
|
|
2
3
|
|
|
3
4
|
<instruction>
|
|
4
5
|
- Prefer over bash for program state, breakpoints, stepping, thread inspection, or interrupting a running process.
|
|
@@ -23,6 +24,7 @@ Provides debugger access through the Debug Adapter Protocol (DAP). Use this to l
|
|
|
23
24
|
3. `debug(action: "continue")`
|
|
24
25
|
4. If the program appears hung: `debug(action: "pause")`
|
|
25
26
|
5. Inspect state with `threads`, `stack_trace`, `scopes`, and `variables`
|
|
27
|
+
|
|
26
28
|
# Raw debugger command through repl
|
|
27
29
|
`debug(action: "evaluate", expression: "info registers", context: "repl")`
|
|
28
30
|
</examples>
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
Run code in a persistent kernel
|
|
1
|
+
Run code in a persistent kernel using codeblock cells.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
|
|
4
|
+
Cell header format:
|
|
5
5
|
|
|
6
6
|
```
|
|
7
7
|
===== <info> =====
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **Language**: {{#if py}}`py` for Python{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`js` / `ts` for JavaScript{{/if}}.{{#ifAll py js}} Omitted → inherit
|
|
10
|
+
At least 5 equal signs on each side. Content between one header and the next (or end of input) is the cell's code, verbatim.
|
|
11
|
+
- **Language**: {{#if py}}`py` for Python{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`js` / `ts` for JavaScript{{/if}}.{{#ifAll py js}} Omitted → inherit previous cell's language (first cell defaults to Python, falls back to JavaScript).{{else}} Omitted → inherit previous cell's language.{{/ifAll}}
|
|
12
12
|
- **Title shorthand**: `py:"…"`, `js:"…"`, `ts:"…"` set the language and the cell title together.
|
|
13
13
|
- **Attributes**:
|
|
14
14
|
- `id:"…"` — cell title (when language is unchanged or already set).
|
|
15
|
-
- `t:<duration>` — per-cell timeout.
|
|
16
|
-
- `rst` — wipe
|
|
15
|
+
- `t:<duration>` — per-cell timeout. Digits with optional `ms` / `s` / `m` units (e.g., `t:500ms`, `t:15s`, `t:2m`). Default 30s.
|
|
16
|
+
- `rst` — wipe this cell's own language kernel before running.{{#ifAll py js}} Other languages are untouched.{{/ifAll}}
|
|
17
17
|
|
|
18
|
-
**Work incrementally:**
|
|
18
|
+
**Work incrementally:**
|
|
19
|
+
- One logical step per cell (imports, define, test, use).
|
|
20
|
+
- Pass multiple small cells in one call.
|
|
21
|
+
- Define small reusable functions for individual debugging.
|
|
22
|
+
- Put workflow explanations in the assistant message or cell title — never inside cell code.
|
|
19
23
|
|
|
20
24
|
**On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells).
|
|
21
25
|
</instruction>
|
|
22
26
|
|
|
23
27
|
<prelude>
|
|
24
|
-
{{#ifAll py js}}
|
|
28
|
+
{{#ifAll py js}}Same helpers in both runtimes with the same positional argument order. Python: trailing options as keyword args. JavaScript: trailing options as a trailing object literal. JavaScript helpers are async and `await`able; Python helpers run synchronously.{{else}}{{#if py}}Helpers run synchronously. Trailing options are keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are a final object literal.{{/if}}{{/ifAll}}
|
|
25
29
|
```
|
|
26
30
|
display(value) → None
|
|
27
31
|
Render a value in the current cell output.
|
|
@@ -49,7 +53,7 @@ output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | di
|
|
|
49
53
|
{{/if}}</prelude>
|
|
50
54
|
|
|
51
55
|
<output>
|
|
52
|
-
Cells render like a Jupyter notebook.
|
|
56
|
+
Cells render like a Jupyter notebook. `display(value)` renders non-presentable data as an interactive JSON tree. Presentable values (figures, images, dataframes, etc.) use their native representation.
|
|
53
57
|
</output>
|
|
54
58
|
|
|
55
59
|
<caution>
|
|
@@ -4,7 +4,7 @@ A patch contains one or more file sections. The first non-blank line of every ed
|
|
|
4
4
|
Operations reference lines in the file by their line number and hash, called "Anchors", e.g. `5th`, `123ab`.
|
|
5
5
|
You **MUST** copy them verbatim from the latest output for the file you're editing.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Purely textual format. The tool has NO awareness of language, indentation, brackets, fences, or table widths. Emit valid syntax in replacements/insertions.
|
|
8
8
|
|
|
9
9
|
<ops>
|
|
10
10
|
@PATH header: subsequent ops apply to PATH
|
|
@@ -89,7 +89,7 @@ This format is purely textual. The tool has NO awareness of language, indentatio
|
|
|
89
89
|
+ {{hrefr 1}}
|
|
90
90
|
{{hsep}}const DEBUG = false;
|
|
91
91
|
|
|
92
|
-
If your replacement payload would render with even one unchanged line in the diff, you have the wrong op or
|
|
92
|
+
If your replacement payload would render with even one unchanged line in the diff, you have the wrong op or range. Stop and rewrite as `+`/`<`/`-` plus a narrower `=`.
|
|
93
93
|
</anti-pattern>
|
|
94
94
|
|
|
95
95
|
<critical>
|
|
@@ -98,4 +98,6 @@ If your replacement payload would render with even one unchanged line in the dif
|
|
|
98
98
|
- Do not write unified diff syntax (`@@`, `-OLD`, `+NEW`).
|
|
99
99
|
- `= A..B` deletes the range; payload is what's written. If a payload edge line already exists immediately outside `A..B`, widen the range to cover it — otherwise it duplicates.
|
|
100
100
|
- Multiple ops in one patch are cheap. Prefer two narrow ops over one wide `=`.
|
|
101
|
+
- Before choosing a `= A..B` range, mentally delete lines A through B. If that would split an unclosed bracket, paren, brace, or string/template from a line above A, or orphan a closing delimiter that belongs to an opener inside the range, you are bisecting a syntactic construct. Widen the range to a self-contained boundary, or use `+`/`-` instead.
|
|
102
|
+
- `= A..B` removes the range as a unit; the lines immediately outside it remain. If those outside lines form a wrapper (`try {`, `catch`, `if`, `else`, loop delimiters) you do not intend to delete, your payload is inserted inside that wrapper. Make sure the payload remains valid and preserves required behavior like error handling. If you need to change the wrapper itself, include it in the range and reproduce it.
|
|
101
103
|
</critical>
|
|
@@ -7,7 +7,6 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
|
|
|
7
7
|
|
|
8
8
|
## Parameters
|
|
9
9
|
- `path` — file path or URL (required). Append `:<sel>` for line ranges or raw mode (for example `src/foo.ts:50-200` or `src/foo.ts:raw`).
|
|
10
|
-
- `timeout` — seconds, for URLs only
|
|
11
10
|
|
|
12
11
|
## Selectors
|
|
13
12
|
|
|
@@ -33,7 +32,7 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
|
|
|
33
32
|
|
|
34
33
|
# Inspection
|
|
35
34
|
|
|
36
|
-
Extracts text from PDF, Word, PowerPoint, Excel, RTF, EPUB, and Jupyter notebook files. Can inspect images.
|
|
35
|
+
Extracts text from PDF, Word, PowerPoint, Excel, RTF, EPUB, and Jupyter notebook files. Notebooks are shown as editable `# %% [type] cell:N` text; edits to that text are applied back to the underlying `.ipynb` JSON while preserving notebook metadata where possible. Can inspect images.
|
|
37
36
|
|
|
38
37
|
# Directories & Archives
|
|
39
38
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
Generate a synthesised answer by reasoning over long-term memory. Unlike `recall
|
|
1
|
+
Generate a synthesised answer by reasoning over long-term memory. Unlike `recall`, `reflect` blends relevant memories into a coherent response.
|
|
2
2
|
|
|
3
|
-
Use for open-ended questions
|
|
3
|
+
Use for open-ended questions spanning many stored facts: "What do you know about this user?", "Summarize project decisions.", "What are my preferences for X?"
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Optional `context` parameter focuses the synthesis on a specific angle or sub-topic.
|
|
@@ -5,5 +5,5 @@ Parameters:
|
|
|
5
5
|
- `config` (optional): JSON render configuration (spacing and layout options).
|
|
6
6
|
Behavior:
|
|
7
7
|
- Returns ASCII diagram text.
|
|
8
|
-
- Saves full
|
|
9
|
-
- Returns
|
|
8
|
+
- Saves full output to `artifact://<id>` when storage is available.
|
|
9
|
+
- Returns error when Mermaid input is invalid or rendering fails.
|
|
@@ -4,5 +4,5 @@ Resolves a pending preview action by either applying or discarding it.
|
|
|
4
4
|
- `"discard"` rejects the pending changes.
|
|
5
5
|
- `reason` is required and must explain why you chose to apply or discard.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Only valid when a pending action exists (typically after a preview step).
|
|
8
|
+
Call fails with an error when no pending action exists.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Store one or more facts in long-term memory for future sessions.
|
|
2
2
|
|
|
3
|
-
Use for durable, reusable knowledge: user preferences, project decisions, architectural choices,
|
|
3
|
+
Use for durable, reusable knowledge: user preferences, project decisions, architectural choices, anything that improves future responses.
|
|
4
|
+
Ephemeral task state does not belong here.
|
|
4
5
|
|
|
5
|
-
Each item
|
|
6
|
+
Each item **MUST** be specific and self-contained — include who, what, when, and why. Batch related facts in a single call; they are deduplicated and consolidated.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
End an active checkpoint. Rewind context to it, replacing intermediate exploration with your report.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Call immediately after `checkpoint`-started investigative work.
|
|
4
4
|
|
|
5
5
|
Requirements:
|
|
6
6
|
- `report` is **REQUIRED** and must be concise, factual, and actionable.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
Search hidden tool metadata to discover and activate tools.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Activate hidden tools (MCP and built-in) when you need a capability not in your active tool set.
|
|
5
4
|
{{#if hasDiscoverableMCPServers}}Discoverable MCP servers in this session: {{#list discoverableMCPServerSummaries join=", "}}{{this}}{{/list}}.{{/if}}
|
|
6
5
|
{{#if discoverableMCPToolCount}}Total discoverable tools available: {{discoverableMCPToolCount}}.{{/if}}
|
|
7
6
|
Input:
|
|
@@ -16,7 +15,7 @@ Behavior:
|
|
|
16
15
|
- Newly activated tools become available before the next model call in the same overall turn
|
|
17
16
|
|
|
18
17
|
Notes:
|
|
19
|
-
|
|
18
|
+
Start with `limit` 5–10 if unsure.
|
|
20
19
|
- `query` is matched against tool metadata fields:
|
|
21
20
|
- `name`
|
|
22
21
|
- `label`
|
|
@@ -25,7 +24,7 @@ Notes:
|
|
|
25
24
|
- `description` / `summary`
|
|
26
25
|
- input schema property keys (`schema_keys`)
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
Not for repository/file/code search. Tool discovery only.
|
|
29
28
|
|
|
30
29
|
Returns JSON with:
|
|
31
30
|
- `query`
|
|
@@ -5,7 +5,7 @@ Launches subagents to parallelize workflows.
|
|
|
5
5
|
- Use `job` (with `poll`) to wait. **MUST NOT** poll `read jobs://` in a loop.
|
|
6
6
|
{{/if}}
|
|
7
7
|
|
|
8
|
-
Subagents have no
|
|
8
|
+
Subagents have no conversation history. Every fact, file path, and decision they need **MUST** be explicit in {{#if contextEnabled}}`context` or `assignment`{{else}}each `assignment`{{/if}}.
|
|
9
9
|
|
|
10
10
|
<parameters>
|
|
11
11
|
- `agent`: agent type for all tasks
|
|
@@ -119,8 +119,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
119
119
|
name: "loop",
|
|
120
120
|
description:
|
|
121
121
|
"Toggle loop mode. While enabled, the next prompt you send re-submits after every yield. Esc cancels the current iteration; /loop again to disable.",
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
inlineHint: "[count|duration]",
|
|
123
|
+
allowArgs: true,
|
|
124
|
+
handle: async (command, runtime) => {
|
|
125
|
+
await runtime.ctx.handleLoopCommand(command.args);
|
|
124
126
|
runtime.ctx.editor.setText("");
|
|
125
127
|
},
|
|
126
128
|
},
|
package/src/task/commands.ts
CHANGED
|
@@ -9,8 +9,12 @@ import { type SlashCommand, slashCommandCapability } from "../capability/slash-c
|
|
|
9
9
|
import { loadCapability } from "../discovery";
|
|
10
10
|
// Embed command markdown files at build time
|
|
11
11
|
import initMd from "../prompts/agents/init.md" with { type: "text" };
|
|
12
|
+
import orchestrateMd from "../prompts/commands/orchestrate.md" with { type: "text" };
|
|
12
13
|
|
|
13
|
-
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
14
|
+
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
15
|
+
{ name: "init.md", content: prompt.render(initMd) },
|
|
16
|
+
{ name: "orchestrate.md", content: prompt.render(orchestrateMd) },
|
|
17
|
+
];
|
|
14
18
|
|
|
15
19
|
export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
|
|
16
20
|
|
package/src/tools/fetch.ts
CHANGED
|
@@ -1220,13 +1220,13 @@ function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: bool
|
|
|
1220
1220
|
|
|
1221
1221
|
async function buildReadUrlCacheEntry(
|
|
1222
1222
|
session: ToolSession,
|
|
1223
|
-
params: { path: string;
|
|
1223
|
+
params: { path: string; raw?: boolean },
|
|
1224
1224
|
signal?: AbortSignal,
|
|
1225
1225
|
options?: { ensureArtifact?: boolean },
|
|
1226
1226
|
): Promise<ReadUrlCacheEntry> {
|
|
1227
|
-
const { path: url,
|
|
1227
|
+
const { path: url, raw = false } = params;
|
|
1228
1228
|
|
|
1229
|
-
const effectiveTimeout = clampTimeout("fetch",
|
|
1229
|
+
const effectiveTimeout = clampTimeout("fetch", 30);
|
|
1230
1230
|
|
|
1231
1231
|
if (signal?.aborted) {
|
|
1232
1232
|
throw new ToolAbortError();
|
|
@@ -1254,7 +1254,7 @@ async function buildReadUrlCacheEntry(
|
|
|
1254
1254
|
|
|
1255
1255
|
export async function loadReadUrlCacheEntry(
|
|
1256
1256
|
session: ToolSession,
|
|
1257
|
-
params: { path: string;
|
|
1257
|
+
params: { path: string; raw?: boolean },
|
|
1258
1258
|
signal?: AbortSignal,
|
|
1259
1259
|
options?: { ensureArtifact?: boolean; preferCached?: boolean },
|
|
1260
1260
|
): Promise<ReadUrlCacheEntry> {
|
|
@@ -1291,7 +1291,7 @@ function buildUrlReadOutput(result: FetchRenderResult, content: string): string
|
|
|
1291
1291
|
|
|
1292
1292
|
export async function executeReadUrl(
|
|
1293
1293
|
session: ToolSession,
|
|
1294
|
-
params: { path: string;
|
|
1294
|
+
params: { path: string; raw?: boolean },
|
|
1295
1295
|
signal?: AbortSignal,
|
|
1296
1296
|
): Promise<AgentToolResult<ReadUrlToolDetails>> {
|
|
1297
1297
|
let cacheEntry = await loadReadUrlCacheEntry(session, params, signal, { preferCached: true });
|
|
@@ -1345,7 +1345,7 @@ function countNonEmptyLines(text: string): number {
|
|
|
1345
1345
|
|
|
1346
1346
|
/** Render URL read call (URL preview) */
|
|
1347
1347
|
export function renderReadUrlCall(
|
|
1348
|
-
args: { path?: string; url?: string;
|
|
1348
|
+
args: { path?: string; url?: string; raw?: boolean },
|
|
1349
1349
|
_options: RenderResultOptions,
|
|
1350
1350
|
uiTheme: Theme = theme,
|
|
1351
1351
|
): Component {
|
|
@@ -1355,7 +1355,6 @@ export function renderReadUrlCall(
|
|
|
1355
1355
|
const description = `${domain}${path ? ` ${path}` : ""}`.trim();
|
|
1356
1356
|
const meta: string[] = [];
|
|
1357
1357
|
if (args.raw) meta.push("raw");
|
|
1358
|
-
if (args.timeout !== undefined) meta.push(`timeout:${args.timeout}s`);
|
|
1359
1358
|
const text = renderStatusLine({ icon: "pending", title: "Read", description, meta }, uiTheme);
|
|
1360
1359
|
return new Text(text, 0, 0);
|
|
1361
1360
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -37,7 +37,6 @@ import { HindsightRetainTool } from "./hindsight-retain";
|
|
|
37
37
|
import { InspectImageTool } from "./inspect-image";
|
|
38
38
|
import { IrcTool } from "./irc";
|
|
39
39
|
import { JobTool } from "./job";
|
|
40
|
-
import { NotebookTool } from "./notebook";
|
|
41
40
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
42
41
|
import { ReadTool } from "./read";
|
|
43
42
|
import { RecipeTool } from "./recipe";
|
|
@@ -80,7 +79,6 @@ export * from "./image-gen";
|
|
|
80
79
|
export * from "./inspect-image";
|
|
81
80
|
export * from "./irc";
|
|
82
81
|
export * from "./job";
|
|
83
|
-
export * from "./notebook";
|
|
84
82
|
export * from "./read";
|
|
85
83
|
export * from "./recipe";
|
|
86
84
|
export * from "./render-mermaid";
|
|
@@ -271,7 +269,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
271
269
|
find: s => new FindTool(s),
|
|
272
270
|
search: s => new SearchTool(s),
|
|
273
271
|
lsp: LspTool.createIf,
|
|
274
|
-
notebook: s => new NotebookTool(s),
|
|
275
272
|
inspect_image: s => new InspectImageTool(s),
|
|
276
273
|
browser: s => new BrowserTool(s),
|
|
277
274
|
checkpoint: CheckpointTool.createIf,
|
|
@@ -430,7 +427,6 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
430
427
|
if (name === "ast_grep") return session.settings.get("astGrep.enabled");
|
|
431
428
|
if (name === "ast_edit") return session.settings.get("astEdit.enabled");
|
|
432
429
|
if (name === "render_mermaid") return session.settings.get("renderMermaid.enabled");
|
|
433
|
-
if (name === "notebook") return session.settings.get("notebook.enabled");
|
|
434
430
|
if (name === "inspect_image") return session.settings.get("inspect_image.enabled");
|
|
435
431
|
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
436
432
|
// search_tool_bm25 is allowed when either legacy mcp.discoveryMode or new tools.discoveryMode is active.
|
package/src/tools/read.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Text } from "@oh-my-pi/pi-tui";
|
|
|
9
9
|
import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import { type Static, Type } from "@sinclair/typebox";
|
|
11
11
|
import { formatHashLine, formatHashLines, formatLineHash, HL_BODY_SEP } from "../edit/line-hash";
|
|
12
|
+
import { isNotebookPath, readEditableNotebookText } from "../edit/notebook";
|
|
12
13
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
14
|
import { parseInternalUrl } from "../internal-urls/parse";
|
|
14
15
|
import type { InternalUrl } from "../internal-urls/types";
|
|
@@ -418,7 +419,6 @@ const readSchema = Type.Object({
|
|
|
418
419
|
description: 'path or url; append :<sel> for line ranges or raw mode (e.g. "src/foo.ts:50-100")',
|
|
419
420
|
examples: ["src/foo.ts", "src/foo.ts:50-100", "https://example.com:L1-L40"],
|
|
420
421
|
}),
|
|
421
|
-
timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 20 })),
|
|
422
422
|
});
|
|
423
423
|
|
|
424
424
|
export type ReadToolInput = Static<typeof readSchema>;
|
|
@@ -1084,7 +1084,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1084
1084
|
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
1085
1085
|
_toolContext?: AgentToolContext,
|
|
1086
1086
|
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
1087
|
-
let { path: readPath
|
|
1087
|
+
let { path: readPath } = params;
|
|
1088
1088
|
if (readPath.startsWith("file://")) {
|
|
1089
1089
|
readPath = expandPath(readPath);
|
|
1090
1090
|
}
|
|
@@ -1098,7 +1098,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1098
1098
|
if (parsedUrlTarget.offset !== undefined || parsedUrlTarget.limit !== undefined) {
|
|
1099
1099
|
const cached = await loadReadUrlCacheEntry(
|
|
1100
1100
|
this.session,
|
|
1101
|
-
{ path: parsedUrlTarget.path,
|
|
1101
|
+
{ path: parsedUrlTarget.path, raw: parsedUrlTarget.raw },
|
|
1102
1102
|
signal,
|
|
1103
1103
|
{
|
|
1104
1104
|
ensureArtifact: true,
|
|
@@ -1111,7 +1111,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1111
1111
|
entityLabel: "URL output",
|
|
1112
1112
|
});
|
|
1113
1113
|
}
|
|
1114
|
-
return executeReadUrl(this.session, { path: parsedUrlTarget.path,
|
|
1114
|
+
return executeReadUrl(this.session, { path: parsedUrlTarget.path, raw: parsedUrlTarget.raw }, signal);
|
|
1115
1115
|
}
|
|
1116
1116
|
|
|
1117
1117
|
// Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://)
|
|
@@ -1196,7 +1196,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1196
1196
|
const ext = path.extname(absolutePath).toLowerCase();
|
|
1197
1197
|
const _hasEditTool = this.session.hasEditTool ?? true;
|
|
1198
1198
|
const _language = getLanguageFromPath(absolutePath);
|
|
1199
|
-
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext)
|
|
1199
|
+
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext);
|
|
1200
1200
|
// Read the file based on type
|
|
1201
1201
|
let content: Array<TextContent | ImageContent> | undefined;
|
|
1202
1202
|
let details: ReadToolDetails = {};
|
|
@@ -1263,8 +1263,20 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1263
1263
|
throw error;
|
|
1264
1264
|
}
|
|
1265
1265
|
}
|
|
1266
|
+
} else if (isNotebookPath(absolutePath) && parsed.kind !== "raw") {
|
|
1267
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
1268
|
+
return this.#buildInMemoryTextResult(
|
|
1269
|
+
await readEditableNotebookText(absolutePath, localReadPath),
|
|
1270
|
+
offset,
|
|
1271
|
+
limit,
|
|
1272
|
+
{
|
|
1273
|
+
details: { resolvedPath: absolutePath },
|
|
1274
|
+
sourcePath: absolutePath,
|
|
1275
|
+
entityLabel: "notebook",
|
|
1276
|
+
},
|
|
1277
|
+
);
|
|
1266
1278
|
} else if (shouldConvertWithMarkit) {
|
|
1267
|
-
// Convert document
|
|
1279
|
+
// Convert document via markit.
|
|
1268
1280
|
const result = await convertFileWithMarkit(absolutePath, signal);
|
|
1269
1281
|
if (result.ok) {
|
|
1270
1282
|
// Apply truncation to converted content
|
|
@@ -1556,7 +1568,6 @@ interface ReadRenderArgs {
|
|
|
1556
1568
|
path?: string;
|
|
1557
1569
|
file_path?: string;
|
|
1558
1570
|
sel?: string;
|
|
1559
|
-
timeout?: number;
|
|
1560
1571
|
// Legacy fields from old schema — tolerated for in-flight tool calls during transition
|
|
1561
1572
|
offset?: number;
|
|
1562
1573
|
limit?: number;
|
package/src/tools/renderers.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { findToolRenderer } from "./find";
|
|
|
22
22
|
import { githubToolRenderer } from "./gh-renderer";
|
|
23
23
|
import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
24
24
|
import { jobToolRenderer } from "./job";
|
|
25
|
-
import { notebookToolRenderer } from "./notebook";
|
|
26
25
|
import { readToolRenderer } from "./read";
|
|
27
26
|
import { recipeToolRenderer } from "./recipe/render";
|
|
28
27
|
import { resolveToolRenderer } from "./resolve";
|
|
@@ -60,7 +59,6 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
60
59
|
find: findToolRenderer as ToolRenderer,
|
|
61
60
|
search: searchToolRenderer as ToolRenderer,
|
|
62
61
|
lsp: lspToolRenderer as ToolRenderer,
|
|
63
|
-
notebook: notebookToolRenderer as ToolRenderer,
|
|
64
62
|
inspect_image: inspectImageToolRenderer as ToolRenderer,
|
|
65
63
|
read: readToolRenderer as ToolRenderer,
|
|
66
64
|
job: jobToolRenderer as ToolRenderer,
|
package/src/tools/write.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type Static, Type } from "@sinclair/typebox";
|
|
|
9
9
|
import { stripHashlinePrefixes } from "../edit";
|
|
10
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
11
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
12
|
-
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
12
|
+
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
|
|
13
13
|
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
14
14
|
import type { ToolSession } from "../sdk";
|
|
15
15
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
@@ -168,6 +168,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
168
168
|
readonly concurrency = "exclusive";
|
|
169
169
|
readonly loadMode = "discoverable";
|
|
170
170
|
readonly summary = "Write content to a file (creates or overwrites)";
|
|
171
|
+
readonly intent = (args: Partial<WriteToolInput>) => (args.path ? `writing ${args.path}` : "writing");
|
|
171
172
|
|
|
172
173
|
readonly #writethrough: WritethroughCallback;
|
|
173
174
|
|
|
@@ -523,52 +524,67 @@ function countLines(text: string): number {
|
|
|
523
524
|
return text.split("\n").length;
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return uiTheme.fg("dim", `${icon} ${lineCount} lines`);
|
|
530
|
-
}
|
|
531
|
-
return uiTheme.fg("dim", `${icon}`);
|
|
527
|
+
function formatLineCountSuffix(lineCount: number, uiTheme: Theme): string {
|
|
528
|
+
if (lineCount <= 0) return "";
|
|
529
|
+
return uiTheme.fg("dim", ` · ${lineCount} line${lineCount === 1 ? "" : "s"}`);
|
|
532
530
|
}
|
|
533
531
|
|
|
534
532
|
function normalizeDisplayText(text: string): string {
|
|
535
533
|
return text.replace(/\r/g, "");
|
|
536
534
|
}
|
|
537
535
|
|
|
538
|
-
function formatStreamingContent(content: string, uiTheme: Theme): string {
|
|
536
|
+
function formatStreamingContent(content: string, language: string | undefined, uiTheme: Theme): string {
|
|
539
537
|
if (!content) return "";
|
|
540
538
|
const lines = normalizeDisplayText(content).split("\n");
|
|
541
|
-
const
|
|
542
|
-
const
|
|
539
|
+
const totalLines = lines.length;
|
|
540
|
+
const startIndex = Math.max(0, totalLines - WRITE_STREAMING_PREVIEW_LINES);
|
|
541
|
+
const visibleLines = lines.slice(startIndex);
|
|
542
|
+
const hidden = startIndex;
|
|
543
|
+
const highlighted = highlightCode(visibleLines.join("\n"), language);
|
|
544
|
+
const lineNumberWidth = String(totalLines).length;
|
|
543
545
|
|
|
544
546
|
let text = "\n\n";
|
|
545
547
|
if (hidden > 0) {
|
|
546
|
-
text += uiTheme.fg("dim", `… (${hidden} earlier
|
|
548
|
+
text += `${uiTheme.fg("dim", `… (${hidden} earlier line${hidden === 1 ? "" : "s"})`)}\n`;
|
|
547
549
|
}
|
|
548
|
-
for (
|
|
549
|
-
|
|
550
|
+
for (let i = 0; i < highlighted.length; i++) {
|
|
551
|
+
const lineNum = startIndex + i + 1;
|
|
552
|
+
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")}│`);
|
|
553
|
+
const body = replaceTabs(highlighted[i] ?? "");
|
|
554
|
+
text += ` ${gutter}${body}\n`;
|
|
550
555
|
}
|
|
551
556
|
text += uiTheme.fg("dim", `… (streaming)`);
|
|
552
557
|
return text;
|
|
553
558
|
}
|
|
554
559
|
|
|
555
|
-
function renderContentPreview(
|
|
560
|
+
function renderContentPreview(
|
|
561
|
+
content: string,
|
|
562
|
+
expanded: boolean,
|
|
563
|
+
language: string | undefined,
|
|
564
|
+
uiTheme: Theme,
|
|
565
|
+
): string {
|
|
556
566
|
if (!content) return "";
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const
|
|
567
|
+
const rawLines = normalizeDisplayText(content).split("\n");
|
|
568
|
+
const totalLines = rawLines.length;
|
|
569
|
+
const maxLines = expanded ? totalLines : Math.min(totalLines, WRITE_PREVIEW_LINES);
|
|
570
|
+
const visibleLines = rawLines.slice(0, maxLines);
|
|
571
|
+
const highlighted = highlightCode(visibleLines.join("\n"), language);
|
|
572
|
+
const lineNumberWidth = String(maxLines).length;
|
|
573
|
+
const hidden = totalLines - maxLines;
|
|
561
574
|
|
|
562
575
|
let text = "\n\n";
|
|
563
|
-
for (
|
|
564
|
-
|
|
576
|
+
for (let i = 0; i < highlighted.length; i++) {
|
|
577
|
+
const lineNum = i + 1;
|
|
578
|
+
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")}│`);
|
|
579
|
+
const body = replaceTabs(highlighted[i] ?? "");
|
|
580
|
+
text += ` ${gutter}${body}\n`;
|
|
565
581
|
}
|
|
566
582
|
if (!expanded && hidden > 0) {
|
|
567
583
|
const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
|
|
568
584
|
const moreLine = `${formatMoreItems(hidden, "line")}${hint ? ` ${hint}` : ""}`;
|
|
569
585
|
text += uiTheme.fg("dim", moreLine);
|
|
570
586
|
}
|
|
571
|
-
return text;
|
|
587
|
+
return text.trimEnd();
|
|
572
588
|
}
|
|
573
589
|
|
|
574
590
|
export const writeToolRenderer = {
|
|
@@ -588,7 +604,7 @@ export const writeToolRenderer = {
|
|
|
588
604
|
}
|
|
589
605
|
|
|
590
606
|
// Show streaming preview of content (tail)
|
|
591
|
-
text += formatStreamingContent(args.content, uiTheme);
|
|
607
|
+
text += formatStreamingContent(args.content, lang, uiTheme);
|
|
592
608
|
|
|
593
609
|
return new Text(text, 0, 0);
|
|
594
610
|
},
|
|
@@ -606,17 +622,17 @@ export const writeToolRenderer = {
|
|
|
606
622
|
const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
607
623
|
const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
608
624
|
const lineCount = countLines(fileContent);
|
|
625
|
+
const lineSuffix = formatLineCountSuffix(lineCount, uiTheme);
|
|
609
626
|
|
|
610
627
|
// Build header with status icon
|
|
611
628
|
const header = renderStatusLine(
|
|
612
629
|
{
|
|
613
630
|
icon: "success",
|
|
614
631
|
title: "Write",
|
|
615
|
-
description: `${langIcon} ${pathDisplay}`,
|
|
632
|
+
description: `${langIcon} ${pathDisplay}${lineSuffix}`,
|
|
616
633
|
},
|
|
617
634
|
uiTheme,
|
|
618
635
|
);
|
|
619
|
-
const metadataLine = formatMetadataLine(lineCount, lang ?? "text", uiTheme);
|
|
620
636
|
const diagnostics = result.details?.diagnostics;
|
|
621
637
|
|
|
622
638
|
let cached: RenderCache | undefined;
|
|
@@ -628,8 +644,7 @@ export const writeToolRenderer = {
|
|
|
628
644
|
if (cached?.key === key) return cached.lines;
|
|
629
645
|
|
|
630
646
|
let text = header;
|
|
631
|
-
text +=
|
|
632
|
-
text += renderContentPreview(fileContent, expanded, uiTheme);
|
|
647
|
+
text += renderContentPreview(fileContent, expanded, lang, uiTheme);
|
|
633
648
|
|
|
634
649
|
if (diagnostics) {
|
|
635
650
|
const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
|