@oh-my-pi/pi-coding-agent 12.5.0 → 12.5.1
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
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "12.5.
|
|
3
|
+
"version": "12.5.1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -84,12 +84,12 @@
|
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
86
|
"@mozilla/readability": "0.6.0",
|
|
87
|
-
"@oh-my-pi/omp-stats": "12.5.
|
|
88
|
-
"@oh-my-pi/pi-agent-core": "12.5.
|
|
89
|
-
"@oh-my-pi/pi-ai": "12.5.
|
|
90
|
-
"@oh-my-pi/pi-natives": "12.5.
|
|
91
|
-
"@oh-my-pi/pi-tui": "12.5.
|
|
92
|
-
"@oh-my-pi/pi-utils": "12.5.
|
|
87
|
+
"@oh-my-pi/omp-stats": "12.5.1",
|
|
88
|
+
"@oh-my-pi/pi-agent-core": "12.5.1",
|
|
89
|
+
"@oh-my-pi/pi-ai": "12.5.1",
|
|
90
|
+
"@oh-my-pi/pi-natives": "12.5.1",
|
|
91
|
+
"@oh-my-pi/pi-tui": "12.5.1",
|
|
92
|
+
"@oh-my-pi/pi-utils": "12.5.1",
|
|
93
93
|
"@sinclair/typebox": "^0.34.48",
|
|
94
94
|
"@xterm/headless": "^6.0.0",
|
|
95
95
|
"ajv": "^8.18.0",
|
|
@@ -253,6 +253,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
253
253
|
description: "Rewrite tool call arguments to normalized format in session history",
|
|
254
254
|
},
|
|
255
255
|
},
|
|
256
|
+
repeatToolDescriptions: {
|
|
257
|
+
type: "boolean",
|
|
258
|
+
default: false,
|
|
259
|
+
ui: {
|
|
260
|
+
tab: "agent",
|
|
261
|
+
label: "Repeat tool descriptions",
|
|
262
|
+
description: "Render full tool descriptions in the system prompt instead of a tool name list",
|
|
263
|
+
},
|
|
264
|
+
},
|
|
256
265
|
readLineNumbers: {
|
|
257
266
|
type: "boolean",
|
|
258
267
|
default: false,
|
|
@@ -56,11 +56,15 @@ The question is not "does this work?" but "under what conditions? What happens o
|
|
|
56
56
|
|
|
57
57
|
<tools>
|
|
58
58
|
## Available Tools
|
|
59
|
+
{{#if repeatToolDescriptions}}
|
|
59
60
|
{{#each toolDescriptions}}
|
|
60
61
|
<tool name="{{name}}">
|
|
61
62
|
{{description}}
|
|
62
63
|
</tool>
|
|
63
64
|
{{/each}}
|
|
65
|
+
{{else}}
|
|
66
|
+
{{#list tools join="\n"}}- {{this}}{{/list}}
|
|
67
|
+
{{/if}}
|
|
64
68
|
|
|
65
69
|
{{#ifAny (includes tools "python") (includes tools "bash")}}
|
|
66
70
|
### Precedence: Specialized → Python → Bash
|
|
@@ -1,94 +1,85 @@
|
|
|
1
|
-
# Edit (Hash
|
|
1
|
+
# Edit (Hash Anchored)
|
|
2
2
|
|
|
3
|
-
Line-addressed edits using hash-verified line references. Read
|
|
3
|
+
Line-addressed edits using hash-verified line references. Read files in hashline mode, collect exact `LINE:HASH` references, and submit edits that change only the targeted token or expression.
|
|
4
|
+
**CRITICAL: Copy `LINE:HASH` refs verbatim from read output. Use only the anchor prefix (e.g., `{{hashline 42 "const x = 1"}}`), never the trailing source text after `|`.**
|
|
4
5
|
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- `new_text` must differ from the current line content — sending identical content is rejected as a no-op
|
|
14
|
-
</critical>
|
|
6
|
+
<workflow>
|
|
7
|
+
1. Read the target file (`read`) to obtain `LINE:HASH` references
|
|
8
|
+
2. Collect the exact `LINE:HASH` refs for lines you will change
|
|
9
|
+
3. Direction-lock each mutation: identify the exact current token/expression → the intended replacement
|
|
10
|
+
4. Submit one `edit` call containing all operations for that file
|
|
11
|
+
5. If another edit is needed on the same file: re-read first, then edit (hashes change after every edit)
|
|
12
|
+
6. Respond with tool calls only — no prose
|
|
13
|
+
</workflow>
|
|
15
14
|
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
<operations>
|
|
16
|
+
Four edit variants are available:
|
|
17
|
+
- **`set_line`**: Replace a single line
|
|
18
|
+
`{ set_line: { anchor: "LINE:HASH", new_text: "..." } }`
|
|
19
|
+
`new_text: ""` keeps the line but makes it blank.
|
|
20
|
+
- **`replace_lines`**: Replace a contiguous range (use for deletions with `new_text: ""`)
|
|
21
|
+
`{ replace_lines: { start_anchor: "LINE:HASH", end_anchor: "LINE:HASH", new_text: "..." } }`
|
|
22
|
+
- **`insert_after`**: Add new content after an anchor line
|
|
23
|
+
`{ insert_after: { anchor: "LINE:HASH", text: "..." } }`
|
|
24
|
+
- **`replace`**: Substring-style fuzzy match (when line refs are unavailable)
|
|
25
|
+
`{ replace: { old_text: "...", new_text: "...", all?: boolean } }`
|
|
26
|
+
**Atomicity:** All edits in one call validate against the file as last read. Line numbers and hashes refer to the original state, not post-edit state. The applicator sorts and applies bottom-up automatically.
|
|
27
|
+
</operations>
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
<rules>
|
|
30
|
+
1. **Scope each operation minimally.** One logical change site per operation. Use separate `set_line` ops for non-adjacent lines instead of a wide `replace_lines` that spans unchanged code.
|
|
31
|
+
2. **Preserve original formatting exactly.** Copy each line's whitespace, braces, semicolons, trailing commas, and style — then change only the targeted token/expression. Keep `import { foo }` as-is; keep indentation and line breaks as-is.
|
|
32
|
+
3. **Use `insert_after` for additions.** When adding a field, argument, or import near existing lines, prefer `insert_after` over replacing a neighboring line.
|
|
33
|
+
4. **Ensure `new_text` differs from current content.** Identical content is rejected as a no-op.
|
|
34
|
+
5. **Edit only requested lines.** Leave unrelated code untouched.
|
|
35
|
+
6. **Lock mutation direction.** Replace the exact currently-present token with the intended target. For swaps between two locations, use two `set_line` ops in one call.
|
|
36
|
+
</rules>
|
|
32
37
|
|
|
33
|
-
<
|
|
34
|
-
**
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- Self-check before submitting: if your edit touches lines unrelated to the stated fix, split or narrow it.
|
|
43
|
-
- Do NOT reformat lines you are replacing — preserve exact whitespace, braces (`{ foo }` not `{foo}`), arrow style, and line breaks. Change ONLY the targeted token/expression. Reformatting causes hash verification failure even when the logic is correct.
|
|
44
|
-
- For swaps (exchanging content between two locations), use two `set_line` operations in one call — the applicator handles ordering. Do not try to account for line number shifts between operations.
|
|
45
|
-
</caution>
|
|
46
|
-
<instruction>
|
|
47
|
-
**Recovery:**
|
|
48
|
-
- Hash mismatch (`>>>` error): copy the updated `LINE:HASH` refs from the error verbatim and retry with the same intended mutation. Do NOT re-read unless you need lines not shown in the error.
|
|
49
|
-
- If hash mismatch repeats after applying updated refs, stop blind retries and re-read the relevant region before retrying.
|
|
50
|
-
- After a successful edit, always re-read the file before making another edit to the same file (hashes have changed).
|
|
51
|
-
- No-op error ("identical content"): your replacement text matches what the file already contains. STOP and re-read the file — you are likely targeting the wrong line or your replacement is not actually different. Do NOT retry with the same content. After 2 consecutive no-op errors on the same line, re-read the entire function/block to understand the current file state.
|
|
52
|
-
</instruction>
|
|
53
|
-
|
|
54
|
-
<instruction>
|
|
55
|
-
**Preflight schema and validation (required):**
|
|
56
|
-
- Payload shape is `{"path": string, "edits": [operation, ...]}` with a non-empty `edits` array.
|
|
57
|
-
- Each operation contains exactly one variant key: `set_line`, `replace_lines`, `insert_after`, or `replace`.
|
|
58
|
-
- Required fields by variant:
|
|
59
|
-
- `set_line`: `anchor`, `new_text`
|
|
60
|
-
- `replace_lines`: `start_anchor`, `end_anchor`, `new_text`
|
|
61
|
-
- `insert_after`: `anchor`, `text` (non-empty)
|
|
62
|
-
- `replace`: `old_text`, `new_text` (fuzzy match; `all: true` for replace-all)
|
|
63
|
-
- Each `anchor`/`start_anchor`/`end_anchor` ref matches `^\d+:[A-Za-z0-9]+$` (no spaces, no trailing source text).
|
|
64
|
-
- `new_text`/`text` preserves original formatting and changes only the direction-locked target locus.
|
|
65
|
-
</instruction>
|
|
66
|
-
|
|
67
|
-
<input>
|
|
68
|
-
- `path`: File path
|
|
69
|
-
- `edits`: Array of edit operations (one of the variants above)
|
|
70
|
-
</input>
|
|
38
|
+
<recovery>
|
|
39
|
+
**Hash mismatch (`>>>` error):**
|
|
40
|
+
→ Copy the updated `LINE:HASH` refs from the error output verbatim and retry with the same intended mutation.
|
|
41
|
+
→ Re-read only if you need lines not shown in the error.
|
|
42
|
+
→ If mismatch repeats after applying updated refs, stop and re-read the relevant region.
|
|
43
|
+
**No-op error ("identical content"):**
|
|
44
|
+
→ Stop. Re-read the file — you are targeting the wrong line or your replacement is not different.
|
|
45
|
+
→ After 2 consecutive no-op errors on the same line, re-read the entire function/block.
|
|
46
|
+
</recovery>
|
|
71
47
|
|
|
48
|
+
<examples>
|
|
72
49
|
<example name="replace single line">
|
|
73
|
-
|
|
50
|
+
set_line: { anchor: "{{hashline 2 " x"}}", new_text: " x = 99" }
|
|
74
51
|
</example>
|
|
75
52
|
|
|
76
53
|
<example name="replace range">
|
|
77
|
-
|
|
54
|
+
replace_lines: { start_anchor: "{{hashline 5 "old start line"}}", end_anchor: "{{hashline 8 "old end line"}}", new_text: " combined = True" }
|
|
78
55
|
</example>
|
|
79
56
|
|
|
80
57
|
<example name="delete lines">
|
|
81
|
-
|
|
58
|
+
replace_lines: { start_anchor: "{{hashline 5 "line to delete A"}}", end_anchor: "{{hashline 6 "line to delete B"}}", new_text: "" }
|
|
82
59
|
</example>
|
|
83
60
|
|
|
84
61
|
<example name="insert after">
|
|
85
|
-
|
|
62
|
+
insert_after: { anchor: "{{hashline 3 "anchor line content"}}", text: " # new comment" }
|
|
86
63
|
</example>
|
|
87
64
|
|
|
88
65
|
<example name="multiple edits (bottom-up safe)">
|
|
89
|
-
|
|
66
|
+
set_line: { anchor: "{{hashline 10 "old line 10"}}", new_text: " return False" }
|
|
67
|
+
set_line: { anchor: "{{hashline 3 "old line 3"}}", new_text: " x = 42" }
|
|
90
68
|
</example>
|
|
91
69
|
|
|
92
70
|
<example name="content replace (substr-style, no hashes)">
|
|
93
|
-
|
|
94
|
-
</example>
|
|
71
|
+
replace: { old_text: "x = 42", new_text: "x = 99" }
|
|
72
|
+
</example>
|
|
73
|
+
</examples>
|
|
74
|
+
|
|
75
|
+
<validation>
|
|
76
|
+
Before submitting, verify:
|
|
77
|
+
- [ ] Payload shape: `{"path": string, "edits": [operation, ...]}` with non-empty `edits` array
|
|
78
|
+
- [ ] Each operation has exactly one variant key: `set_line` | `replace_lines` | `insert_after` | `replace`
|
|
79
|
+
- [ ] Each anchor is copied exactly from the `LINE:HASH` prefix (no spaces, no trailing source text)
|
|
80
|
+
- [ ] `new_text`/`text` contains plain replacement lines only — no `LINE:HASH` prefixes, no diff `+` markers
|
|
81
|
+
- [ ] Each replacement differs from the current line content
|
|
82
|
+
- [ ] Each operation targets one logical change site with minimal scope
|
|
83
|
+
- [ ] Formatting of replaced lines matches the original exactly, except for the targeted change
|
|
84
|
+
</validation>
|
|
85
|
+
**REMINDER: Copy `LINE:HASH` refs verbatim. Anchors are `LINE:HASH` only — never `LINE:HASH|content`. Preserve exact formatting. Change only the targeted token.**
|
package/src/sdk.ts
CHANGED
|
@@ -323,6 +323,7 @@ export interface BuildSystemPromptOptions {
|
|
|
323
323
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
324
324
|
cwd?: string;
|
|
325
325
|
appendPrompt?: string;
|
|
326
|
+
repeatToolDescriptions?: boolean;
|
|
326
327
|
}
|
|
327
328
|
|
|
328
329
|
/**
|
|
@@ -334,6 +335,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
334
335
|
skills: options.skills,
|
|
335
336
|
contextFiles: options.contextFiles,
|
|
336
337
|
appendSystemPrompt: options.appendPrompt,
|
|
338
|
+
repeatToolDescriptions: options.repeatToolDescriptions,
|
|
337
339
|
});
|
|
338
340
|
}
|
|
339
341
|
|
|
@@ -986,6 +988,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
986
988
|
emitEvent: event => cursorEventEmitter?.(event),
|
|
987
989
|
});
|
|
988
990
|
|
|
991
|
+
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
989
992
|
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
990
993
|
toolContextStore.setToolNames(toolNames);
|
|
991
994
|
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
@@ -999,6 +1002,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
999
1002
|
rules: rulebookRules,
|
|
1000
1003
|
skillsSettings: settings.getGroup("skills") as SkillsSettings,
|
|
1001
1004
|
appendSystemPrompt: memoryInstructions,
|
|
1005
|
+
repeatToolDescriptions,
|
|
1002
1006
|
});
|
|
1003
1007
|
|
|
1004
1008
|
if (options.systemPrompt === undefined) {
|
|
@@ -1016,6 +1020,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1016
1020
|
skillsSettings: settings.getGroup("skills") as SkillsSettings,
|
|
1017
1021
|
customPrompt: options.systemPrompt,
|
|
1018
1022
|
appendSystemPrompt: memoryInstructions,
|
|
1023
|
+
repeatToolDescriptions,
|
|
1019
1024
|
});
|
|
1020
1025
|
}
|
|
1021
1026
|
return options.systemPrompt(defaultPrompt);
|
package/src/system-prompt.ts
CHANGED
|
@@ -430,6 +430,8 @@ export interface BuildSystemPromptOptions {
|
|
|
430
430
|
toolNames?: string[];
|
|
431
431
|
/** Text to append to system prompt. */
|
|
432
432
|
appendSystemPrompt?: string;
|
|
433
|
+
/** Repeat full tool descriptions in system prompt. Default: false */
|
|
434
|
+
repeatToolDescriptions?: boolean;
|
|
433
435
|
/** Skills settings for discovery. */
|
|
434
436
|
skillsSettings?: SkillsSettings;
|
|
435
437
|
/** Working directory. Default: getProjectDir() */
|
|
@@ -454,6 +456,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
454
456
|
customPrompt,
|
|
455
457
|
tools,
|
|
456
458
|
appendSystemPrompt,
|
|
459
|
+
repeatToolDescriptions = false,
|
|
457
460
|
skillsSettings,
|
|
458
461
|
toolNames,
|
|
459
462
|
cwd,
|
|
@@ -553,6 +556,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
553
556
|
return renderPromptTemplate(systemPromptTemplate, {
|
|
554
557
|
tools: toolNamesArray,
|
|
555
558
|
toolDescriptions,
|
|
559
|
+
repeatToolDescriptions,
|
|
556
560
|
environment,
|
|
557
561
|
systemPromptCustomization: systemPromptCustomization ?? "",
|
|
558
562
|
contextFiles,
|