@oh-my-pi/pi-coding-agent 13.7.3 → 13.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 +9 -0
- package/package.json +7 -7
- package/src/cli.ts +29 -1
- package/src/config/settings-schema.ts +9 -0
- package/src/modes/controllers/input-controller.ts +3 -3
- package/src/prompts/tools/hashline.md +21 -22
- package/src/tools/fetch.ts +21 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.7.4] - 2026-03-04
|
|
6
|
+
### Added
|
|
7
|
+
- Added `fetch.useKagiSummarizer` setting to toggle Kagi Universal Summarizer usage in the fetch tool.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Fixed incorrect message history reference in session title generation that could cause missing or stale titles on first message
|
|
12
|
+
- Added startup check requiring Bun 1.3.7+ for JSONL session parsing (`Bun.JSONL.parseChunk`) and clear upgrade guidance so `/resume` and `--resume` do not silently report missing sessions on older Bun runtimes
|
|
13
|
+
|
|
5
14
|
## [13.7.3] - 2026-03-04
|
|
6
15
|
|
|
7
16
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.7.
|
|
4
|
+
"version": "13.7.5",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.7.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.7.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.7.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.7.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.7.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.7.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.7.5",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.7.5",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.7.5",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.7.5",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.7.5",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.7.5",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
package/src/cli.ts
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { APP_NAME, MIN_BUN_VERSION, VERSION } from "@oh-my-pi/pi-utils";
|
|
3
3
|
/**
|
|
4
4
|
* CLI entry point — registers all commands explicitly and delegates to the
|
|
5
5
|
* lightweight CLI runner from pi-utils.
|
|
6
6
|
*/
|
|
7
7
|
import { type CommandEntry, run } from "@oh-my-pi/pi-utils/cli";
|
|
8
8
|
|
|
9
|
+
function parseSemver(version: string): [number, number, number] {
|
|
10
|
+
function toint(value: string): number {
|
|
11
|
+
const int = Number.parseInt(value, 10);
|
|
12
|
+
if (Number.isNaN(int) || !Number.isFinite(int)) return 0;
|
|
13
|
+
return int;
|
|
14
|
+
}
|
|
15
|
+
const [majorRaw, minorRaw, patchRaw] = version.split(".").map(toint);
|
|
16
|
+
return [majorRaw, minorRaw, patchRaw];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isAtLeastBunVersion(minimum: string): boolean {
|
|
20
|
+
const ver = parseSemver(Bun.version);
|
|
21
|
+
const min = parseSemver(minimum);
|
|
22
|
+
for (let i = 0; i < 3; i++) {
|
|
23
|
+
if (ver[i] !== min[i]) {
|
|
24
|
+
return ver[i] > min[i];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof Bun.JSONL?.parseChunk !== "function" || !isAtLeastBunVersion(MIN_BUN_VERSION)) {
|
|
31
|
+
process.stderr.write(
|
|
32
|
+
`error: Bun runtime must be >= ${MIN_BUN_VERSION} (found v${Bun.version}). Please update Bun: bun upgrade\n`,
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
9
37
|
// Detect known Bun errata that cause TUI crashes (e.g. Bun.stringWidth mishandling OSC sequences).
|
|
10
38
|
if (Bun.stringWidth("\x1b[0m\x1b]8;;\x07") !== 0) {
|
|
11
39
|
process.stderr.write(`error: Bun runtime errata detected (v${Bun.version}). Please update Bun: bun upgrade\n`);
|
|
@@ -490,6 +490,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
490
490
|
default: true,
|
|
491
491
|
ui: { tab: "tools", label: "Enable Fetch", description: "Enable the fetch tool for URL fetching" },
|
|
492
492
|
},
|
|
493
|
+
"fetch.useKagiSummarizer": {
|
|
494
|
+
type: "boolean",
|
|
495
|
+
default: true,
|
|
496
|
+
ui: {
|
|
497
|
+
tab: "tools",
|
|
498
|
+
label: "Use Kagi in Fetch",
|
|
499
|
+
description: "Use Kagi Universal Summarizer when rendering HTML in fetch",
|
|
500
|
+
},
|
|
501
|
+
},
|
|
493
502
|
"web_search.enabled": {
|
|
494
503
|
type: "boolean",
|
|
495
504
|
default: true,
|
|
@@ -300,7 +300,7 @@ export class InputController {
|
|
|
300
300
|
this.ctx.flushPendingBashComponents();
|
|
301
301
|
|
|
302
302
|
// Generate session title on first message
|
|
303
|
-
const hasUserMessages = this.ctx.
|
|
303
|
+
const hasUserMessages = this.ctx.session.messages.some((m: AgentMessage) => m.role === "user");
|
|
304
304
|
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$env.PI_NO_TITLE) {
|
|
305
305
|
const registry = this.ctx.session.modelRegistry;
|
|
306
306
|
const smolModel = this.ctx.settings.getModelRole("smol");
|
|
@@ -393,7 +393,7 @@ export class InputController {
|
|
|
393
393
|
if (allQueued.length === 0) {
|
|
394
394
|
this.ctx.updatePendingMessagesDisplay();
|
|
395
395
|
if (options?.abort) {
|
|
396
|
-
this.ctx.
|
|
396
|
+
this.ctx.session.abort();
|
|
397
397
|
}
|
|
398
398
|
return 0;
|
|
399
399
|
}
|
|
@@ -403,7 +403,7 @@ export class InputController {
|
|
|
403
403
|
this.ctx.editor.setText(combinedText);
|
|
404
404
|
this.ctx.updatePendingMessagesDisplay();
|
|
405
405
|
if (options?.abort) {
|
|
406
|
-
this.ctx.
|
|
406
|
+
this.ctx.session.abort();
|
|
407
407
|
}
|
|
408
408
|
return allQueued.length;
|
|
409
409
|
}
|
|
@@ -1,41 +1,40 @@
|
|
|
1
|
-
Applies precise file edits
|
|
1
|
+
Applies precise, surgical file edits by referencing `LINE#ID` tags from `read` output. Each tag uniquely identifies a line, so edits remain stable even when lines shift.
|
|
2
2
|
|
|
3
3
|
<workflow>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Follow these steps in order for every edit:
|
|
5
|
+
1. You **SHOULD** issue a `read` call before editing to get fresh `LINE#ID` tags. Editing without current tags causes mismatches because other edits or external changes may have shifted line numbers since your last read.
|
|
6
|
+
2. You **MUST** submit one `edit` call per file with all operations. Multiple calls to the same file require re-reading between each one (tags shift after each edit), so batching avoids wasted round-trips. Think your changes through before submitting.
|
|
7
|
+
3. You **MUST** pick the smallest operation per change site. Each operation should be one logical mutation — a single replace, insert, or delete. Combining unrelated changes into one operation makes errors harder to diagnose and recover from.
|
|
7
8
|
</workflow>
|
|
8
9
|
|
|
9
10
|
<operations>
|
|
10
11
|
**`path`** — the path to the file to edit.
|
|
11
12
|
**`move`** — if set, move the file to the given path.
|
|
12
13
|
**`delete`** — if true, delete the file.
|
|
13
|
-
**`edits
|
|
14
|
+
**`edits[n].pos`** — the anchor line. Meaning depends on `op`:
|
|
14
15
|
- `replace`: start of range (or the single line to replace)
|
|
15
16
|
- `prepend`: insert new lines **before** this line; omit for beginning of file
|
|
16
17
|
- `append`: insert new lines **after** this line; omit for end of file
|
|
17
|
-
**`edits
|
|
18
|
-
**`edits
|
|
18
|
+
**`edits[n].end`** — range replace only. The last line of the range (inclusive). Omit for single-line replace.
|
|
19
|
+
**`edits[n].lines`** — the replacement content:
|
|
19
20
|
- `["line1", "line2"]` — replace with these lines (array of strings)
|
|
20
21
|
- `"line1"` — shorthand for `["line1"]` (single-line replace)
|
|
21
22
|
- `[""]` — replace content with a blank line (line preserved, content cleared)
|
|
22
23
|
- `null` or `[]` — **delete** the line(s) entirely
|
|
23
24
|
|
|
24
|
-
Tags
|
|
25
|
-
Edits are applied bottom-up, so earlier tags stay valid even when later ops add or remove lines.
|
|
25
|
+
Tags are applied bottom-up: later edits (by position) are applied first, so earlier tags remain valid even when subsequent ops add or remove lines. Tags **MUST** be referenced from the most recent `read` output.
|
|
26
26
|
</operations>
|
|
27
27
|
|
|
28
28
|
<rules>
|
|
29
|
-
1. **
|
|
30
|
-
2. **Prefer insertion over neighbor rewrites
|
|
31
|
-
3. **
|
|
32
|
-
- If `lines` includes a closing boundary token (`}`, `]`, `)`, `);`, `},`), `end` **MUST** include the original boundary line.
|
|
33
|
-
- You **MUST NOT** set `end` to an interior line and then re-add the boundary token in `lines`; that duplicates the next surviving line.
|
|
29
|
+
1. **Anchor on unique, structural lines.** You **SHOULD** choose anchors like function signatures, class declarations, or distinct statements — lines that appear exactly once. Blank lines, `}`, and `return null;` repeat throughout a file; anchoring on them risks matching the wrong location. When inserting between blocks, anchor on the nearest unique declaration using `prepend` or `append`.
|
|
30
|
+
2. **Prefer insertion over neighbor rewrites.** You **SHOULD** use `prepend`/`append` anchored on a structural boundary (`}`, `]`, `},`) rather than replacing adjacent lines when adding code near existing code. This keeps the edit minimal and avoids accidentally rewriting lines that should stay.
|
|
31
|
+
3. **Include boundary lines in the replaced range.** `end` is inclusive and **MUST** point to the final line being replaced. If your replacement `lines` include a closing token (`}`, `]`, `)`, `);`, `},`), `end` **MUST** include the original closing line. Otherwise the original closer survives and you get a duplicate.
|
|
34
32
|
</rules>
|
|
35
33
|
|
|
36
34
|
<recovery>
|
|
37
|
-
|
|
38
|
-
**
|
|
35
|
+
Edits can fail in two ways. Here is exactly what to do for each:
|
|
36
|
+
1. **Tag mismatch (`>>>`):** The file changed since your last read, so the tag no longer matches. You **MUST** retry using the fresh tags from the error snippet. If the snippet lacks enough context, or if you fail repeatedly, you **MUST** re-read the entire file and submit a simpler, single-op edit.
|
|
37
|
+
2. **No-op (`identical`):** Your replacement is identical to the existing content — nothing changed. You **MUST NOT** resubmit the same edit. Re-read the target lines to understand what is actually there, then adjust your edit.
|
|
39
38
|
</recovery>
|
|
40
39
|
|
|
41
40
|
<example name="single-line replace">
|
|
@@ -121,6 +120,7 @@ Range — add `end`:
|
|
|
121
120
|
</example>
|
|
122
121
|
|
|
123
122
|
<example name="inclusive end avoids duplicate boundary">
|
|
123
|
+
This example demonstrates why `end` must include the original closing line when your replacement also contains that closer.
|
|
124
124
|
```ts
|
|
125
125
|
{{hlinefull 70 "if (ok) {"}}
|
|
126
126
|
{{hlinefull 71 " run();"}}
|
|
@@ -159,7 +159,6 @@ Good — include original `}` in the replaced range when replacement keeps `}`:
|
|
|
159
159
|
}]
|
|
160
160
|
}
|
|
161
161
|
```
|
|
162
|
-
Also apply the same rule to `);`, `],`, and `},` closers: if replacement includes the closer token, `end` must include the original closer line.
|
|
163
162
|
</example>
|
|
164
163
|
|
|
165
164
|
<example name="insert between sibling declarations">
|
|
@@ -191,7 +190,7 @@ Use a trailing `""` to preserve the blank line between top-level sibling declara
|
|
|
191
190
|
</example>
|
|
192
191
|
|
|
193
192
|
<example name="disambiguate anchors">
|
|
194
|
-
Blank lines and repeated patterns (`}`, `return null;`) appear many times
|
|
193
|
+
Blank lines and repeated patterns (`}`, `return null;`) appear many times. Always anchor on a unique line nearby instead.
|
|
195
194
|
```ts
|
|
196
195
|
{{hlinefull 101 "}"}}
|
|
197
196
|
{{hlinefull 102 ""}}
|
|
@@ -233,8 +232,8 @@ Good — anchor on the unique declaration line:
|
|
|
233
232
|
|
|
234
233
|
<critical>
|
|
235
234
|
- Edit payload: `{ path, edits[] }`. Each entry: `op`, `lines`, optional `pos`/`end`. No extra keys.
|
|
236
|
-
- Every tag **MUST** be copied exactly from
|
|
237
|
-
- You **MUST** re-read after each edit call before issuing another on same file.
|
|
238
|
-
-
|
|
239
|
-
- `lines` entries **MUST** be literal file content with indentation copied exactly from the `read` output. If the file uses tabs, use `\t` in JSON (a real tab character)
|
|
235
|
+
- Every tag **MUST** be copied exactly from your most recent `read` output as `N#ID`. Stale or mistyped tags cause mismatches.
|
|
236
|
+
- You **MUST** re-read the file after each edit call before issuing another on the same file. Tags shift after every edit, so reusing old tags produces mismatches.
|
|
237
|
+
- You **MUST NOT** use this tool to reformat, reindent, or adjust whitespace — run the project's formatter instead. If the only difference is whitespace, it is formatting; leave it alone.
|
|
238
|
+
- `lines` entries **MUST** be literal file content with indentation copied exactly from the `read` output. If the file uses tabs, use `\t` in JSON (a real tab character). Using `\\t` (backslash + t) writes the literal two-character string `\t` into the file.
|
|
240
239
|
</critical>
|
package/src/tools/fetch.ts
CHANGED
|
@@ -429,6 +429,7 @@ async function renderHtmlToText(
|
|
|
429
429
|
url: string,
|
|
430
430
|
html: string,
|
|
431
431
|
timeout: number,
|
|
432
|
+
useKagiSummarizer: boolean,
|
|
432
433
|
userSignal?: AbortSignal,
|
|
433
434
|
): Promise<{ content: string; ok: boolean; method: string }> {
|
|
434
435
|
const signal = ptree.combineSignals(userSignal, timeout * 1000);
|
|
@@ -440,15 +441,17 @@ async function renderHtmlToText(
|
|
|
440
441
|
signal,
|
|
441
442
|
};
|
|
442
443
|
|
|
443
|
-
// Try Kagi Universal Summarizer first (if KAGI_API_KEY is configured)
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
444
|
+
// Try Kagi Universal Summarizer first (if enabled and KAGI_API_KEY is configured)
|
|
445
|
+
if (useKagiSummarizer) {
|
|
446
|
+
try {
|
|
447
|
+
const kagiSummary = await summarizeUrlWithKagi(url, { signal });
|
|
448
|
+
if (kagiSummary && kagiSummary.length > 100 && !isLowQualityOutput(kagiSummary)) {
|
|
449
|
+
return { content: kagiSummary, ok: true, method: "kagi" };
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
// Kagi failed, continue to next method
|
|
453
|
+
signal?.throwIfAborted();
|
|
448
454
|
}
|
|
449
|
-
} catch {
|
|
450
|
-
// Kagi failed, continue to next method
|
|
451
|
-
signal?.throwIfAborted();
|
|
452
455
|
}
|
|
453
456
|
|
|
454
457
|
// Try jina next (reader API)
|
|
@@ -564,7 +567,13 @@ async function handleSpecialUrls(url: string, timeout: number, signal?: AbortSig
|
|
|
564
567
|
/**
|
|
565
568
|
* Main render function implementing the full pipeline
|
|
566
569
|
*/
|
|
567
|
-
async function renderUrl(
|
|
570
|
+
async function renderUrl(
|
|
571
|
+
url: string,
|
|
572
|
+
timeout: number,
|
|
573
|
+
raw: boolean,
|
|
574
|
+
useKagiSummarizer: boolean,
|
|
575
|
+
signal?: AbortSignal,
|
|
576
|
+
): Promise<RenderResult> {
|
|
568
577
|
const notes: string[] = [];
|
|
569
578
|
const fetchedAt = new Date().toISOString();
|
|
570
579
|
if (signal?.aborted) {
|
|
@@ -803,7 +812,7 @@ async function renderUrl(url: string, timeout: number, raw: boolean, signal?: Ab
|
|
|
803
812
|
}
|
|
804
813
|
|
|
805
814
|
// Step 6: Render HTML with lynx or html2text
|
|
806
|
-
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, signal);
|
|
815
|
+
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, useKagiSummarizer, signal);
|
|
807
816
|
if (!htmlResult.ok) {
|
|
808
817
|
notes.push("html rendering failed (lynx/html2text unavailable)");
|
|
809
818
|
const output = finalizeOutput(rawContent);
|
|
@@ -926,7 +935,8 @@ export class FetchTool implements AgentTool<typeof fetchSchema, FetchToolDetails
|
|
|
926
935
|
throw new ToolAbortError();
|
|
927
936
|
}
|
|
928
937
|
|
|
929
|
-
const
|
|
938
|
+
const useKagiSummarizer = this.session.settings.get("fetch.useKagiSummarizer");
|
|
939
|
+
const result = await renderUrl(url, effectiveTimeout, raw, useKagiSummarizer, signal);
|
|
930
940
|
const truncation = truncateHead(result.content, {
|
|
931
941
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
932
942
|
maxLines: FETCH_DEFAULT_MAX_LINES,
|