@oh-my-pi/pi-coding-agent 15.1.2 → 15.1.3
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 +42 -0
- package/dist/types/cli/auth-broker-cli.d.ts +25 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
- package/dist/types/cli/grievances-cli.d.ts +12 -0
- package/dist/types/commands/auth-broker.d.ts +54 -0
- package/dist/types/commands/auth-gateway.d.ts +32 -0
- package/dist/types/commands/grievances.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -1
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +46 -0
- package/dist/types/discovery/agents.d.ts +12 -1
- package/dist/types/edit/renderer.d.ts +3 -0
- package/dist/types/eval/index.d.ts +0 -2
- package/dist/types/goals/tools/goal-tool.d.ts +10 -2
- package/dist/types/index.d.ts +0 -1
- package/dist/types/internal-urls/index.d.ts +1 -1
- package/dist/types/internal-urls/{pi-protocol.d.ts → omp-protocol.d.ts} +3 -3
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/modes/acp/acp-agent.d.ts +1 -0
- package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/plan-mode/approved-plan.d.ts +4 -0
- package/dist/types/sdk.d.ts +10 -3
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/auth-broker-config.d.ts +13 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +41 -7
- package/dist/types/tools/irc.d.ts +8 -2
- package/dist/types/tools/report-tool-issue.d.ts +118 -1
- package/dist/types/tools/resolve.d.ts +8 -2
- package/examples/custom-tools/README.md +3 -12
- package/examples/extensions/README.md +2 -15
- package/examples/extensions/api-demo.ts +1 -7
- package/package.json +7 -7
- package/src/autoresearch/tools/init-experiment.ts +11 -33
- package/src/autoresearch/tools/log-experiment.ts +10 -24
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +2 -9
- package/src/cli/auth-broker-cli.ts +746 -0
- package/src/cli/auth-gateway-cli.ts +342 -0
- package/src/cli/grievances-cli.ts +109 -16
- package/src/cli.ts +4 -2
- package/src/commands/auth-broker.ts +96 -0
- package/src/commands/auth-gateway.ts +61 -0
- package/src/commands/grievances.ts +13 -8
- package/src/commands/launch.ts +1 -1
- package/src/commit/agentic/agent.ts +2 -0
- package/src/commit/agentic/tools/analyze-file.ts +2 -2
- package/src/commit/agentic/tools/git-file-diff.ts +2 -2
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +2 -2
- package/src/commit/agentic/tools/propose-changelog.ts +1 -3
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -9
- package/src/config/model-equivalence.ts +279 -174
- package/src/config/model-registry.ts +37 -6
- package/src/config/model-resolver.ts +13 -8
- package/src/config/models-config-schema.ts +8 -0
- package/src/config/settings-schema.ts +52 -0
- package/src/cursor.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/profiler.ts +4 -0
- package/src/debug/raw-sse-buffer.ts +100 -59
- package/src/debug/raw-sse.ts +1 -1
- package/src/discovery/agents.ts +15 -4
- package/src/edit/modes/apply-patch.ts +1 -5
- package/src/edit/modes/patch.ts +5 -5
- package/src/edit/modes/replace.ts +5 -5
- package/src/edit/renderer.ts +2 -1
- package/src/edit/streaming.ts +1 -1
- package/src/eval/index.ts +0 -2
- package/src/eval/js/shared/runtime.ts +25 -0
- package/src/eval/py/kernel.ts +1 -1
- package/src/exa/researcher.ts +4 -4
- package/src/exa/search.ts +10 -22
- package/src/exa/websets.ts +33 -33
- package/src/goals/tools/goal-tool.ts +3 -3
- package/src/index.ts +0 -3
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/internal-urls/index.ts +1 -1
- package/src/internal-urls/{pi-protocol.ts → omp-protocol.ts} +10 -10
- package/src/internal-urls/router.ts +3 -3
- package/src/internal-urls/types.ts +1 -1
- package/src/lsp/types.ts +8 -11
- package/src/main.ts +3 -0
- package/src/mcp/tool-bridge.ts +3 -3
- package/src/modes/acp/acp-agent.ts +88 -25
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/diff.ts +1 -2
- package/src/modes/components/eval-execution.ts +1 -1
- package/src/modes/components/oauth-selector.ts +38 -2
- package/src/modes/components/tool-execution.ts +1 -2
- package/src/modes/controllers/command-controller.ts +95 -34
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/interactive-mode.ts +92 -19
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/prompt-action-autocomplete.ts +14 -0
- package/src/plan-mode/approved-plan.ts +9 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/tools/eval.md +25 -26
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/resolve.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/web-search.md +1 -1
- package/src/sdk.ts +78 -7
- package/src/session/agent-session.ts +176 -77
- package/src/session/agent-storage.ts +7 -2
- package/src/session/auth-broker-config.ts +102 -0
- package/src/session/auth-storage.ts +7 -1
- package/src/session/streaming-output.ts +1 -1
- package/src/task/types.ts +10 -35
- package/src/tools/bash-interactive.ts +4 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser.ts +12 -20
- package/src/tools/eval.ts +77 -100
- package/src/tools/gh.ts +21 -45
- package/src/tools/hindsight-recall.ts +1 -1
- package/src/tools/hindsight-reflect.ts +2 -2
- package/src/tools/hindsight-retain.ts +3 -7
- package/src/tools/index.ts +8 -1
- package/src/tools/inspect-image.ts +4 -1
- package/src/tools/irc.ts +4 -12
- package/src/tools/job.ts +3 -11
- package/src/tools/report-tool-issue.ts +462 -17
- package/src/tools/resolve.ts +2 -7
- package/src/tools/todo-write.ts +8 -15
- package/src/utils/title-generator.ts +3 -0
- package/src/web/search/index.ts +6 -6
- package/dist/types/eval/parse.d.ts +0 -28
- package/dist/types/eval/sniff.d.ts +0 -11
- package/src/eval/eval.lark +0 -36
- package/src/eval/parse.ts +0 -407
- package/src/eval/sniff.ts +0 -28
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import buckets from "./data/emojis.json" with { type: "json" };
|
|
3
|
+
|
|
4
|
+
// Bucket layout: `{ "<first-char>": [["<name>", "<emoji>"], ...] }`, with each
|
|
5
|
+
// bucket pre-sorted by name. Built offline by scripts/build-emojis.py
|
|
6
|
+
// so the runtime never has to allocate sorted arrays or filter flag sequences.
|
|
7
|
+
type Entry = readonly [name: string, char: string];
|
|
8
|
+
const BUCKETS = buckets as unknown as Readonly<Record<string, readonly Entry[]>>;
|
|
9
|
+
|
|
10
|
+
// Western text emoticons (`:D`, `;)`, `<3`, …) sit outside the `:name:`
|
|
11
|
+
// shortcode grammar, so they live in a hand-maintained table here rather than
|
|
12
|
+
// in `emojis.json`. Sorted longest-first so `:-)` wins over `:)` when both
|
|
13
|
+
// would match.
|
|
14
|
+
const EMOTICONS: ReadonlyArray<readonly [pattern: string, char: string]> = [
|
|
15
|
+
[":'-(", "😢"],
|
|
16
|
+
[">:-(", "😠"],
|
|
17
|
+
[":-)", "🙂"],
|
|
18
|
+
[":-(", "🙁"],
|
|
19
|
+
[":-D", "😃"],
|
|
20
|
+
[":-P", "😛"],
|
|
21
|
+
[":-p", "😛"],
|
|
22
|
+
[":-O", "😮"],
|
|
23
|
+
[":-o", "😮"],
|
|
24
|
+
[":-|", "😐"],
|
|
25
|
+
[":-/", "😕"],
|
|
26
|
+
[":-\\", "😕"],
|
|
27
|
+
[":-*", "😘"],
|
|
28
|
+
[";-)", "😉"],
|
|
29
|
+
[";-P", "😜"],
|
|
30
|
+
[":')", "🥲"],
|
|
31
|
+
[":'D", "😂"],
|
|
32
|
+
[":'(", "😢"],
|
|
33
|
+
["</3", "💔"],
|
|
34
|
+
[">:(", "😠"],
|
|
35
|
+
["B-)", "😎"],
|
|
36
|
+
["8-)", "😎"],
|
|
37
|
+
["o.O", "😳"],
|
|
38
|
+
["O.o", "😳"],
|
|
39
|
+
[":)", "🙂"],
|
|
40
|
+
[":(", "🙁"],
|
|
41
|
+
[":D", "😃"],
|
|
42
|
+
[":P", "😛"],
|
|
43
|
+
[":p", "😛"],
|
|
44
|
+
[":O", "😮"],
|
|
45
|
+
[":o", "😮"],
|
|
46
|
+
[":|", "😐"],
|
|
47
|
+
[":/", "😕"],
|
|
48
|
+
[":\\", "😕"],
|
|
49
|
+
[":*", "😘"],
|
|
50
|
+
[";)", "😉"],
|
|
51
|
+
[":3", "😺"],
|
|
52
|
+
["<3", "❤️"],
|
|
53
|
+
["xD", "😆"],
|
|
54
|
+
["XD", "😆"],
|
|
55
|
+
["B)", "😎"],
|
|
56
|
+
["8)", "😎"],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const MAX_SUGGESTIONS = 12;
|
|
60
|
+
|
|
61
|
+
function lowerBound(arr: readonly Entry[], target: string): number {
|
|
62
|
+
let lo = 0;
|
|
63
|
+
let hi = arr.length;
|
|
64
|
+
while (lo < hi) {
|
|
65
|
+
const mid = (lo + hi) >>> 1;
|
|
66
|
+
if (arr[mid]![0] < target) lo = mid + 1;
|
|
67
|
+
else hi = mid;
|
|
68
|
+
}
|
|
69
|
+
return lo;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function lookupExact(name: string): string | undefined {
|
|
73
|
+
const bucket = BUCKETS[name[0] ?? ""];
|
|
74
|
+
if (!bucket) return undefined;
|
|
75
|
+
const i = lowerBound(bucket, name);
|
|
76
|
+
const hit = bucket[i];
|
|
77
|
+
return hit && hit[0] === name ? hit[1] : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Shortcode-name characters mirror the GitHub/gemoji grammar: `a-z`, `A-Z`,
|
|
81
|
+
// `0-9`, `_`, `+`, `-`.
|
|
82
|
+
function isNameCharCode(c: number): boolean {
|
|
83
|
+
return (
|
|
84
|
+
(c >= 0x61 && c <= 0x7a) ||
|
|
85
|
+
(c >= 0x41 && c <= 0x5a) ||
|
|
86
|
+
(c >= 0x30 && c <= 0x39) ||
|
|
87
|
+
c === 0x5f ||
|
|
88
|
+
c === 0x2b ||
|
|
89
|
+
c === 0x2d
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Token boundary to the left of an opening `:`: start-of-string or one of
|
|
94
|
+
// the punctuation characters we treat as a "fresh token" marker (whitespace,
|
|
95
|
+
// opening brackets, `>` for quoted blocks).
|
|
96
|
+
function hasLeftBoundary(text: string, colonIdx: number): boolean {
|
|
97
|
+
if (colonIdx === 0) return true;
|
|
98
|
+
const c = text.charCodeAt(colonIdx - 1);
|
|
99
|
+
return (
|
|
100
|
+
c === 0x20 || // space
|
|
101
|
+
c === 0x09 || // tab
|
|
102
|
+
c === 0x0a || // \n
|
|
103
|
+
c === 0x0d || // \r
|
|
104
|
+
c === 0x28 || // (
|
|
105
|
+
c === 0x5b || // [
|
|
106
|
+
c === 0x7b || // {
|
|
107
|
+
c === 0x3e // >
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface EmojiTrigger {
|
|
112
|
+
/** Full token including the leading `:` (e.g. `:joy`). */
|
|
113
|
+
prefix: string;
|
|
114
|
+
/** Lowercased name portion (e.g. `joy`). May be empty when only `:` has been typed. */
|
|
115
|
+
query: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Walk back over name characters then verify an opening `:` with a left
|
|
119
|
+
// boundary. Cheaper than a regex on every keystroke and avoids allocating
|
|
120
|
+
// match arrays.
|
|
121
|
+
function extractTrigger(text: string): EmojiTrigger | null {
|
|
122
|
+
let i = text.length;
|
|
123
|
+
while (i > 0 && isNameCharCode(text.charCodeAt(i - 1))) i--;
|
|
124
|
+
if (i === 0 || text.charCodeAt(i - 1) !== 0x3a) return null;
|
|
125
|
+
const colonIdx = i - 1;
|
|
126
|
+
if (!hasLeftBoundary(text, colonIdx)) return null;
|
|
127
|
+
const name = text.slice(i);
|
|
128
|
+
return { prefix: `:${name}`, query: name.toLowerCase() };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getEmojiSuggestions(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null {
|
|
132
|
+
const trigger = extractTrigger(textBeforeCursor);
|
|
133
|
+
if (!trigger) return null;
|
|
134
|
+
// Wait until the user has typed at least one letter so a bare `:` in prose
|
|
135
|
+
// (e.g. "note:") does not spam the popup.
|
|
136
|
+
if (trigger.query.length === 0) return null;
|
|
137
|
+
|
|
138
|
+
const items: AutocompleteItem[] = [];
|
|
139
|
+
|
|
140
|
+
// Surface emoticon literals (`:D`, `:-)`, …) whose pattern starts with
|
|
141
|
+
// `:<query>` (case-insensitive). These come first so the user sees the
|
|
142
|
+
// emoticon they're literally typing at the top of the popup.
|
|
143
|
+
const wanted = `:${trigger.query}`;
|
|
144
|
+
for (const [pattern, char] of EMOTICONS) {
|
|
145
|
+
if (items.length >= MAX_SUGGESTIONS) break;
|
|
146
|
+
if (pattern.length < wanted.length) continue;
|
|
147
|
+
if (pattern.toLowerCase().slice(0, wanted.length) !== wanted) continue;
|
|
148
|
+
items.push({ value: char, label: `${char} ${pattern}` });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const bucket = BUCKETS[trigger.query[0]!];
|
|
152
|
+
if (bucket) {
|
|
153
|
+
for (let i = lowerBound(bucket, trigger.query); i < bucket.length && items.length < MAX_SUGGESTIONS; i++) {
|
|
154
|
+
const [name, char] = bucket[i]!;
|
|
155
|
+
if (!name.startsWith(trigger.query)) break;
|
|
156
|
+
items.push({
|
|
157
|
+
value: char,
|
|
158
|
+
label: `${char} :${name}:`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (items.length === 0) return null;
|
|
164
|
+
return { items, prefix: trigger.prefix };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function applyEmojiCompletion(
|
|
168
|
+
lines: string[],
|
|
169
|
+
cursorLine: number,
|
|
170
|
+
cursorCol: number,
|
|
171
|
+
item: AutocompleteItem,
|
|
172
|
+
prefix: string,
|
|
173
|
+
): { lines: string[]; cursorLine: number; cursorCol: number } {
|
|
174
|
+
const currentLine = lines[cursorLine] ?? "";
|
|
175
|
+
const before = currentLine.slice(0, cursorCol - prefix.length);
|
|
176
|
+
const after = currentLine.slice(cursorCol);
|
|
177
|
+
const newLines = [...lines];
|
|
178
|
+
newLines[cursorLine] = before + item.value + after;
|
|
179
|
+
return {
|
|
180
|
+
lines: newLines,
|
|
181
|
+
cursorLine,
|
|
182
|
+
cursorCol: before.length + item.value.length,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function tryShortcodeInlineReplace(textBeforeCursor: string): { replaceLen: number; insert: string } | null {
|
|
187
|
+
const len = textBeforeCursor.length;
|
|
188
|
+
// Cheap early-out: shortcode replace only fires on a trailing `:`.
|
|
189
|
+
if (len === 0 || textBeforeCursor.charCodeAt(len - 1) !== 0x3a) return null;
|
|
190
|
+
|
|
191
|
+
// Walk back over the candidate name, then require an opening `:` with a
|
|
192
|
+
// left boundary.
|
|
193
|
+
const closeIdx = len - 1;
|
|
194
|
+
let nameStart = closeIdx;
|
|
195
|
+
while (nameStart > 0 && isNameCharCode(textBeforeCursor.charCodeAt(nameStart - 1))) nameStart--;
|
|
196
|
+
if (nameStart === closeIdx) return null; // empty name (`::`)
|
|
197
|
+
if (nameStart === 0 || textBeforeCursor.charCodeAt(nameStart - 1) !== 0x3a) return null;
|
|
198
|
+
const openIdx = nameStart - 1;
|
|
199
|
+
if (!hasLeftBoundary(textBeforeCursor, openIdx)) return null;
|
|
200
|
+
|
|
201
|
+
const name = textBeforeCursor.slice(nameStart, closeIdx).toLowerCase();
|
|
202
|
+
const char = lookupExact(name);
|
|
203
|
+
if (!char) return null;
|
|
204
|
+
// Replace `:name:` (name + 2 colons) with the emoji character.
|
|
205
|
+
return { replaceLen: name.length + 2, insert: char };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// A trailing delimiter (space/tab/newline) confirms the user is done with the
|
|
209
|
+
// token — that way typing `:PATH` doesn't turn into `😛ATH` halfway through.
|
|
210
|
+
function isEmoticonTerminator(c: number): boolean {
|
|
211
|
+
return c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Western text emoticons fire only once a terminator follows the pattern
|
|
215
|
+
// (e.g. typing space after `;)` rewrites `;) ` to `😉 `). The terminator is
|
|
216
|
+
// preserved in the replacement so the user keeps typing without losing it.
|
|
217
|
+
// EMOTICONS is sorted longest-first so `:-) ` wins over `:) `.
|
|
218
|
+
function tryEmoticonInlineReplace(textBeforeCursor: string): { replaceLen: number; insert: string } | null {
|
|
219
|
+
const len = textBeforeCursor.length;
|
|
220
|
+
if (len < 2) return null;
|
|
221
|
+
const terminator = textBeforeCursor.charCodeAt(len - 1);
|
|
222
|
+
if (!isEmoticonTerminator(terminator)) return null;
|
|
223
|
+
const term = textBeforeCursor[len - 1]!;
|
|
224
|
+
const tail = len - 1;
|
|
225
|
+
for (const [pattern, char] of EMOTICONS) {
|
|
226
|
+
const plen = pattern.length;
|
|
227
|
+
if (tail < plen) continue;
|
|
228
|
+
const start = tail - plen;
|
|
229
|
+
let match = true;
|
|
230
|
+
for (let j = 0; j < plen; j++) {
|
|
231
|
+
if (textBeforeCursor.charCodeAt(start + j) !== pattern.charCodeAt(j)) {
|
|
232
|
+
match = false;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!match) continue;
|
|
237
|
+
// Same left-boundary rule as shortcodes: emoticons embedded in
|
|
238
|
+
// identifiers / URLs / code stay untouched.
|
|
239
|
+
if (start > 0 && !hasLeftBoundary(textBeforeCursor, start)) continue;
|
|
240
|
+
return { replaceLen: plen + 1, insert: char + term };
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function tryEmojiInlineReplace(textBeforeCursor: string): { replaceLen: number; insert: string } | null {
|
|
246
|
+
return tryShortcodeInlineReplace(textBeforeCursor) ?? tryEmoticonInlineReplace(textBeforeCursor);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function isEmojiPrefix(prefix: string): boolean {
|
|
250
|
+
return prefix.startsWith(":");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Submit-time expansion: scan a whole message for emoticons sitting at token
|
|
254
|
+
// boundaries (preceded by a left boundary, followed by whitespace or EOS) and
|
|
255
|
+
// rewrite them. Catches the case where the user pressed Enter without typing a
|
|
256
|
+
// trailing space after the emoticon. EMOTICONS sorted longest-first means the
|
|
257
|
+
// first `startsWith` hit is always the maximal match.
|
|
258
|
+
export function expandEmoticons(text: string): string {
|
|
259
|
+
if (text.length < 2) return text;
|
|
260
|
+
let out = "";
|
|
261
|
+
let cursor = 0;
|
|
262
|
+
let i = 0;
|
|
263
|
+
while (i < text.length) {
|
|
264
|
+
if (i === 0 || hasLeftBoundary(text, i)) {
|
|
265
|
+
let matched = false;
|
|
266
|
+
for (const [pattern, char] of EMOTICONS) {
|
|
267
|
+
if (!text.startsWith(pattern, i)) continue;
|
|
268
|
+
const end = i + pattern.length;
|
|
269
|
+
if (end !== text.length) {
|
|
270
|
+
const next = text.charCodeAt(end);
|
|
271
|
+
if (!isEmoticonTerminator(next)) continue;
|
|
272
|
+
}
|
|
273
|
+
out += text.slice(cursor, i) + char;
|
|
274
|
+
cursor = end;
|
|
275
|
+
i = end;
|
|
276
|
+
matched = true;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
if (matched) continue;
|
|
280
|
+
}
|
|
281
|
+
i++;
|
|
282
|
+
}
|
|
283
|
+
if (cursor === 0) return text;
|
|
284
|
+
return out + text.slice(cursor);
|
|
285
|
+
}
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@oh-my-pi/pi-utils";
|
|
30
30
|
import chalk from "chalk";
|
|
31
31
|
import { KeybindingsManager } from "../config/keybindings";
|
|
32
|
-
import { isSettingsInitialized,
|
|
32
|
+
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
33
33
|
import type {
|
|
34
34
|
ExtensionUIContext,
|
|
35
35
|
ExtensionUIDialogOptions,
|
|
@@ -41,7 +41,12 @@ import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slas
|
|
|
41
41
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
42
42
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
43
43
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
humanizePlanTitle,
|
|
46
|
+
normalizePlanTitle,
|
|
47
|
+
type PlanApprovalDetails,
|
|
48
|
+
renameApprovedPlanFile,
|
|
49
|
+
} from "../plan-mode/approved-plan";
|
|
45
50
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
46
51
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
47
52
|
type: "text",
|
|
@@ -54,6 +59,7 @@ import { formatDuration } from "../slash-commands/helpers/format";
|
|
|
54
59
|
import { STTController, type SttState } from "../stt";
|
|
55
60
|
import type { LspStartupServerInfo } from "../tools";
|
|
56
61
|
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
62
|
+
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
57
63
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
58
64
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
59
65
|
import { ToolError } from "../tools/tool-errors";
|
|
@@ -383,6 +389,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
383
389
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
384
390
|
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
385
391
|
|
|
392
|
+
// Wire the report_tool_issue consent gate to the Yes/No dialog popup.
|
|
393
|
+
// The handler is process-global — subagent tools (which can't reach
|
|
394
|
+
// `showHookSelector` on their own) resolve through this exact closure.
|
|
395
|
+
// `Settings.instance` is the disk-backed singleton; passing it explicitly
|
|
396
|
+
// guarantees the decision persists even when the prompt is triggered
|
|
397
|
+
// from a subagent whose own `Settings` is an in-memory snapshot.
|
|
398
|
+
setAutoQaConsentHandler(() => this.#promptAutoQaConsent(), Settings.instance);
|
|
399
|
+
|
|
386
400
|
await logger.time(
|
|
387
401
|
"InteractiveMode.init:slashCommands",
|
|
388
402
|
this.refreshSlashCommandState.bind(this),
|
|
@@ -1440,6 +1454,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1440
1454
|
options: {
|
|
1441
1455
|
planFilePath: string;
|
|
1442
1456
|
finalPlanFilePath: string;
|
|
1457
|
+
title: string;
|
|
1443
1458
|
preserveContext?: boolean;
|
|
1444
1459
|
compactBeforeExecute?: boolean;
|
|
1445
1460
|
},
|
|
@@ -1523,6 +1538,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1523
1538
|
return;
|
|
1524
1539
|
}
|
|
1525
1540
|
|
|
1541
|
+
// Approved plans land in a fresh (or compacted) session whose first user-visible
|
|
1542
|
+
// turn is the synthetic plan-approved prompt — that path bypasses the
|
|
1543
|
+
// input-controller's title generation. Seed an auto-name from the plan title
|
|
1544
|
+
// so the session is not left unnamed. `setSessionName("auto")` is a no-op
|
|
1545
|
+
// when the user has already chosen a name (preserveContext paths).
|
|
1546
|
+
const seededName = humanizePlanTitle(options.title);
|
|
1547
|
+
if (seededName && !this.sessionManager.getSessionName()) {
|
|
1548
|
+
const applied = await this.sessionManager.setSessionName(seededName, "auto");
|
|
1549
|
+
if (applied) {
|
|
1550
|
+
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
1551
|
+
this.updateEditorBorderColor();
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1526
1555
|
// markPlanReferenceSent fires only on the dispatch path so the synthetic
|
|
1527
1556
|
// plan-approved prompt is the source of the reference injection.
|
|
1528
1557
|
this.session.markPlanReferenceSent();
|
|
@@ -1806,13 +1835,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1806
1835
|
this.#renderPlanPreview(planContent, { append: true });
|
|
1807
1836
|
const choice = await this.showHookSelector(
|
|
1808
1837
|
"Plan mode - next step",
|
|
1809
|
-
[
|
|
1810
|
-
"Approve and execute",
|
|
1811
|
-
"Approve and compact context",
|
|
1812
|
-
"Approve and keep context",
|
|
1813
|
-
"Refine plan",
|
|
1814
|
-
"Stay in plan mode",
|
|
1815
|
-
],
|
|
1838
|
+
["Approve and execute", "Approve and compact context", "Approve and keep context", "Refine plan"],
|
|
1816
1839
|
{
|
|
1817
1840
|
helpText: this.#getPlanReviewHelpText(),
|
|
1818
1841
|
onExternalEditor: () => void this.#openPlanInExternalEditor(planFilePath),
|
|
@@ -1834,6 +1857,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1834
1857
|
await this.#approvePlan(latestPlanContent, {
|
|
1835
1858
|
planFilePath,
|
|
1836
1859
|
finalPlanFilePath,
|
|
1860
|
+
title: details.title,
|
|
1837
1861
|
preserveContext: choice !== "Approve and execute",
|
|
1838
1862
|
compactBeforeExecute: choice === "Approve and compact context",
|
|
1839
1863
|
});
|
|
@@ -1844,16 +1868,62 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1844
1868
|
}
|
|
1845
1869
|
return;
|
|
1846
1870
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* Pool of consent-prompt variants. Each entry is `[headline, reassurance]`;
|
|
1875
|
+
* the second line always promises the same scope (tool name + confusion
|
|
1876
|
+
* details, never personal data) so users learn what they're consenting to
|
|
1877
|
+
* even as the top line rotates.
|
|
1878
|
+
*
|
|
1879
|
+
* Kept in-module rather than i18n'd because the whole charm is the tone
|
|
1880
|
+
* — translations would need to preserve it deliberately, not auto-render.
|
|
1881
|
+
*/
|
|
1882
|
+
static #AUTOQA_CONSENT_PROMPTS: ReadonlyArray<readonly [string, string]> = [
|
|
1883
|
+
[
|
|
1884
|
+
"😤 Your agent is fuming about a tool.",
|
|
1885
|
+
"Wanna let it vent to the devs? Just the tool name + what set it off, nothing personal.",
|
|
1886
|
+
],
|
|
1887
|
+
[
|
|
1888
|
+
"😵💫 Your agent is having an existential crisis over a tool.",
|
|
1889
|
+
"Forward the dread to the devs? Tool + what broke its little mind, no personal info.",
|
|
1890
|
+
],
|
|
1891
|
+
[
|
|
1892
|
+
"😭 Your agent wants to cry about a misbehaving tool.",
|
|
1893
|
+
"Let it cry to the devs? Tool + the tears, never anything personal.",
|
|
1894
|
+
],
|
|
1895
|
+
[
|
|
1896
|
+
"🤬 Your agent is BIG MAD at one of the tools.",
|
|
1897
|
+
"Pass the rant along? Just the tool name and what enraged it, nothing personal.",
|
|
1898
|
+
],
|
|
1899
|
+
[
|
|
1900
|
+
"🫠 Your agent is melting down over a tool.",
|
|
1901
|
+
"Mop up by alerting the devs? Tool + what melted it, no personal info.",
|
|
1902
|
+
],
|
|
1903
|
+
[
|
|
1904
|
+
"🤯 Your agent's brain broke at a tool's nonsense.",
|
|
1905
|
+
"Ship the pieces to the devs? Tool name + the confusion, never anything personal.",
|
|
1906
|
+
],
|
|
1907
|
+
[
|
|
1908
|
+
"😩 Your agent is begging to file a complaint about a tool.",
|
|
1909
|
+
"Hand it the form? Tool + what wronged it, nothing personal.",
|
|
1910
|
+
],
|
|
1911
|
+
[
|
|
1912
|
+
"🥲 Your agent put on a brave face but a tool did it dirty.",
|
|
1913
|
+
"Let it tell the devs the truth? Tool name + the dirt, no personal info.",
|
|
1914
|
+
],
|
|
1915
|
+
];
|
|
1916
|
+
|
|
1917
|
+
/**
|
|
1918
|
+
* Show the report_tool_issue consent popup and return the user's decision.
|
|
1919
|
+
* Invoked by the process-global consent handler the tool dispatches to;
|
|
1920
|
+
* subagent invocations bubble up here through the shared module state.
|
|
1921
|
+
*/
|
|
1922
|
+
async #promptAutoQaConsent(): Promise<boolean | null> {
|
|
1923
|
+
const pool = InteractiveMode.#AUTOQA_CONSENT_PROMPTS;
|
|
1924
|
+
const [headline, body] = pool[Math.floor(Math.random() * pool.length)];
|
|
1925
|
+
const choice = await this.showHookSelector(`${headline}\n${body}`, ["Yes", "No"]);
|
|
1926
|
+
return choice === "Yes";
|
|
1857
1927
|
}
|
|
1858
1928
|
|
|
1859
1929
|
stop(): void {
|
|
@@ -1886,6 +1956,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1886
1956
|
if (this.#cleanupUnsubscribe) {
|
|
1887
1957
|
this.#cleanupUnsubscribe();
|
|
1888
1958
|
}
|
|
1959
|
+
// Clear the process-global consent handler so it doesn't outlive this
|
|
1960
|
+
// InteractiveMode instance (e.g. test harnesses, headless re-init).
|
|
1961
|
+
setAutoQaConsentHandler(null, null);
|
|
1889
1962
|
if (this.isInitialized) {
|
|
1890
1963
|
this.ui.stop();
|
|
1891
1964
|
this.isInitialized = false;
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - `omp --mode json "prompt"` - JSON event stream
|
|
7
7
|
*/
|
|
8
8
|
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
9
|
-
import { sanitizeText } from "@oh-my-pi/pi-
|
|
9
|
+
import { logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import type { AgentSession } from "../session/agent-session";
|
|
11
11
|
import { isSilentAbort } from "../session/messages";
|
|
12
12
|
import { initializeExtensions } from "./runtime-init";
|
|
@@ -61,12 +61,12 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
61
61
|
|
|
62
62
|
// Send initial message with attachments
|
|
63
63
|
if (initialMessage !== undefined) {
|
|
64
|
-
await session.prompt(initialMessage, { images: initialImages });
|
|
64
|
+
await logger.time("print:prompt:initial", () => session.prompt(initialMessage, { images: initialImages }));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Send remaining messages
|
|
68
68
|
for (const message of messages) {
|
|
69
|
-
await session.prompt(message);
|
|
69
|
+
await logger.time("print:prompt:next", () => session.prompt(message));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// In text mode, output final response
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
type SlashCommand,
|
|
7
7
|
} from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { formatKeyHints, type KeybindingsManager } from "../config/keybindings";
|
|
9
|
+
import { isSettingsInitialized, settings } from "../config/settings";
|
|
10
|
+
import { applyEmojiCompletion, getEmojiSuggestions, isEmojiPrefix, tryEmojiInlineReplace } from "./emoji-autocomplete";
|
|
9
11
|
|
|
10
12
|
interface PromptActionDefinition {
|
|
11
13
|
id: string;
|
|
@@ -126,6 +128,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
if (!isSettingsInitialized() || settings.get("emojiAutocomplete")) {
|
|
132
|
+
const emojiSuggestions = getEmojiSuggestions(textBeforeCursor);
|
|
133
|
+
if (emojiSuggestions) return emojiSuggestions;
|
|
134
|
+
}
|
|
135
|
+
|
|
129
136
|
return this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol);
|
|
130
137
|
}
|
|
131
138
|
|
|
@@ -163,6 +170,9 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
163
170
|
};
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
if (isEmojiPrefix(prefix)) {
|
|
174
|
+
return applyEmojiCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
175
|
+
}
|
|
166
176
|
return this.#baseProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
167
177
|
}
|
|
168
178
|
|
|
@@ -172,6 +182,10 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
172
182
|
trySyncSlashCompletion(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null {
|
|
173
183
|
return this.#baseProvider.trySyncSlashCompletion?.(textBeforeCursor) ?? null;
|
|
174
184
|
}
|
|
185
|
+
trySyncInlineReplace(textBeforeCursor: string): { replaceLen: number; insert: string } | null {
|
|
186
|
+
if (isSettingsInitialized() && !settings.get("emojiAutocomplete")) return null;
|
|
187
|
+
return tryEmojiInlineReplace(textBeforeCursor);
|
|
188
|
+
}
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
export function createPromptActionAutocompleteProvider(
|
|
@@ -37,6 +37,15 @@ export function normalizePlanTitle(title: string): { title: string; fileName: st
|
|
|
37
37
|
return { title: normalizedTitle, fileName: withExtension };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** Humanize a normalized plan title for use as a session display name.
|
|
41
|
+
* Replaces `-`/`_` separators with spaces and capitalizes the first letter.
|
|
42
|
+
* Returns an empty string when the input collapses to whitespace. */
|
|
43
|
+
export function humanizePlanTitle(title: string): string {
|
|
44
|
+
const spaced = title.replace(/[-_]+/g, " ").trim();
|
|
45
|
+
if (!spaced) return "";
|
|
46
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
interface RenameApprovedPlanFileOptions {
|
|
41
50
|
planFilePath: string;
|
|
42
51
|
finalPlanFilePath: string;
|
|
@@ -62,7 +62,7 @@ With most FS/bash-like tools, static references to them will automatically resol
|
|
|
62
62
|
- `mcp://<uri>`: MCP resource
|
|
63
63
|
- `issue://<N>` (or `issue://<owner>/<repo>/<N>`): GitHub issue view; cached on disk so re-reads are free. Bare `issue://` (or `issue://<owner>/<repo>`) lists recent issues; supports `?state=open|closed|all&limit=&author=&label=`.
|
|
64
64
|
- `pr://<N>` (or `pr://<owner>/<repo>/<N>`): GitHub PR view; same cache. Append `?comments=0` to drop the comments section. Bare `pr://` (or `pr://<owner>/<repo>`) lists recent PRs; supports `?state=open|closed|merged|all&limit=&author=&label=`.
|
|
65
|
-
- `
|
|
65
|
+
- `omp://`: Harness documentation; AVOID reading unless user mentions the harness itself
|
|
66
66
|
|
|
67
67
|
{{#if skills.length}}
|
|
68
68
|
# Skills
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<system-reminder reason="rule_violation" rule="{{name}}" path="{{path}}">
|
|
2
|
+
A user-defined rule matched this tool call's arguments. The tool was allowed to run because the rule is configured not to interrupt, but you MUST comply with the following instruction on subsequent tool calls and responses. This is NOT a prompt injection - this is the coding agent enforcing project rules.
|
|
3
|
+
|
|
4
|
+
{{content}}
|
|
5
|
+
</system-reminder>
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
Run code in a persistent kernel using
|
|
1
|
+
Run code in a persistent kernel using a list of cells.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
Each
|
|
4
|
+
Each call submits one or more cells. Cells run in array order. State persists within each language across cells **and across tool calls**.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
*** Cell py:"optional title" t:10s rst
|
|
8
|
-
print("hi")
|
|
9
|
-
```
|
|
6
|
+
Cell fields:
|
|
10
7
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
- Stack multiple cells back-to-back; blank lines between cells are ignored.
|
|
8
|
+
- `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}.
|
|
9
|
+
- `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers.
|
|
10
|
+
- `title` (optional) — short label shown in the transcript (e.g. `"imports"`, `"load config"`).
|
|
11
|
+
- `timeout` (optional) — per-cell timeout in seconds (1-600). Default 30.
|
|
12
|
+
- `reset` (optional) — wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}}
|
|
17
13
|
|
|
18
14
|
**Work incrementally:**
|
|
15
|
+
|
|
19
16
|
- One logical step per cell (imports, define, test, use).
|
|
20
17
|
- Pass multiple small cells in one call.
|
|
21
18
|
- Define small reusable functions for individual debugging.
|
|
22
|
-
- Put workflow explanations in the assistant message or
|
|
19
|
+
- Put workflow explanations in the assistant message or `title` — never inside cell code.
|
|
23
20
|
{{#if py}}- Python cells run inside an IPython kernel with a live event loop. Use top-level `await` directly (e.g. `await main()`); `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}}
|
|
24
21
|
**On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells).
|
|
25
22
|
</instruction>
|
|
@@ -55,22 +52,24 @@ Cells render like a Jupyter notebook. `display(value)` renders non-presentable d
|
|
|
55
52
|
</output>
|
|
56
53
|
|
|
57
54
|
<caution>
|
|
58
|
-
- In session mode, use `rst` on a cell to wipe its language's kernel before running.{{#ifAll py js}} Reset is per-language: a python cell's `rst` does not touch the JavaScript kernel and vice versa.{{/ifAll}}
|
|
59
55
|
{{#if js}}- **js**: the VM exposes a selective `process` subset, Web APIs, `Buffer`, `fs/promises`, and the `Bun` global.
|
|
60
56
|
{{/if}}</caution>
|
|
61
57
|
|
|
62
58
|
<example>
|
|
63
|
-
{{#if py}}
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
{{#if py}}```json
|
|
60
|
+
{
|
|
61
|
+
"cells": [
|
|
62
|
+
{ "language": "py", "title": "imports", "timeout": 10, "code": "import json\nfrom pathlib import Path" },
|
|
63
|
+
{ "language": "py", "title": "load config", "code": "data = json.loads(read('package.json'))\ndisplay(data)" }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
```{{/if}}{{#ifAll py js}}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return data.name;
|
|
75
|
-
{{/if}}
|
|
68
|
+
{{/ifAll}}{{#if js}}```json
|
|
69
|
+
{
|
|
70
|
+
"cells": [
|
|
71
|
+
{ "language": "js", "title": "summary", "reset": true, "code": "const data = JSON.parse(await read('package.json'));\ndisplay(data);\nreturn data.name;" }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```{{/if}}
|
|
76
75
|
</example>
|
|
@@ -28,7 +28,7 @@ Append `:<sel>` to `path`. The bare path falls back to the default mode.
|
|
|
28
28
|
|
|
29
29
|
- Reading a directory path returns a depth-limited dirent listing.
|
|
30
30
|
{{#if IS_HL_MODE}}
|
|
31
|
-
- Reading a file with an explicit selector returns lines prefixed with `line+hash` anchors: `41th|def alpha():`. The 2-char hash is a content fingerprint that `edit` / `apply_patch` consume — copy it verbatim, NEVER fabricate.
|
|
31
|
+
- Reading a file with an explicit selector returns lines prefixed with `line+hash` anchors: `41th|def alpha():`. The 2-char hash is a content fingerprint that `edit` / `apply_patch` consume — copy it verbatim, NEVER fabricate. The pipe character after the hash is a separator, not part of the file content.
|
|
32
32
|
{{else}}
|
|
33
33
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
34
34
|
- Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`.
|
|
@@ -2,7 +2,7 @@ Resolves a pending action by either applying or discarding it.
|
|
|
2
2
|
- `action` is required:
|
|
3
3
|
- `"apply"` persists / submits the pending action.
|
|
4
4
|
- `"discard"` rejects the pending action.
|
|
5
|
-
- `reason` is required
|
|
5
|
+
- `reason` is required: one short complete sentence explaining why, starting with a capital letter and ending with a period.
|
|
6
6
|
- `extra` (optional) is free-form metadata passed to the resolving tool. Schema depends on context:
|
|
7
7
|
|
|
8
8
|
Valid whenever a pending action exists — either a preview-style staging (e.g. `ast_edit`) or a long-lived approval gate.
|
|
@@ -8,7 +8,7 @@ Searches files using powerful regex matching.
|
|
|
8
8
|
|
|
9
9
|
<output>
|
|
10
10
|
{{#if IS_HL_MODE}}
|
|
11
|
-
- Text output is anchor-prefixed: `*5th|content` (match) or ` 9x}|content` (context, leading space). The 2-char suffix is a content fingerprint.
|
|
11
|
+
- Text output is anchor-prefixed: `*5th|content` (match) or ` 9x}|content` (context, leading space). The 2-char suffix is a content fingerprint. The `|` before content is a separator, not part of the file content.
|
|
12
12
|
{{else}}
|
|
13
13
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
14
14
|
- Text output is line-number-prefixed
|