@oh-my-pi/pi-coding-agent 14.8.1 → 14.9.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 +38 -0
- package/package.json +16 -7
- package/src/config/model-resolver.ts +92 -35
- package/src/config/prompt-templates.ts +1 -1
- package/src/debug/index.ts +21 -0
- package/src/debug/raw-sse-buffer.ts +229 -0
- package/src/debug/raw-sse.ts +213 -0
- package/src/edit/index.ts +9 -10
- package/src/edit/streaming.ts +6 -5
- package/src/eval/js/context-manager.ts +91 -47
- package/src/extensibility/extensions/loader.ts +9 -3
- package/src/extensibility/plugins/legacy-pi-compat.ts +99 -20
- package/src/hashline/anchors.ts +113 -0
- package/src/hashline/apply.ts +732 -0
- package/src/hashline/bigrams.json +649 -0
- package/src/hashline/constants.ts +8 -0
- package/src/hashline/diff-preview.ts +43 -0
- package/src/hashline/diff.ts +56 -0
- package/src/hashline/execute.ts +268 -0
- package/src/{edit/modes/hashline.lark → hashline/grammar.lark} +1 -1
- package/src/{edit/line-hash.ts → hashline/hash.ts} +5 -651
- package/src/hashline/index.ts +14 -0
- package/src/hashline/input.ts +110 -0
- package/src/hashline/parser.ts +220 -0
- package/src/hashline/prefixes.ts +101 -0
- package/src/hashline/recovery.ts +72 -0
- package/src/hashline/stream.ts +123 -0
- package/src/hashline/types.ts +69 -0
- package/src/hashline/utils.ts +3 -0
- package/src/index.ts +1 -1
- package/src/lsp/index.ts +1 -1
- package/src/lsp/render.ts +4 -0
- package/src/memories/index.ts +13 -4
- package/src/modes/components/assistant-message.ts +55 -9
- package/src/modes/components/welcome.ts +114 -38
- package/src/modes/controllers/event-controller.ts +3 -1
- package/src/modes/controllers/input-controller.ts +8 -1
- package/src/modes/interactive-mode.ts +9 -9
- package/src/modes/rpc/rpc-client.ts +53 -2
- package/src/modes/rpc/rpc-mode.ts +67 -1
- package/src/modes/rpc/rpc-types.ts +17 -2
- package/src/modes/utils/ui-helpers.ts +3 -1
- package/src/prompts/agents/reviewer.md +14 -0
- package/src/prompts/tools/hashline.md +57 -10
- package/src/sdk.ts +4 -3
- package/src/session/agent-session.ts +195 -30
- package/src/session/compaction/branch-summarization.ts +4 -2
- package/src/session/compaction/compaction.ts +22 -3
- package/src/task/executor.ts +21 -2
- package/src/task/index.ts +4 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/match-line-format.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/title-generator.ts +11 -0
- package/src/edit/modes/hashline.ts +0 -2039
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { formatCodeFrameLine } from "../tools/render-utils";
|
|
2
|
+
import { MISMATCH_CONTEXT } from "./constants";
|
|
3
|
+
import { computeLineHash, describeAnchorExamples, HL_ANCHOR_RE_RAW, HL_BODY_SEP } from "./hash";
|
|
4
|
+
import type { HashMismatch } from "./types";
|
|
5
|
+
|
|
6
|
+
const HL_HASH_HINT_RE = /^[a-z]{2}$/i;
|
|
7
|
+
const HL_ANCHOR_EXAMPLES = describeAnchorExamples("160");
|
|
8
|
+
const PARSE_TAG_RE = new RegExp(`^${HL_ANCHOR_RE_RAW}`);
|
|
9
|
+
|
|
10
|
+
export function formatFullAnchorRequirement(raw?: string): string {
|
|
11
|
+
const suffix = typeof raw === "string" ? raw.trim() : "";
|
|
12
|
+
const hashOnlyHint = HL_HASH_HINT_RE.test(suffix)
|
|
13
|
+
? ` It looks like you supplied only the hash suffix (${JSON.stringify(suffix)}). ` +
|
|
14
|
+
`Copy the full anchor exactly as shown (for example, "160${suffix}").`
|
|
15
|
+
: "";
|
|
16
|
+
const received = raw === undefined ? "" : ` Received ${JSON.stringify(raw)}.`;
|
|
17
|
+
return (
|
|
18
|
+
`the full anchor exactly as shown by read/search output ` +
|
|
19
|
+
`(line number + hash, for example ${HL_ANCHOR_EXAMPLES})${received}${hashOnlyHint}`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseTag(ref: string): { line: number; hash: string } {
|
|
24
|
+
const match = ref.match(PARSE_TAG_RE);
|
|
25
|
+
if (!match) {
|
|
26
|
+
throw new Error(`Invalid line reference. Expected ${formatFullAnchorRequirement(ref)}.`);
|
|
27
|
+
}
|
|
28
|
+
const line = Number.parseInt(match[1], 10);
|
|
29
|
+
if (line < 1) throw new Error(`Line number must be >= 1, got ${line} in "${ref}".`);
|
|
30
|
+
return { line, hash: match[2] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getMismatchDisplayLines(mismatches: HashMismatch[], fileLines: string[]): number[] {
|
|
34
|
+
const displayLines = new Set<number>();
|
|
35
|
+
for (const mismatch of mismatches) {
|
|
36
|
+
const lo = Math.max(1, mismatch.line - MISMATCH_CONTEXT);
|
|
37
|
+
const hi = Math.min(fileLines.length, mismatch.line + MISMATCH_CONTEXT);
|
|
38
|
+
for (let lineNum = lo; lineNum <= hi; lineNum++) displayLines.add(lineNum);
|
|
39
|
+
}
|
|
40
|
+
return [...displayLines].sort((a, b) => a - b);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class HashlineMismatchError extends Error {
|
|
44
|
+
readonly remaps: ReadonlyMap<string, string>;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
public readonly mismatches: HashMismatch[],
|
|
48
|
+
public readonly fileLines: string[],
|
|
49
|
+
) {
|
|
50
|
+
super(HashlineMismatchError.formatMessage(mismatches, fileLines));
|
|
51
|
+
this.name = "HashlineMismatchError";
|
|
52
|
+
|
|
53
|
+
const remaps = new Map<string, string>();
|
|
54
|
+
for (const mismatch of mismatches) {
|
|
55
|
+
const actual = computeLineHash(mismatch.line, fileLines[mismatch.line - 1] ?? "");
|
|
56
|
+
remaps.set(`${mismatch.line}${mismatch.expected}`, `${mismatch.line}${actual}`);
|
|
57
|
+
}
|
|
58
|
+
this.remaps = remaps;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get displayMessage(): string {
|
|
62
|
+
return HashlineMismatchError.formatDisplayMessage(this.mismatches, this.fileLines);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private static rejectionHeader(mismatches: HashMismatch[]): string[] {
|
|
66
|
+
const noun = mismatches.length > 1 ? "lines have" : "line has";
|
|
67
|
+
return [
|
|
68
|
+
`Edit rejected: ${mismatches.length} ${noun} changed since the last read (marked *).`,
|
|
69
|
+
"The edit was NOT applied, please use the updated file content shown below, and issue another edit tool-call.",
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static formatDisplayMessage(mismatches: HashMismatch[], fileLines: string[]): string {
|
|
74
|
+
const mismatchSet = new Set<number>(mismatches.map(m => m.line));
|
|
75
|
+
const displayLines = getMismatchDisplayLines(mismatches, fileLines);
|
|
76
|
+
const width = displayLines.reduce((cur, n) => Math.max(cur, String(n).length), 0);
|
|
77
|
+
|
|
78
|
+
const out = [...HashlineMismatchError.rejectionHeader(mismatches), ""];
|
|
79
|
+
let previous = -1;
|
|
80
|
+
for (const lineNum of displayLines) {
|
|
81
|
+
if (previous !== -1 && lineNum > previous + 1) out.push("...");
|
|
82
|
+
previous = lineNum;
|
|
83
|
+
const marker = mismatchSet.has(lineNum) ? "*" : " ";
|
|
84
|
+
out.push(formatCodeFrameLine(marker, lineNum, fileLines[lineNum - 1] ?? "", width));
|
|
85
|
+
}
|
|
86
|
+
return out.join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static formatMessage(mismatches: HashMismatch[], fileLines: string[]): string {
|
|
90
|
+
const mismatchSet = new Set<number>(mismatches.map(m => m.line));
|
|
91
|
+
const lines = HashlineMismatchError.rejectionHeader(mismatches);
|
|
92
|
+
let previous = -1;
|
|
93
|
+
for (const lineNum of getMismatchDisplayLines(mismatches, fileLines)) {
|
|
94
|
+
if (previous !== -1 && lineNum > previous + 1) lines.push("...");
|
|
95
|
+
previous = lineNum;
|
|
96
|
+
const text = fileLines[lineNum - 1] ?? "";
|
|
97
|
+
const hash = computeLineHash(lineNum, text);
|
|
98
|
+
const marker = mismatchSet.has(lineNum) ? "*" : " ";
|
|
99
|
+
lines.push(`${marker}${lineNum}${hash}${HL_BODY_SEP}${text}`);
|
|
100
|
+
}
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function validateLineRef(ref: { line: number; hash: string }, fileLines: string[]): void {
|
|
106
|
+
if (ref.line < 1 || ref.line > fileLines.length) {
|
|
107
|
+
throw new Error(`Line ${ref.line} does not exist (file has ${fileLines.length} lines)`);
|
|
108
|
+
}
|
|
109
|
+
const actualHash = computeLineHash(ref.line, fileLines[ref.line - 1] ?? "");
|
|
110
|
+
if (actualHash !== ref.hash) {
|
|
111
|
+
throw new HashlineMismatchError([{ line: ref.line, expected: ref.hash, actual: actualHash }], fileLines);
|
|
112
|
+
}
|
|
113
|
+
}
|