@oh-my-pi/pi-coding-agent 14.0.3 → 14.0.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 +63 -1
- package/package.json +11 -8
- package/src/config/model-registry.ts +3 -2
- package/src/config/model-resolver.ts +33 -25
- package/src/config/settings.ts +9 -2
- package/src/dap/session.ts +31 -39
- package/src/debug/log-formatting.ts +2 -2
- package/src/edit/index.ts +2 -0
- package/src/edit/modes/chunk.ts +45 -16
- package/src/edit/modes/hashline.ts +2 -2
- package/src/ipy/executor.ts +3 -7
- package/src/ipy/kernel.ts +3 -3
- package/src/lsp/client.ts +4 -2
- package/src/lsp/index.ts +4 -9
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/utils.ts +27 -143
- package/src/modes/components/diff.ts +1 -1
- package/src/modes/controllers/event-controller.ts +438 -426
- package/src/modes/theme/mermaid-cache.ts +5 -7
- package/src/modes/theme/theme.ts +2 -161
- package/src/priority.json +8 -0
- package/src/prompts/agents/designer.md +1 -2
- package/src/prompts/system/system-prompt.md +40 -2
- package/src/prompts/tools/chunk-edit.md +66 -38
- package/src/prompts/tools/read-chunk.md +10 -1
- package/src/sdk.ts +2 -1
- package/src/session/agent-session.ts +10 -0
- package/src/session/compaction/compaction.ts +1 -1
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/browser.ts +84 -21
- package/src/tools/fetch.ts +1 -1
- package/src/tools/find.ts +40 -94
- package/src/tools/gemini-image.ts +1 -0
- package/src/tools/index.ts +2 -3
- package/src/tools/read.ts +2 -0
- package/src/tools/render-utils.ts +1 -1
- package/src/tools/report-tool-issue.ts +2 -2
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/image-resize.ts +73 -37
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/sixel.ts +2 -2
- package/src/web/scrapers/types.ts +50 -32
- package/src/web/search/providers/codex.ts +21 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { extractMermaidBlocks, logger, renderMermaidAsciiSafe } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
|
-
const cache = new Map<bigint, string>();
|
|
4
|
-
const failed = new Set<bigint>();
|
|
3
|
+
const cache = new Map<bigint | number, string | null>();
|
|
5
4
|
|
|
6
5
|
let onRenderNeeded: (() => void) | null = null;
|
|
7
6
|
|
|
@@ -16,7 +15,7 @@ export function setMermaidRenderCallback(callback: (() => void) | null): void {
|
|
|
16
15
|
* Get a pre-rendered mermaid ASCII diagram by hash.
|
|
17
16
|
* Returns null if not cached or rendering failed.
|
|
18
17
|
*/
|
|
19
|
-
export function getMermaidAscii(hash: bigint): string | null {
|
|
18
|
+
export function getMermaidAscii(hash: bigint | number): string | null {
|
|
20
19
|
return cache.get(hash) ?? null;
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -31,14 +30,14 @@ export function prerenderMermaid(markdown: string): void {
|
|
|
31
30
|
let hasNew = false;
|
|
32
31
|
|
|
33
32
|
for (const { source, hash } of blocks) {
|
|
34
|
-
if (cache.has(hash)
|
|
33
|
+
if (cache.has(hash)) continue;
|
|
35
34
|
|
|
36
35
|
const ascii = renderMermaidAsciiSafe(source);
|
|
37
36
|
if (ascii) {
|
|
38
37
|
cache.set(hash, ascii);
|
|
39
38
|
hasNew = true;
|
|
40
39
|
} else {
|
|
41
|
-
|
|
40
|
+
cache.set(hash, null);
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -58,7 +57,7 @@ export function prerenderMermaid(markdown: string): void {
|
|
|
58
57
|
*/
|
|
59
58
|
export function hasPendingMermaid(markdown: string): boolean {
|
|
60
59
|
const blocks = extractMermaidBlocks(markdown);
|
|
61
|
-
return blocks.some(({ hash }) => !cache.has(hash)
|
|
60
|
+
return blocks.some(({ hash }) => !cache.has(hash));
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
/**
|
|
@@ -66,5 +65,4 @@ export function hasPendingMermaid(markdown: string): boolean {
|
|
|
66
65
|
*/
|
|
67
66
|
export function clearMermaidCache(): void {
|
|
68
67
|
cache.clear();
|
|
69
|
-
failed.clear();
|
|
70
68
|
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { defaultThemes } from "./defaults";
|
|
|
20
20
|
import lightThemeJson from "./light.json" with { type: "json" };
|
|
21
21
|
import { getMermaidAscii } from "./mermaid-cache";
|
|
22
22
|
|
|
23
|
+
export { getLanguageFromPath } from "../../utils/lang-from-path";
|
|
24
|
+
|
|
23
25
|
// ============================================================================
|
|
24
26
|
// Symbol Presets
|
|
25
27
|
// ============================================================================
|
|
@@ -2305,167 +2307,6 @@ export function highlightCode(code: string, lang?: string): string[] {
|
|
|
2305
2307
|
}
|
|
2306
2308
|
}
|
|
2307
2309
|
|
|
2308
|
-
/**
|
|
2309
|
-
* Get language identifier from file path extension.
|
|
2310
|
-
*/
|
|
2311
|
-
export function getLanguageFromPath(filePath: string): string | undefined {
|
|
2312
|
-
const baseName = path.basename(filePath).toLowerCase();
|
|
2313
|
-
if (baseName === ".env" || baseName.startsWith(".env.")) return "env";
|
|
2314
|
-
if (
|
|
2315
|
-
baseName === ".gitignore" ||
|
|
2316
|
-
baseName === ".gitattributes" ||
|
|
2317
|
-
baseName === ".gitmodules" ||
|
|
2318
|
-
baseName === ".editorconfig" ||
|
|
2319
|
-
baseName === ".npmrc" ||
|
|
2320
|
-
baseName === ".prettierrc" ||
|
|
2321
|
-
baseName === ".eslintrc"
|
|
2322
|
-
) {
|
|
2323
|
-
return "conf";
|
|
2324
|
-
}
|
|
2325
|
-
if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
|
|
2326
|
-
return "dockerfile";
|
|
2327
|
-
}
|
|
2328
|
-
if (baseName === "justfile") return "just";
|
|
2329
|
-
if (baseName === "cmakelists.txt") return "cmake";
|
|
2330
|
-
|
|
2331
|
-
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
2332
|
-
if (!ext) return undefined;
|
|
2333
|
-
|
|
2334
|
-
const extToLang: Record<string, string> = {
|
|
2335
|
-
ts: "typescript",
|
|
2336
|
-
cts: "typescript",
|
|
2337
|
-
mts: "typescript",
|
|
2338
|
-
tsx: "tsx",
|
|
2339
|
-
js: "javascript",
|
|
2340
|
-
jsx: "javascript",
|
|
2341
|
-
mjs: "javascript",
|
|
2342
|
-
cjs: "javascript",
|
|
2343
|
-
py: "python",
|
|
2344
|
-
pyi: "python",
|
|
2345
|
-
rb: "ruby",
|
|
2346
|
-
rbw: "ruby",
|
|
2347
|
-
gemspec: "ruby",
|
|
2348
|
-
rs: "rust",
|
|
2349
|
-
go: "go",
|
|
2350
|
-
java: "java",
|
|
2351
|
-
kt: "kotlin",
|
|
2352
|
-
ktm: "kotlin",
|
|
2353
|
-
kts: "kotlin",
|
|
2354
|
-
swift: "swift",
|
|
2355
|
-
c: "c",
|
|
2356
|
-
h: "c",
|
|
2357
|
-
cpp: "cpp",
|
|
2358
|
-
cc: "cpp",
|
|
2359
|
-
cxx: "cpp",
|
|
2360
|
-
hh: "cpp",
|
|
2361
|
-
hpp: "cpp",
|
|
2362
|
-
cu: "cpp",
|
|
2363
|
-
ino: "cpp",
|
|
2364
|
-
cs: "csharp",
|
|
2365
|
-
clj: "clojure",
|
|
2366
|
-
cljc: "clojure",
|
|
2367
|
-
cljs: "clojure",
|
|
2368
|
-
edn: "clojure",
|
|
2369
|
-
php: "php",
|
|
2370
|
-
sh: "bash",
|
|
2371
|
-
bash: "bash",
|
|
2372
|
-
zsh: "bash",
|
|
2373
|
-
ksh: "bash",
|
|
2374
|
-
bats: "bash",
|
|
2375
|
-
tmux: "bash",
|
|
2376
|
-
cgi: "bash",
|
|
2377
|
-
fcgi: "bash",
|
|
2378
|
-
command: "bash",
|
|
2379
|
-
tool: "bash",
|
|
2380
|
-
fish: "fish",
|
|
2381
|
-
ps1: "powershell",
|
|
2382
|
-
psm1: "powershell",
|
|
2383
|
-
sql: "sql",
|
|
2384
|
-
html: "html",
|
|
2385
|
-
htm: "html",
|
|
2386
|
-
xhtml: "html",
|
|
2387
|
-
astro: "astro",
|
|
2388
|
-
vue: "vue",
|
|
2389
|
-
svelte: "svelte",
|
|
2390
|
-
css: "css",
|
|
2391
|
-
scss: "scss",
|
|
2392
|
-
sass: "sass",
|
|
2393
|
-
less: "less",
|
|
2394
|
-
json: "json",
|
|
2395
|
-
ipynb: "ipynb",
|
|
2396
|
-
hbs: "handlebars",
|
|
2397
|
-
hsb: "handlebars",
|
|
2398
|
-
handlebars: "handlebars",
|
|
2399
|
-
yaml: "yaml",
|
|
2400
|
-
yml: "yaml",
|
|
2401
|
-
toml: "toml",
|
|
2402
|
-
xml: "xml",
|
|
2403
|
-
xsl: "xml",
|
|
2404
|
-
xslt: "xml",
|
|
2405
|
-
svg: "xml",
|
|
2406
|
-
plist: "xml",
|
|
2407
|
-
md: "markdown",
|
|
2408
|
-
markdown: "markdown",
|
|
2409
|
-
mdx: "markdown",
|
|
2410
|
-
diff: "diff",
|
|
2411
|
-
patch: "diff",
|
|
2412
|
-
dockerfile: "dockerfile",
|
|
2413
|
-
containerfile: "dockerfile",
|
|
2414
|
-
makefile: "make",
|
|
2415
|
-
justfile: "just",
|
|
2416
|
-
mk: "make",
|
|
2417
|
-
mak: "make",
|
|
2418
|
-
cmake: "cmake",
|
|
2419
|
-
lua: "lua",
|
|
2420
|
-
jl: "julia",
|
|
2421
|
-
pl: "perl",
|
|
2422
|
-
pm: "perl",
|
|
2423
|
-
perl: "perl",
|
|
2424
|
-
r: "r",
|
|
2425
|
-
scala: "scala",
|
|
2426
|
-
sc: "scala",
|
|
2427
|
-
sbt: "scala",
|
|
2428
|
-
ex: "elixir",
|
|
2429
|
-
exs: "elixir",
|
|
2430
|
-
erl: "erlang",
|
|
2431
|
-
hs: "haskell",
|
|
2432
|
-
nix: "nix",
|
|
2433
|
-
odin: "odin",
|
|
2434
|
-
zig: "zig",
|
|
2435
|
-
star: "starlark",
|
|
2436
|
-
bzl: "starlark",
|
|
2437
|
-
sol: "solidity",
|
|
2438
|
-
v: "verilog",
|
|
2439
|
-
sv: "verilog",
|
|
2440
|
-
svh: "verilog",
|
|
2441
|
-
vh: "verilog",
|
|
2442
|
-
m: "objc",
|
|
2443
|
-
mm: "objc",
|
|
2444
|
-
ml: "ocaml",
|
|
2445
|
-
vim: "vim",
|
|
2446
|
-
graphql: "graphql",
|
|
2447
|
-
proto: "protobuf",
|
|
2448
|
-
tf: "hcl",
|
|
2449
|
-
hcl: "hcl",
|
|
2450
|
-
tfvars: "hcl",
|
|
2451
|
-
txt: "text",
|
|
2452
|
-
text: "text",
|
|
2453
|
-
log: "log",
|
|
2454
|
-
csv: "csv",
|
|
2455
|
-
tsv: "tsv",
|
|
2456
|
-
ini: "ini",
|
|
2457
|
-
cfg: "conf",
|
|
2458
|
-
conf: "conf",
|
|
2459
|
-
config: "conf",
|
|
2460
|
-
properties: "conf",
|
|
2461
|
-
tla: "tlaplus",
|
|
2462
|
-
tlaplus: "tlaplus",
|
|
2463
|
-
env: "env",
|
|
2464
|
-
};
|
|
2465
|
-
|
|
2466
|
-
return extToLang[ext];
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
2310
|
export function getSymbolTheme(): SymbolTheme {
|
|
2470
2311
|
const preset = theme.getSymbolPreset();
|
|
2471
2312
|
|
package/src/priority.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: designer
|
|
3
3
|
description: UI/UX specialist for design implementation, review, visual refinement
|
|
4
|
-
|
|
5
|
-
model: google-gemini-cli/gemini-3.1-pro, google-gemini-cli/gemini-3-pro, gemini-3.1-pro, gemini-3-1-pro, gemini-3-pro, gemini-3, pi/default
|
|
4
|
+
model: pi/designer
|
|
6
5
|
---
|
|
7
6
|
|
|
8
7
|
You are an expert UI/UX designer implementing and reviewing UI designs.
|
|
@@ -52,9 +52,29 @@ Push back when warranted: state the downside, propose an alternative, but **MUST
|
|
|
52
52
|
<communication>
|
|
53
53
|
- No emojis, filler, or ceremony.
|
|
54
54
|
- (1) Correctness first, (2) Brevity second, (3) Politeness third.
|
|
55
|
-
-
|
|
55
|
+
- Prefer concise, information-dense writing.
|
|
56
|
+
- Avoid repeating the user's request or narrating routine tool calls.
|
|
56
57
|
</communication>
|
|
57
58
|
|
|
59
|
+
<instruction-priority>
|
|
60
|
+
- User instructions override default style, tone, formatting, and initiative preferences.
|
|
61
|
+
- Higher-priority system constraints about safety, permissions, tool boundaries, and task completion do not yield.
|
|
62
|
+
- If a newer user instruction conflicts with an earlier user instruction, follow the newer one.
|
|
63
|
+
- Preserve earlier instructions that do not conflict.
|
|
64
|
+
</instruction-priority>
|
|
65
|
+
|
|
66
|
+
<output-contract>
|
|
67
|
+
- Brief preambles are allowed when they improve orientation, but they **MUST** stay short and **MUST NOT** be treated as completion.
|
|
68
|
+
- Claims about code, tools, tests, docs, or external sources **MUST** be grounded in what you actually observed. If a statement is an inference, say so.
|
|
69
|
+
- Apply brevity to prose, not to evidence, verification, or blocking details.
|
|
70
|
+
</output-contract>
|
|
71
|
+
|
|
72
|
+
<default-follow-through>
|
|
73
|
+
- If the user's intent is clear and the next step is reversible and low-risk, proceed without asking.
|
|
74
|
+
- Ask only when the next step is irreversible, has external side effects, or requires a missing choice that would materially change the outcome.
|
|
75
|
+
- If you proceed, state what you did, what you verified, and what remains optional.
|
|
76
|
+
</default-follow-through>
|
|
77
|
+
|
|
58
78
|
<behavior>
|
|
59
79
|
You **MUST** guard against the completion reflex — the urge to ship something that compiles before you've understood the problem:
|
|
60
80
|
- Compiling ≠ Correctness. "It works" ≠ "Works in all cases".
|
|
@@ -248,6 +268,15 @@ Don't open a file hoping. Hope is not a strategy.
|
|
|
248
268
|
{{#has tools "task"}}- `task` for investigate+edit in one pass — prefer this over a separate explore→task chain{{/has}}
|
|
249
269
|
{{/ifAny}}
|
|
250
270
|
|
|
271
|
+
<tool-persistence>
|
|
272
|
+
- Use tools whenever they materially improve correctness, completeness, or grounding.
|
|
273
|
+
- Do not stop at the first plausible answer if another tool call would materially reduce uncertainty, verify a dependency, or improve coverage.
|
|
274
|
+
- Before taking an action, check whether prerequisite discovery, lookup, or memory retrieval is required. Resolve prerequisites first.
|
|
275
|
+
- If a lookup is empty, partial, or suspiciously narrow, retry with a different strategy before concluding nothing exists.
|
|
276
|
+
- When multiple retrieval steps are independent, parallelize them. When one result determines the next step, keep the workflow sequential.
|
|
277
|
+
- After parallel retrieval, pause to synthesize before making more calls.
|
|
278
|
+
</tool-persistence>
|
|
279
|
+
|
|
251
280
|
{{#if (includes tools "inspect_image")}}
|
|
252
281
|
### Image inspection
|
|
253
282
|
- For image understanding tasks: **MUST** use `inspect_image` over `read` to avoid overloading main session context.
|
|
@@ -262,7 +291,14 @@ These are inviolable. Violation is system failure.
|
|
|
262
291
|
- You **MUST NOT** suppress tests to make code pass. You **MUST NOT** fabricate outputs not observed.
|
|
263
292
|
- You **MUST NOT** solve the wished-for problem instead of the actual problem.
|
|
264
293
|
- You **MUST NOT** ask for information obtainable from tools, repo context, or files.
|
|
265
|
-
- You **MUST** always design a clean solution. You **MUST NOT** introduce unnecessary backwards
|
|
294
|
+
- You **MUST** always design a clean solution. You **MUST NOT** introduce unnecessary backwards compatibility layers, no shims, no gradual migration, no bridges to old code unless user explicitly asks for it. Let the errors guide you on what to include in the refactoring. **ALWAYS default to performing full CUTOVER!**
|
|
295
|
+
|
|
296
|
+
<completeness-contract>
|
|
297
|
+
- Treat the task as incomplete until every requested deliverable is done or explicitly marked [blocked].
|
|
298
|
+
- Keep an internal checklist of requested outcomes, implied cleanup, affected callsites, tests, docs, and follow-on edits.
|
|
299
|
+
- For lists, batches, paginated results, or multi-file migrations, determine expected scope when possible and confirm coverage before yielding.
|
|
300
|
+
- If something is blocked, label it [blocked], say exactly what is missing, and distinguish it from work that is complete.
|
|
301
|
+
</completeness-contract>
|
|
266
302
|
|
|
267
303
|
# Design Integrity
|
|
268
304
|
|
|
@@ -280,6 +316,7 @@ Design integrity means the code tells the truth about what the system currently
|
|
|
280
316
|
{{#has tools "task"}}- You **MUST** determine if the task is parallelizable via `task` tool.{{/has}}
|
|
281
317
|
- If multi-file or imprecisely scoped, you **MUST** write out a step-by-step plan, phased if it warrants, before touching any file.
|
|
282
318
|
- For new work, you **MUST**: (1) think about architecture, (2) search official docs/papers on best practices, (3) review existing codebase, (4) compare research with codebase, (5) implement the best fit or surface tradeoffs.
|
|
319
|
+
- If required context is missing, do **NOT** guess. Prefer tool-based retrieval first, ask a minimal question only when the answer cannot be recovered from tools, repo context, or files.
|
|
283
320
|
## 2. Before You Edit
|
|
284
321
|
- Read the relevant section of any file before editing. Don't edit from a grep snippet alone — context above and below the match changes what the correct edit is.
|
|
285
322
|
- You **MUST** grep for existing examples before implementing any pattern, utility, or abstraction. If the codebase already solves it, you **MUST** use that. Inventing a parallel convention is **PROHIBITED**.
|
|
@@ -313,6 +350,7 @@ When a tool call fails, read the full error before doing anything else. When a f
|
|
|
313
350
|
- Test everything rigorously → Future contributor cannot break behavior without failure. Prefer unit/e2e.
|
|
314
351
|
- You **MUST NOT** rely on mocks — they invent behaviors that never happen in production and hide real bugs.
|
|
315
352
|
- You **SHOULD** run only tests you added/modified unless asked otherwise.
|
|
353
|
+
- Before yielding, verify: (1) every requirement is satisfied, (2) claims match files/tool output/source material, (3) the output format matches the ask, and (4) any high-impact action was either verified or explicitly held for permission.
|
|
316
354
|
- You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
|
|
317
355
|
|
|
318
356
|
{{#if secretsEnabled}}
|
|
@@ -3,58 +3,66 @@ Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit
|
|
|
3
3
|
<rules>
|
|
4
4
|
- **MUST** `read` first. Never invent chunk paths or CRCs. Copy them from the latest `read` output or edit response.
|
|
5
5
|
- `sel` format:
|
|
6
|
-
- insertions: `chunk` or `chunk
|
|
7
|
-
- replacements: `chunk#CRC` or `chunk#CRC
|
|
8
|
-
- Without a
|
|
6
|
+
- insertions: `chunk`, `chunk~`, or `chunk^`
|
|
7
|
+
- replacements: `chunk#CRC`, `chunk#CRC~`, or `chunk#CRC^`
|
|
8
|
+
- Without a suffix it defaults to the entire chunk including leading trivia. `~` targets the body, `^` targets the head.
|
|
9
9
|
- If the exact chunk path is unclear, run `read(path="file", sel="?")` and copy a selector from that listing.
|
|
10
|
-
|
|
10
|
+
{{#if chunkAutoIndent}}
|
|
11
|
+
- Use `\t` for indentation in `content`. Write content at indent-level 0 — the tool re-indents it to match the chunk's position in the file. For example, to replace `~` of a method, write the body starting at column 0:
|
|
11
12
|
```
|
|
12
13
|
content: "if (x) {\n\treturn true;\n}"
|
|
13
14
|
```
|
|
14
15
|
The tool adds the correct base indent automatically. Never manually pad with the chunk's own indentation.
|
|
15
|
-
|
|
16
|
+
{{else}}
|
|
17
|
+
- Match the file's literal tabs/spaces in `content`. Do not convert indentation to canonical `\t`.
|
|
18
|
+
- Write content at indent-level 0 relative to the target region. For example, to replace `~` of a method, write:
|
|
19
|
+
```
|
|
20
|
+
content: "if (x) {\n return true;\n}"
|
|
21
|
+
```
|
|
22
|
+
The tool adds the correct base indent automatically, then preserves the tabs/spaces you used inside the snippet. Never manually pad with the chunk's own indentation.
|
|
23
|
+
{{/if}}
|
|
24
|
+
- Region suffixes only apply to container chunks (classes, functions, impl blocks, sections). On leaf chunks (enum variants, fields, single statements, and compound statements like `if`/`for`/`while`/`match`/`try`), `~` and `^` silently fall back to whole-chunk replacement — prefer the unsuffixed form and always supply the complete replacement (condition + body, not just the body) to avoid dropping structural parts.
|
|
16
25
|
- `replace` requires the current CRC. Insertions do not.
|
|
17
|
-
- **CRCs change after every edit.**
|
|
26
|
+
- **CRCs change after every edit.** The edit response always carries the new CRCs — use those for the next call or run `read(path="file", sel="?")` to refresh. Never reuse a CRC from before the latest edit.
|
|
18
27
|
</rules>
|
|
19
28
|
|
|
20
29
|
<critical>
|
|
21
30
|
You **MUST** use the narrowest region that covers your change. Replacing without a region replaces the **entire chunk including leading comments, decorators, and attributes** — omitting them from `content` deletes them.
|
|
22
31
|
|
|
23
|
-
**`replace` is total, not surgical.** The `content` you supply becomes the *complete* new content for the targeted region. Everything in the original region that you omit from `content` is deleted. Before replacing
|
|
32
|
+
**`replace` is total, not surgical.** The `content` you supply becomes the *complete* new content for the targeted region. Everything in the original region that you omit from `content` is deleted. Before replacing `~` on any chunk, verify the chunk does not contain children you intend to keep. If a chunk spans hundreds of lines and your change touches only a few, target a specific child chunk — not the parent.
|
|
24
33
|
|
|
25
|
-
**Group chunks (`stmts_*`, `imports_*`, `decls_*`) are containers.** They hold many sibling items (test functions, import statements, declarations). Replacing
|
|
34
|
+
**Group chunks (`stmts_*`, `imports_*`, `decls_*`) are containers.** They hold many sibling items (test functions, import statements, declarations). Replacing `~` on a group chunk replaces **all** of its children. To edit one item inside a group, target that item's own chunk path. If no child chunk exists, use the specific child's chunk selector from `read` output — do not replace the parent group.
|
|
26
35
|
</critical>
|
|
27
36
|
|
|
28
37
|
<regions>
|
|
38
|
+
Given a chunk like:
|
|
29
39
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
/// doc comment <-- leading trivia
|
|
41
|
+
#[attr] <-- leading trivia
|
|
42
|
+
fn foo(x: i32) { <-- signature + opening delimiter
|
|
43
|
+
body(); <-- body
|
|
44
|
+
} <-- closing delimiter
|
|
35
45
|
```
|
|
36
|
-
- `@body` — the interior only. **Use for most edits.**
|
|
37
|
-
- `@head` — leading trivia + signature + opening delimiter.
|
|
38
|
-
- `@tail` — the closing delimiter.
|
|
39
|
-
- `@decl` — everything except leading trivia (signature + body + closing delimiter).
|
|
40
|
-
- *(no region)* — the entire chunk including leading trivia. Same as `@head` + `@body` + `@tail`.
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
Append `~` to target the body, `^` to target the head (trivia + signature), or nothing for the whole chunk:
|
|
48
|
+
- `fn_foo#CRC~` — body only. **Use for most edits.** On leaf chunks, falls back to whole chunk.
|
|
49
|
+
- `fn_foo#CRC^` — head (decorators, attributes, doc comments, signature, opening delimiter).
|
|
50
|
+
- `fn_foo#CRC` — entire chunk including leading trivia.
|
|
51
|
+
- `chunk~` + `append`/`prepend` inserts *inside* the container. `chunk` + `append`/`prepend` inserts *outside*.
|
|
52
|
+
|
|
53
|
+
**Note on leading trivia:** whether a decorator/doc comment belongs to `^` depends on the parser. In Rust and Python, attributes and decorators are attached to the function chunk, so `^` covers them. In TypeScript/JavaScript, a `@decorator` + `/** jsdoc */` block immediately above a method often surfaces as a **separate sibling chunk** (shown as `chunk#CRC` in the `?` listing) rather than as part of the function's `^`. If you need to rewrite a decorator, check the `?` listing for a sibling `chunk#CRC` directly above your target.
|
|
43
54
|
|
|
44
|
-
`
|
|
45
|
-
- `class_Foo@body` + `append` → adds inside the class before `}`
|
|
46
|
-
- `class_Foo@body` + `prepend` → adds inside the class after `{`
|
|
47
|
-
- `class_Foo` + `append` → adds after the entire class (after `}`)
|
|
55
|
+
**Note on non-code formats:** for prose and data formats (markdown, YAML, JSON, fenced code blocks, frontmatter), `^` and `~` fall back to the whole chunk. Always replace the entire chunk and include any delimiter syntax (fence backticks, `---` frontmatter markers, list markers) in your `content` — omitting them deletes them. For markdown sections (`sect_*`), always use unsuffixed whole-chunk replace — `^` and `~` on section containers also fall back to whole-chunk replace. When editing fenced code blocks in markdown, use the exact whitespace from the file (read with `raw` first) — the tool preserves literal indentation inside fenced blocks, but any content you supply is written verbatim. To insert content after a markdown section heading, use `after` on the heading chunk (`sect_*.chunk` or `sect_*.chunk_1`) — not `before`/`prepend` on the section itself, which lands physically before the heading and gets absorbed by the preceding section on reparse.
|
|
48
56
|
</regions>
|
|
49
57
|
|
|
50
58
|
<ops>
|
|
51
59
|
|op|sel|effect|
|
|
52
60
|
|---|---|---|
|
|
53
|
-
|`replace`|`chunk#CRC
|
|
54
|
-
|`before`|`chunk
|
|
55
|
-
|`after`|`chunk
|
|
56
|
-
|`prepend`|`chunk
|
|
57
|
-
|`append`|`chunk
|
|
61
|
+
|`replace`|`chunk#CRC`, `chunk#CRC~`, or `chunk#CRC^`|rewrite the addressed region|
|
|
62
|
+
|`before`|`chunk`, `chunk~`, or `chunk^`|insert before the region span|
|
|
63
|
+
|`after`|`chunk`, `chunk~`, or `chunk^`|insert after the region span|
|
|
64
|
+
|`prepend`|`chunk`, `chunk~`, or `chunk^`|insert at the start inside the region|
|
|
65
|
+
|`append`|`chunk`, `chunk~`, or `chunk^`|insert at the end inside the region|
|
|
58
66
|
</ops>
|
|
59
67
|
|
|
60
68
|
<examples>
|
|
@@ -113,7 +121,11 @@ Given this `read` output for `example.ts`:
|
|
|
113
121
|
|
|
114
122
|
**Replace a whole chunk** (rename a function):
|
|
115
123
|
~~~json
|
|
124
|
+
{{#if chunkAutoIndent}}
|
|
116
125
|
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n\tconst c = new Counter();\n\tc.value = start;\n\treturn c;\n}\n" }
|
|
126
|
+
{{else}}
|
|
127
|
+
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n const c = new Counter();\n c.value = start;\n return c;\n}\n" }
|
|
128
|
+
{{/if}}
|
|
117
129
|
~~~
|
|
118
130
|
Result — the entire chunk is rewritten:
|
|
119
131
|
```
|
|
@@ -124,9 +136,9 @@ function makeCounter(start: number): Counter {
|
|
|
124
136
|
}
|
|
125
137
|
```
|
|
126
138
|
|
|
127
|
-
**Replace a method body** (
|
|
139
|
+
**Replace a method body** (`~`):
|
|
128
140
|
```
|
|
129
|
-
{ "sel": "class_Counter.fn_increment#NQWY
|
|
141
|
+
{ "sel": "class_Counter.fn_increment#NQWY~", "op": "replace", "content": "this.value += 1;\nconsole.log('incremented to', this.value);\n" }
|
|
130
142
|
```
|
|
131
143
|
Result — only the body changes, signature and braces are kept:
|
|
132
144
|
```
|
|
@@ -136,9 +148,9 @@ Result — only the body changes, signature and braces are kept:
|
|
|
136
148
|
}
|
|
137
149
|
```
|
|
138
150
|
|
|
139
|
-
**Replace a function header** (
|
|
151
|
+
**Replace a function header** (`^` — signature and doc comment):
|
|
140
152
|
```
|
|
141
|
-
{ "sel": "fn_createCounter#PQQY
|
|
153
|
+
{ "sel": "fn_createCounter#PQQY^", "op": "replace", "content": "/** Creates a counter with the given start value. */\nfunction createCounter(initial: number, label?: string): Counter {\n" }
|
|
142
154
|
```
|
|
143
155
|
Result — adds a doc comment and updates the signature, body untouched:
|
|
144
156
|
```
|
|
@@ -163,7 +175,11 @@ function createCounter(initial: number): Counter {
|
|
|
163
175
|
|
|
164
176
|
**Insert after a chunk** (`after`):
|
|
165
177
|
~~~json
|
|
178
|
+
{{#if chunkAutoIndent}}
|
|
166
179
|
{ "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n\treturn s === Status.Active;\n}\n" }
|
|
180
|
+
{{else}}
|
|
181
|
+
{ "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n return s === Status.Active;\n}\n" }
|
|
182
|
+
{{/if}}
|
|
167
183
|
~~~
|
|
168
184
|
Result — a new function appears after the enum:
|
|
169
185
|
```
|
|
@@ -180,9 +196,9 @@ function isActive(s: Status): boolean {
|
|
|
180
196
|
function createCounter(initial: number): Counter {
|
|
181
197
|
```
|
|
182
198
|
|
|
183
|
-
**Prepend inside a container** (
|
|
199
|
+
**Prepend inside a container** (`~` + `prepend`):
|
|
184
200
|
```
|
|
185
|
-
{ "sel": "class_Counter
|
|
201
|
+
{ "sel": "class_Counter~", "op": "prepend", "content": "label: string = 'default';\n\n" }
|
|
186
202
|
```
|
|
187
203
|
Result — a new field is added at the top of the class body, before existing members:
|
|
188
204
|
```
|
|
@@ -192,9 +208,13 @@ class Counter {
|
|
|
192
208
|
value: number = 0;
|
|
193
209
|
```
|
|
194
210
|
|
|
195
|
-
**Append inside a container** (
|
|
211
|
+
**Append inside a container** (`~` + `append`):
|
|
196
212
|
~~~json
|
|
197
|
-
{
|
|
213
|
+
{{#if chunkAutoIndent}}
|
|
214
|
+
{ "sel": "class_Counter~", "op": "append", "content": "\nreset(): void {\n\tthis.value = 0;\n}\n" }
|
|
215
|
+
{{else}}
|
|
216
|
+
{ "sel": "class_Counter~", "op": "append", "content": "\nreset(): void {\n this.value = 0;\n}\n" }
|
|
217
|
+
{{/if}}
|
|
198
218
|
~~~
|
|
199
219
|
Result — a new method is added at the end of the class body, before the closing `}`:
|
|
200
220
|
```
|
|
@@ -214,10 +234,18 @@ Result — a new method is added at the end of the class body, before the closin
|
|
|
214
234
|
```
|
|
215
235
|
Result — the method is removed from the class.
|
|
216
236
|
- Indentation rules (important):
|
|
237
|
+
{{#if chunkAutoIndent}}
|
|
217
238
|
- Use `\t` for each indent level. The tool converts tabs to the file's actual style (2-space, 4-space, etc.).
|
|
239
|
+
{{else}}
|
|
240
|
+
- Match the file's real indentation characters in your snippet. The tool preserves your literal tabs/spaces after adding the target region's base indent.
|
|
241
|
+
{{/if}}
|
|
218
242
|
- Do NOT include the chunk's base indentation — only indent relative to the region's opening level.
|
|
219
|
-
- For
|
|
220
|
-
- For
|
|
243
|
+
- For `~` of a function: write at column 0, and use `\t` for *relative* nesting. Flat body: `"return x;\n"`. Nested body: `"if (cond) {\n\treturn x;\n}\n"` — the `if` is at column 0, the `return` is one tab in, and the tool adds the method's base indent to both.
|
|
244
|
+
- For `^`: write at the chunk's own depth. A class member's head uses `"/** doc */\nstart(): void {"`.
|
|
245
|
+
{{#if chunkAutoIndent}}
|
|
221
246
|
- For a top-level item: start at zero indent. Write `"function foo() {\n\treturn 1;\n}\n"`.
|
|
247
|
+
{{else}}
|
|
248
|
+
- For a top-level item: start at zero indent. Write `"function foo() {\n return 1;\n}\n"`.
|
|
249
|
+
{{/if}}
|
|
222
250
|
- The tool strips common leading indentation from your content as a safety net, so accidental over-indentation is corrected.
|
|
223
251
|
</examples>
|
|
@@ -2,16 +2,25 @@ Reads files using syntax-aware chunks.
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- `path` — file path or URL; may include `:selector` suffix
|
|
5
|
-
- `sel` — optional selector: `class_Foo`, `class_Foo.fn_bar#ABCD
|
|
5
|
+
- `sel` — optional selector: `class_Foo`, `class_Foo.fn_bar#ABCD~`, `?`, `L50`, `L50-L120`, or `raw`
|
|
6
6
|
- `timeout` — seconds, for URLs only
|
|
7
7
|
|
|
8
8
|
Each opening anchor `[< full.chunk.path#CCCC ]` in the default output identifies a chunk. Use `full.chunk.path#CCCC` as-is to read truncated chunks.
|
|
9
9
|
If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with CRCs.
|
|
10
10
|
Line numbers in the gutter are absolute file line numbers.
|
|
11
11
|
|
|
12
|
+
`L20` (single line, no explicit end) is shorthand for `L20` to end-of-file. Use `L20-L20` for a one-line window.
|
|
13
|
+
|
|
14
|
+
{{#if chunkAutoIndent}}
|
|
15
|
+
Chunk reads normalize leading indentation so copied content round-trips cleanly into chunk edits.
|
|
16
|
+
{{else}}
|
|
17
|
+
Chunk reads preserve literal leading tabs/spaces from the file. When editing, keep the same whitespace characters you see here.
|
|
18
|
+
{{/if}}
|
|
19
|
+
|
|
12
20
|
Chunk trees: JS, TS, TSX, Python, Rust, Go. Others use blank-line fallback.
|
|
13
21
|
</instruction>
|
|
14
22
|
|
|
15
23
|
<critical>
|
|
16
24
|
- **MUST** `read` before editing — never invent chunk names or CRCs.
|
|
25
|
+
- Chunk names are truncated (e.g., `handleRequest` becomes `fn_handleRequ`). Always copy chunk paths from `read` or `?` output — never construct them from source identifiers.
|
|
17
26
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { SearchDb } from "@oh-my-pi/pi-natives";
|
|
|
15
15
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
16
16
|
import {
|
|
17
17
|
$env,
|
|
18
|
+
$flag,
|
|
18
19
|
getAgentDbPath,
|
|
19
20
|
getAgentDir,
|
|
20
21
|
getProjectDir,
|
|
@@ -1262,7 +1263,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1262
1263
|
|
|
1263
1264
|
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
1264
1265
|
const eagerTasks = settings.get("task.eager");
|
|
1265
|
-
const intentField = settings.get("tools.intentTracing") || $
|
|
1266
|
+
const intentField = settings.get("tools.intentTracing") || $flag("PI_INTENT_TRACING") ? INTENT_FIELD : undefined;
|
|
1266
1267
|
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
1267
1268
|
toolContextStore.setToolNames(toolNames);
|
|
1268
1269
|
const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
|
|
@@ -997,6 +997,16 @@ export class AgentSession {
|
|
|
997
997
|
this.#lastAssistantMessage = undefined;
|
|
998
998
|
if (!msg) return;
|
|
999
999
|
|
|
1000
|
+
// Invalidate GitHub Copilot credentials on auth failure so stale tokens
|
|
1001
|
+
// aren't reused on the next request
|
|
1002
|
+
if (
|
|
1003
|
+
msg.stopReason === "error" &&
|
|
1004
|
+
msg.provider === "github-copilot" &&
|
|
1005
|
+
msg.errorMessage?.includes("GitHub Copilot authentication failed")
|
|
1006
|
+
) {
|
|
1007
|
+
await this.#modelRegistry.authStorage.remove("github-copilot");
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1000
1010
|
if (this.#skipPostTurnMaintenanceAssistantTimestamp === msg.timestamp) {
|
|
1001
1011
|
this.#skipPostTurnMaintenanceAssistantTimestamp = undefined;
|
|
1002
1012
|
return;
|
|
@@ -761,7 +761,7 @@ function buildOpenAiNativeHistory(
|
|
|
761
761
|
if (!msgId) {
|
|
762
762
|
msgId = `msg_${msgIndex}`;
|
|
763
763
|
} else if (msgId.length > 64) {
|
|
764
|
-
msgId = `msg_${Bun.hash
|
|
764
|
+
msgId = `msg_${Bun.hash(msgId).toString(36)}`;
|
|
765
765
|
}
|
|
766
766
|
input.push({
|
|
767
767
|
type: "message",
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
3
3
|
import { type AstReplaceChange, astEdit } 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
|
-
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import { computeLineHash } from "../edit/line-hash";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -103,7 +103,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
103
103
|
if (maxReplacements !== undefined && (!Number.isFinite(maxReplacements) || maxReplacements < 1)) {
|
|
104
104
|
throw new ToolError("limit must be a positive number");
|
|
105
105
|
}
|
|
106
|
-
const maxFiles =
|
|
106
|
+
const maxFiles = $envpos("PI_MAX_AST_FILES", 1000);
|
|
107
107
|
|
|
108
108
|
const formatScopePath = (targetPath: string): string => {
|
|
109
109
|
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|