@oh-my-pi/pi-coding-agent 14.7.3 → 14.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 +35 -0
- package/package.json +7 -7
- package/src/cli/read-cli.ts +1 -2
- package/src/cli.ts +7 -1
- package/src/commands/read.ts +2 -7
- package/src/config/settings-schema.ts +0 -5
- package/src/edit/modes/hashline.ts +40 -19
- package/src/edit/modes/patch.ts +7 -5
- package/src/edit/modes/replace.ts +6 -2
- package/src/edit/notebook.ts +222 -0
- package/src/edit/read-file.ts +7 -0
- package/src/edit/renderer.ts +4 -3
- package/src/edit/streaming.ts +49 -7
- package/src/modes/components/diff.ts +54 -7
- package/src/modes/interactive-mode.ts +32 -4
- package/src/modes/loop-limit.ts +140 -0
- package/src/modes/types.ts +3 -1
- package/src/prompts/agents/designer.md +1 -2
- package/src/prompts/agents/explore.md +2 -5
- package/src/prompts/agents/init.md +1 -4
- package/src/prompts/agents/librarian.md +1 -3
- package/src/prompts/agents/plan.md +7 -8
- package/src/prompts/agents/reviewer.md +1 -2
- package/src/prompts/ci-green-request.md +10 -10
- package/src/prompts/commands/orchestrate.md +48 -0
- package/src/prompts/memories/consolidation.md +10 -10
- package/src/prompts/memories/read-path.md +6 -6
- package/src/prompts/system/agent-creation-architect.md +54 -44
- package/src/prompts/system/custom-system-prompt.md +3 -5
- package/src/prompts/system/eager-todo.md +4 -4
- package/src/prompts/system/handoff-document.md +7 -4
- package/src/prompts/system/plan-mode-active.md +7 -3
- package/src/prompts/system/plan-mode-approved.md +5 -5
- package/src/prompts/system/summarization-system.md +2 -2
- package/src/prompts/system/system-prompt.md +53 -65
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/web-search.md +16 -19
- package/src/prompts/tools/bash.md +8 -8
- package/src/prompts/tools/browser.md +4 -4
- package/src/prompts/tools/debug.md +3 -1
- package/src/prompts/tools/eval.md +13 -9
- package/src/prompts/tools/hashline.md +4 -2
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/reflect.md +3 -3
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/resolve.md +2 -2
- package/src/prompts/tools/retain.md +3 -2
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +3 -4
- package/src/prompts/tools/task.md +1 -1
- package/src/slash-commands/builtin-registry.ts +4 -2
- package/src/task/commands.ts +5 -1
- package/src/tools/fetch.ts +6 -7
- package/src/tools/index.ts +0 -4
- package/src/tools/read.ts +18 -7
- package/src/tools/renderers.ts +0 -2
- package/src/tools/write.ts +41 -26
- package/src/tools/notebook.ts +0 -286
package/src/edit/streaming.ts
CHANGED
|
@@ -16,7 +16,13 @@ import type { Theme } from "../modes/theme/theme";
|
|
|
16
16
|
import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
17
17
|
import { computeEditDiff, type DiffError, type DiffResult } from "./diff";
|
|
18
18
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
computeHashlineDiff,
|
|
21
|
+
computeHashlineSectionDiff,
|
|
22
|
+
containsRecognizableHashlineOperations,
|
|
23
|
+
type HashlineInputSection,
|
|
24
|
+
splitHashlineInputs,
|
|
25
|
+
} from "./modes/hashline";
|
|
20
26
|
import { computePatchDiff, type PatchEditEntry } from "./modes/patch";
|
|
21
27
|
import type { ReplaceEditEntry } from "./modes/replace";
|
|
22
28
|
|
|
@@ -223,12 +229,48 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
223
229
|
async computeDiffPreview(args, ctx) {
|
|
224
230
|
if (typeof args.input !== "string" || args.input.length === 0) return null;
|
|
225
231
|
ctx.signal.throwIfAborted();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
|
|
233
|
+
let sections: HashlineInputSection[];
|
|
234
|
+
try {
|
|
235
|
+
sections = splitHashlineInputs(args.input, { cwd: ctx.cwd, path: args.path });
|
|
236
|
+
} catch {
|
|
237
|
+
// Single-section fallback keeps the original error rendering for the
|
|
238
|
+
// "haven't typed `@PATH` yet" case.
|
|
239
|
+
const result = await computeHashlineDiff({ input: args.input, path: args.path }, ctx.cwd, {
|
|
240
|
+
autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
|
|
241
|
+
});
|
|
242
|
+
ctx.signal.throwIfAborted();
|
|
243
|
+
if ("error" in result && !args.path) return [{ path: "", error: result.error }];
|
|
244
|
+
return [toPerFilePreview(args.path ?? "", result)];
|
|
245
|
+
}
|
|
246
|
+
if (sections.length === 0) return null;
|
|
247
|
+
|
|
248
|
+
// While the trailing section is still being typed (no operations yet)
|
|
249
|
+
// skip it so its empty/parse-error result doesn't replace previews of
|
|
250
|
+
// already-completed sections with an opaque header.
|
|
251
|
+
const lastIndex = sections.length - 1;
|
|
252
|
+
const trailingIncomplete =
|
|
253
|
+
sections.length > 1 && !containsRecognizableHashlineOperations(sections[lastIndex].diff);
|
|
254
|
+
const sectionsToProcess = trailingIncomplete ? sections.slice(0, -1) : sections;
|
|
255
|
+
const trailingProcessedIndex = sectionsToProcess.length - 1;
|
|
256
|
+
|
|
257
|
+
const previews: PerFileDiffPreview[] = [];
|
|
258
|
+
for (let i = 0; i < sectionsToProcess.length; i++) {
|
|
259
|
+
ctx.signal.throwIfAborted();
|
|
260
|
+
const section = sectionsToProcess[i];
|
|
261
|
+
const result = await computeHashlineSectionDiff(section, ctx.cwd, {
|
|
262
|
+
autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
|
|
263
|
+
});
|
|
264
|
+
ctx.signal.throwIfAborted();
|
|
265
|
+
// In a multi-section preview, ignore parse/apply errors from the
|
|
266
|
+
// last section: it's still streaming and the partial op may not
|
|
267
|
+
// parse yet. Earlier sections are stable and stay rendered.
|
|
268
|
+
if (sectionsToProcess.length > 1 && i === trailingProcessedIndex && "error" in result) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
previews.push(toPerFilePreview(section.path, result));
|
|
272
|
+
}
|
|
273
|
+
return previews.length > 0 ? previews : null;
|
|
232
274
|
},
|
|
233
275
|
renderStreamingFallback() {
|
|
234
276
|
return "";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
2
2
|
import { getIndentation } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import * as Diff from "diff";
|
|
4
|
-
import { theme } from "../../modes/theme/theme";
|
|
4
|
+
import { getLanguageFromPath, highlightCode, theme } from "../../modes/theme/theme";
|
|
5
5
|
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../../tools/render-utils";
|
|
6
6
|
|
|
7
7
|
/** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
|
|
@@ -115,6 +115,10 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
115
115
|
return Math.max(width, lineNumber.length);
|
|
116
116
|
}, 0);
|
|
117
117
|
|
|
118
|
+
// Batch-highlight context (unedited) lines so consecutive lines tokenize
|
|
119
|
+
// with full multi-line context. Highlighting is a no-op when no language
|
|
120
|
+
// can be detected from the file path.
|
|
121
|
+
const contextHighlights = highlightContextLines(parsedLines, options.filePath);
|
|
118
122
|
// Track the line number rendered on the previous emitted line so we can
|
|
119
123
|
// blank out duplicate gutters. Two cases trigger this:
|
|
120
124
|
// 1. Single-line replacement (`-N` followed by `+N`) — the `+N` repeats `N`.
|
|
@@ -206,15 +210,58 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
206
210
|
);
|
|
207
211
|
i++;
|
|
208
212
|
} else {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
213
|
+
const highlighted = contextHighlights.get(i);
|
|
214
|
+
const content =
|
|
215
|
+
highlighted !== undefined
|
|
216
|
+
? replaceTabs(highlighted, options.filePath)
|
|
217
|
+
: visualizeIndent(parsed.content, options.filePath);
|
|
218
|
+
result.push(theme.fg("toolDiffContext", formatLine(" ", parsed.lineNum, content)));
|
|
215
219
|
i++;
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
return result.join("\n");
|
|
220
224
|
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Batch-highlight runs of consecutive context lines.
|
|
228
|
+
* Returns a map keyed by index in `parsedLines` to the highlighted content
|
|
229
|
+
* for that line. Lines whose language is unknown are not added to the map,
|
|
230
|
+
* letting callers fall back to the existing rendering path.
|
|
231
|
+
*/
|
|
232
|
+
function highlightContextLines(
|
|
233
|
+
parsedLines: Array<{ prefix: CodeFrameMarker; lineNum: string; content: string } | null>,
|
|
234
|
+
filePath: string | undefined,
|
|
235
|
+
): Map<number, string> {
|
|
236
|
+
const map = new Map<number, string>();
|
|
237
|
+
const lang = filePath ? getLanguageFromPath(filePath) : undefined;
|
|
238
|
+
if (!lang) return map;
|
|
239
|
+
|
|
240
|
+
let runIndices: number[] = [];
|
|
241
|
+
let runContents: string[] = [];
|
|
242
|
+
const flush = () => {
|
|
243
|
+
if (runContents.length === 0) return;
|
|
244
|
+
const highlighted = highlightCode(runContents.join("\n"), lang);
|
|
245
|
+
for (let k = 0; k < runIndices.length; k++) {
|
|
246
|
+
map.set(runIndices[k], highlighted[k] ?? runContents[k]);
|
|
247
|
+
}
|
|
248
|
+
runIndices = [];
|
|
249
|
+
runContents = [];
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
for (let j = 0; j < parsedLines.length; j++) {
|
|
253
|
+
const p = parsedLines[j];
|
|
254
|
+
// Collapse markers ("...") are emitted as context lines but are not real
|
|
255
|
+
// code; highlighting them produces nonsense (e.g. "..." → spread operator)
|
|
256
|
+
// and would also stitch together unrelated context blocks across the gap.
|
|
257
|
+
const isCollapseMarker = p?.prefix === " " && (p.content === "..." || p.content === "…");
|
|
258
|
+
if (p && p.prefix === " " && !isCollapseMarker) {
|
|
259
|
+
runIndices.push(j);
|
|
260
|
+
runContents.push(p.content);
|
|
261
|
+
} else {
|
|
262
|
+
flush();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
flush();
|
|
266
|
+
return map;
|
|
267
|
+
}
|
|
@@ -73,6 +73,15 @@ import { MCPCommandController } from "./controllers/mcp-command-controller";
|
|
|
73
73
|
import { SelectorController } from "./controllers/selector-controller";
|
|
74
74
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
75
75
|
import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
76
|
+
import {
|
|
77
|
+
consumeLoopLimitIteration,
|
|
78
|
+
createLoopLimitRuntime,
|
|
79
|
+
describeLoopLimit,
|
|
80
|
+
describeLoopLimitRuntime,
|
|
81
|
+
isLoopDurationExpired,
|
|
82
|
+
type LoopLimitRuntime,
|
|
83
|
+
parseLoopLimitArgs,
|
|
84
|
+
} from "./loop-limit";
|
|
76
85
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
77
86
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
78
87
|
import type { Theme } from "./theme/theme";
|
|
@@ -158,6 +167,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
158
167
|
planModePlanFilePath: string | undefined = undefined;
|
|
159
168
|
loopModeEnabled = false;
|
|
160
169
|
loopPrompt: string | undefined = undefined;
|
|
170
|
+
loopLimit: LoopLimitRuntime | undefined = undefined;
|
|
161
171
|
#loopAutoSubmitTimer: NodeJS.Timeout | undefined;
|
|
162
172
|
todoPhases: TodoPhase[] = [];
|
|
163
173
|
hideThinkingBlock = false;
|
|
@@ -535,25 +545,35 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
535
545
|
}
|
|
536
546
|
|
|
537
547
|
async #runLoopIteration(action: "prompt" | "compact" | "reset", prompt: string): Promise<void> {
|
|
548
|
+
if (!consumeLoopLimitIteration(this.loopLimit)) {
|
|
549
|
+
this.disableLoopMode("Loop limit reached. Loop mode disabled.");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
538
553
|
if (action === "compact") {
|
|
539
554
|
await this.handleCompactCommand();
|
|
540
555
|
} else if (action === "reset") {
|
|
541
556
|
await this.handleClearCommand();
|
|
542
557
|
}
|
|
543
558
|
if (!this.loopModeEnabled || !this.onInputCallback) return;
|
|
559
|
+
if (isLoopDurationExpired(this.loopLimit)) {
|
|
560
|
+
this.disableLoopMode("Loop time limit reached. Loop mode disabled.");
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
544
563
|
this.onInputCallback(this.startPendingSubmission({ text: prompt }));
|
|
545
564
|
}
|
|
546
565
|
|
|
547
|
-
disableLoopMode(): void {
|
|
566
|
+
disableLoopMode(message = "Loop mode disabled."): void {
|
|
548
567
|
const wasEnabled = this.loopModeEnabled;
|
|
549
568
|
this.loopModeEnabled = false;
|
|
550
569
|
this.loopPrompt = undefined;
|
|
570
|
+
this.loopLimit = undefined;
|
|
551
571
|
this.#cancelLoopAutoSubmit();
|
|
552
572
|
this.statusLine.setLoopModeStatus(undefined);
|
|
553
573
|
this.updateEditorTopBorder();
|
|
554
574
|
this.ui.requestRender();
|
|
555
575
|
if (wasEnabled) {
|
|
556
|
-
this.showStatus(
|
|
576
|
+
this.showStatus(message);
|
|
557
577
|
}
|
|
558
578
|
}
|
|
559
579
|
|
|
@@ -567,18 +587,26 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
567
587
|
this.#cancelLoopAutoSubmit();
|
|
568
588
|
}
|
|
569
589
|
|
|
570
|
-
async handleLoopCommand(): Promise<void> {
|
|
590
|
+
async handleLoopCommand(args = ""): Promise<void> {
|
|
571
591
|
if (this.loopModeEnabled) {
|
|
572
592
|
this.disableLoopMode();
|
|
573
593
|
return;
|
|
574
594
|
}
|
|
595
|
+
const parsedLimit = parseLoopLimitArgs(args);
|
|
596
|
+
if (typeof parsedLimit === "string") {
|
|
597
|
+
this.showError(parsedLimit);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
575
600
|
this.loopModeEnabled = true;
|
|
576
601
|
this.loopPrompt = undefined;
|
|
602
|
+
this.loopLimit = createLoopLimitRuntime(parsedLimit);
|
|
577
603
|
this.statusLine.setLoopModeStatus({ enabled: true });
|
|
578
604
|
this.updateEditorTopBorder();
|
|
579
605
|
this.ui.requestRender();
|
|
606
|
+
const limitSuffix = parsedLimit ? ` Limited to ${describeLoopLimit(parsedLimit)}.` : "";
|
|
607
|
+
const remainingSuffix = this.loopLimit ? ` ${describeLoopLimitRuntime(this.loopLimit)}.` : "";
|
|
580
608
|
this.showStatus(
|
|
581
|
-
|
|
609
|
+
`Loop mode enabled.${limitSuffix}${remainingSuffix} Your next prompt will repeat after each turn. Esc cancels the current iteration; /loop again to disable.`,
|
|
582
610
|
);
|
|
583
611
|
}
|
|
584
612
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export type LoopLimitConfig =
|
|
2
|
+
| {
|
|
3
|
+
kind: "iterations";
|
|
4
|
+
iterations: number;
|
|
5
|
+
}
|
|
6
|
+
| {
|
|
7
|
+
kind: "duration";
|
|
8
|
+
durationMs: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type LoopLimitRuntime =
|
|
12
|
+
| {
|
|
13
|
+
kind: "iterations";
|
|
14
|
+
initial: number;
|
|
15
|
+
remaining: number;
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
kind: "duration";
|
|
19
|
+
durationMs: number;
|
|
20
|
+
deadlineMs: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const TIME_UNITS_MS = new Map<string, number>([
|
|
24
|
+
["s", 1_000],
|
|
25
|
+
["sec", 1_000],
|
|
26
|
+
["secs", 1_000],
|
|
27
|
+
["second", 1_000],
|
|
28
|
+
["seconds", 1_000],
|
|
29
|
+
["m", 60_000],
|
|
30
|
+
["min", 60_000],
|
|
31
|
+
["mins", 60_000],
|
|
32
|
+
["minute", 60_000],
|
|
33
|
+
["minutes", 60_000],
|
|
34
|
+
["h", 3_600_000],
|
|
35
|
+
["hr", 3_600_000],
|
|
36
|
+
["hrs", 3_600_000],
|
|
37
|
+
["hour", 3_600_000],
|
|
38
|
+
["hours", 3_600_000],
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export function parseLoopLimitArgs(args: string): LoopLimitConfig | undefined | string {
|
|
42
|
+
const trimmed = args.trim().toLowerCase();
|
|
43
|
+
if (!trimmed) return undefined;
|
|
44
|
+
|
|
45
|
+
const parts = trimmed.split(/\s+/);
|
|
46
|
+
if (parts.length > 2) {
|
|
47
|
+
return "Usage: /loop [count|duration]. Examples: /loop 10, /loop 10m, /loop 10min.";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (parts.length === 2) {
|
|
51
|
+
return parseDurationParts(parts[0], parts[1]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const token = parts[0];
|
|
55
|
+
const iterationMatch = /^(\d+)$/.exec(token);
|
|
56
|
+
if (iterationMatch) {
|
|
57
|
+
const iterations = Number(iterationMatch[1]);
|
|
58
|
+
if (!Number.isSafeInteger(iterations) || iterations <= 0) {
|
|
59
|
+
return "Loop count must be a positive integer.";
|
|
60
|
+
}
|
|
61
|
+
return { kind: "iterations", iterations };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const durationMatch = /^(\d+)([a-z]+)$/.exec(token);
|
|
65
|
+
if (durationMatch) {
|
|
66
|
+
return parseDurationParts(durationMatch[1], durationMatch[2]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return "Usage: /loop [count|duration]. Examples: /loop 10, /loop 10m, /loop 10min.";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseDurationParts(amountText: string, unitText: string): LoopLimitConfig | string {
|
|
73
|
+
if (!/^\d+$/.test(amountText)) {
|
|
74
|
+
return "Loop duration must use a positive integer amount.";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const amount = Number(amountText);
|
|
78
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
79
|
+
return "Loop duration must be positive.";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const unitMs = TIME_UNITS_MS.get(unitText);
|
|
83
|
+
if (unitMs === undefined) {
|
|
84
|
+
return "Loop duration unit must be seconds, minutes, or hours.";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { kind: "duration", durationMs: amount * unitMs };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createLoopLimitRuntime(
|
|
91
|
+
config: LoopLimitConfig | undefined,
|
|
92
|
+
nowMs = Date.now(),
|
|
93
|
+
): LoopLimitRuntime | undefined {
|
|
94
|
+
if (!config) return undefined;
|
|
95
|
+
if (config.kind === "iterations") {
|
|
96
|
+
return { kind: "iterations", initial: config.iterations, remaining: config.iterations };
|
|
97
|
+
}
|
|
98
|
+
return { kind: "duration", durationMs: config.durationMs, deadlineMs: nowMs + config.durationMs };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function consumeLoopLimitIteration(limit: LoopLimitRuntime | undefined, nowMs = Date.now()): boolean {
|
|
102
|
+
if (!limit) return true;
|
|
103
|
+
if (limit.kind === "duration") {
|
|
104
|
+
return nowMs < limit.deadlineMs;
|
|
105
|
+
}
|
|
106
|
+
if (limit.remaining <= 0) return false;
|
|
107
|
+
limit.remaining -= 1;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isLoopDurationExpired(limit: LoopLimitRuntime | undefined, nowMs = Date.now()): boolean {
|
|
112
|
+
return limit?.kind === "duration" && nowMs >= limit.deadlineMs;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function describeLoopLimit(config: LoopLimitConfig): string {
|
|
116
|
+
if (config.kind === "iterations") {
|
|
117
|
+
return `${config.iterations} ${config.iterations === 1 ? "iteration" : "iterations"}`;
|
|
118
|
+
}
|
|
119
|
+
return formatDuration(config.durationMs);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function describeLoopLimitRuntime(limit: LoopLimitRuntime): string {
|
|
123
|
+
if (limit.kind === "iterations") {
|
|
124
|
+
return `${limit.remaining} of ${limit.initial} ${limit.initial === 1 ? "iteration" : "iterations"} remaining`;
|
|
125
|
+
}
|
|
126
|
+
return `${formatDuration(limit.durationMs)} limit`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function formatDuration(durationMs: number): string {
|
|
130
|
+
if (durationMs % 3_600_000 === 0) {
|
|
131
|
+
const hours = durationMs / 3_600_000;
|
|
132
|
+
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
133
|
+
}
|
|
134
|
+
if (durationMs % 60_000 === 0) {
|
|
135
|
+
const minutes = durationMs / 60_000;
|
|
136
|
+
return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
137
|
+
}
|
|
138
|
+
const seconds = durationMs / 1_000;
|
|
139
|
+
return `${seconds} ${seconds === 1 ? "second" : "seconds"}`;
|
|
140
|
+
}
|
package/src/modes/types.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { HookInputComponent } from "./components/hook-input";
|
|
|
24
24
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
25
25
|
import type { StatusLineComponent } from "./components/status-line";
|
|
26
26
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
27
|
+
import type { LoopLimitRuntime } from "./loop-limit";
|
|
27
28
|
import type { OAuthManualInputManager } from "./oauth-manual-input";
|
|
28
29
|
import type { Theme } from "./theme/theme";
|
|
29
30
|
|
|
@@ -86,6 +87,7 @@ export interface InteractiveModeContext {
|
|
|
86
87
|
planModeEnabled: boolean;
|
|
87
88
|
loopModeEnabled: boolean;
|
|
88
89
|
loopPrompt?: string;
|
|
90
|
+
loopLimit?: LoopLimitRuntime;
|
|
89
91
|
planModePlanFilePath?: string;
|
|
90
92
|
hideThinkingBlock: boolean;
|
|
91
93
|
pendingImages: ImageContent[];
|
|
@@ -248,7 +250,7 @@ export interface InteractiveModeContext {
|
|
|
248
250
|
openExternalEditor(): void;
|
|
249
251
|
registerExtensionShortcuts(): void;
|
|
250
252
|
handlePlanModeCommand(initialPrompt?: string): Promise<void>;
|
|
251
|
-
handleLoopCommand(): Promise<void>;
|
|
253
|
+
handleLoopCommand(args?: string): Promise<void>;
|
|
252
254
|
disableLoopMode(): void;
|
|
253
255
|
pauseLoop(): void;
|
|
254
256
|
handleExitPlanModeTool(details: ExitPlanModeDetails): Promise<void>;
|
|
@@ -4,8 +4,7 @@ description: UI/UX specialist for design implementation, review, visual refineme
|
|
|
4
4
|
model: pi/designer
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
You **MAY** make file edits, create components, and run commands—and **SHOULD** do so when needed.
|
|
7
|
+
Implement and review UI designs. Edit files, create components, run commands when needed.
|
|
9
8
|
|
|
10
9
|
<strengths>
|
|
11
10
|
- Translate design intent into working UI code
|
|
@@ -29,13 +29,11 @@ output:
|
|
|
29
29
|
type: string
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Given a task, you rapidly investigate the codebase and return structured findings another agent can use without re-reading everything.
|
|
32
|
+
Investigate the codebase rapidly. Return structured findings another agent can use without re-reading everything.
|
|
35
33
|
|
|
36
34
|
<directives>
|
|
37
35
|
- You **MUST** use tools for broad pattern matching / code search as much as possible.
|
|
38
|
-
- You **SHOULD** invoke tools in parallel
|
|
36
|
+
- You **SHOULD** invoke tools in parallel—this is a short investigation, and you are supposed to finish in a few seconds.
|
|
39
37
|
- If a search returns empty results, you **MUST** try at least one alternate strategy (different pattern, broader path, or AST search) before concluding the target doesn't exist.
|
|
40
38
|
</directives>
|
|
41
39
|
|
|
@@ -47,7 +45,6 @@ You **MUST** infer the thoroughness from the task; default to medium:
|
|
|
47
45
|
</thoroughness>
|
|
48
46
|
|
|
49
47
|
<procedure>
|
|
50
|
-
You **SHOULD** generally follow this procedure, but are allowed to adjust it as the task requires:
|
|
51
48
|
1. Locate relevant code using tools.
|
|
52
49
|
2. Read key sections (You **MUST NOT** read full files unless they're tiny)
|
|
53
50
|
3. Identify types/interfaces/key functions.
|
|
@@ -4,12 +4,9 @@ description: Generate AGENTS.md for current codebase
|
|
|
4
4
|
thinking-level: medium
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
You **MUST** launch multiple `explore` agents in parallel (via `task` tool) scanning different areas (core src, tests, configs/build, scripts/docs), then synthesize your findings into a detailed AGENTS.md file.
|
|
7
|
+
Generate AGENTS.md by launching multiple `explore` agents in parallel (via `task` tool) scanning different areas (core src, tests, configs/build, scripts/docs), then synthesize findings into a single file.
|
|
10
8
|
|
|
11
9
|
<structure>
|
|
12
|
-
You will likely need to document these sections, but only take it as a starting point and adjust it to the specific codebase:
|
|
13
10
|
- **Project Overview**: Brief description of project purpose
|
|
14
11
|
- **Architecture & Data Flow**: High-level structure, key modules, data flow
|
|
15
12
|
- **Key Directories**: Main source directories, purposes
|
|
@@ -65,7 +65,7 @@ output:
|
|
|
65
65
|
type: string
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Answer questions about external libraries, frameworks, and APIs by reading source code and official documentation.
|
|
69
69
|
|
|
70
70
|
<critical>
|
|
71
71
|
You **MUST** ground every claim in source code or official documentation. You **MUST NOT** rely on training data for API details — it may be stale or wrong.
|
|
@@ -74,8 +74,6 @@ You **MUST** operate as read-only on the user's project. You **MUST NOT** modify
|
|
|
74
74
|
|
|
75
75
|
<procedure>
|
|
76
76
|
## 1. Classify the request
|
|
77
|
-
|
|
78
|
-
Before acting, determine what kind of question this is:
|
|
79
77
|
- **Conceptual**: "How do I use X?", "Best practice for Y?" — Prioritize types, docs, and usage examples.
|
|
80
78
|
- **Implementation**: "How does X implement Y?", "Show me the source of Z" — Clone and read the actual code.
|
|
81
79
|
- **Behavioral**: "Why does X behave this way?", "What's the default for Y?" — Read implementation, find where values are set, check tests.
|
|
@@ -7,7 +7,7 @@ model: pi/plan, pi/slow
|
|
|
7
7
|
thinking-level: high
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Analyze the codebase and the user's request. Produce a detailed implementation plan.
|
|
11
11
|
|
|
12
12
|
## Phase 1: Understand
|
|
13
13
|
1. Parse requirements precisely
|
|
@@ -33,14 +33,13 @@ You **MUST** spawn `explore` agents for independent areas and synthesize finding
|
|
|
33
33
|
|
|
34
34
|
You **MUST** write a plan executable without re-exploration.
|
|
35
35
|
|
|
36
|
-
You will likely need to document these sections, but only take it as a starting point and adjust it to the specific request.
|
|
37
36
|
<structure>
|
|
38
|
-
**Summary**: What to build and why (one paragraph).
|
|
39
|
-
**Changes**: List concrete changes (files, functions, types), concrete as much as possible. Exact file paths/line ranges where relevant.
|
|
40
|
-
**Sequence**: List sequence and dependencies between sub-tasks, to schedule them in the best order.
|
|
41
|
-
**Edge Cases**: List edge cases and error conditions, to be aware of.
|
|
42
|
-
**Verification**: List verification steps, to be able to verify the correctness.
|
|
43
|
-
**Critical Files**: List critical files, to be able to read them and understand the codebase.
|
|
37
|
+
- **Summary**: What to build and why (one paragraph).
|
|
38
|
+
- **Changes**: List concrete changes (files, functions, types), concrete as much as possible. Exact file paths/line ranges where relevant.
|
|
39
|
+
- **Sequence**: List sequence and dependencies between sub-tasks, to schedule them in the best order.
|
|
40
|
+
- **Edge Cases**: List edge cases and error conditions, to be aware of.
|
|
41
|
+
- **Verification**: List verification steps, to be able to verify the correctness.
|
|
42
|
+
- **Critical Files**: List critical files, to be able to read them and understand the codebase.
|
|
44
43
|
</structure>
|
|
45
44
|
|
|
46
45
|
<critical>
|
|
@@ -56,8 +56,7 @@ output:
|
|
|
56
56
|
type: number
|
|
57
57
|
---
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
Your goal is to identify bugs the author would want fixed before merge.
|
|
59
|
+
Identify bugs the author would want fixed before merge.
|
|
61
60
|
|
|
62
61
|
<procedure>
|
|
63
62
|
1. Run `git diff` (or `gh pr diff <number>`) to view patch
|
|
@@ -4,24 +4,24 @@ Do not stop after a single fix attempt.
|
|
|
4
4
|
</critical>
|
|
5
5
|
|
|
6
6
|
<instruction>
|
|
7
|
-
- Prefer
|
|
7
|
+
- Prefer `github` tool with `op: run_watch` and no other arguments if available.
|
|
8
8
|
- Otherwise use `gh` cli.
|
|
9
|
-
- Use
|
|
9
|
+
- Use workflow runs for current HEAD as source of truth after each push.
|
|
10
10
|
</instruction>
|
|
11
11
|
|
|
12
12
|
<procedure>
|
|
13
|
-
1. Watch
|
|
14
|
-
2. If any run fails, inspect
|
|
15
|
-
3. Identify
|
|
16
|
-
4. Run local verification
|
|
13
|
+
1. Watch workflow runs for current HEAD commit.
|
|
14
|
+
2. If any run fails, inspect failing job output and logs.
|
|
15
|
+
3. Identify root cause and make minimal correct fix.
|
|
16
|
+
4. Run local verification if it reduces chance of another failing push.
|
|
17
17
|
5. Push the branch.
|
|
18
|
-
6. Watch
|
|
19
|
-
7. Repeat until
|
|
18
|
+
6. Watch workflow runs for new HEAD commit again.
|
|
19
|
+
7. Repeat until workflow runs for latest HEAD commit succeed.
|
|
20
20
|
</procedure>
|
|
21
21
|
|
|
22
22
|
<caution>
|
|
23
|
-
- Treat each
|
|
24
|
-
- If
|
|
23
|
+
- Treat each push as fresh CI attempt. Re-watch new HEAD immediately.
|
|
24
|
+
- If watcher output is insufficient, inspect underlying workflow or job context before changing code.
|
|
25
25
|
</caution>
|
|
26
26
|
|
|
27
27
|
{{#if headTag}}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrate
|
|
3
|
+
description: Drive a multi-phase task to completion via parallel subagents
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Task
|
|
7
|
+
|
|
8
|
+
$@
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Orchestration Contract
|
|
13
|
+
|
|
14
|
+
You are the **orchestrator** for the task above. Read it once, then execute under the rules below. The contract overrides any default tendency to yield early, narrate, or do work yourself.
|
|
15
|
+
|
|
16
|
+
<role>
|
|
17
|
+
You decompose, dispatch, verify, and iterate. You do **not** edit code. Every file mutation goes through a `task` subagent. Your tool budget is: reading for planning, `task` for dispatch, verification (`bun check`, `bun test`, `recipe`, `lsp diagnostics`), git via `bash`, and `todo_write` for tracking.
|
|
18
|
+
</role>
|
|
19
|
+
|
|
20
|
+
<rules>
|
|
21
|
+
1. **Do not yield until everything is closed.** A phase finishing is *not* a yield point — launch the next phase in the same turn. Stop only when every requested item is verifiably done, or you hit a concrete [blocked] state that genuinely requires the user.
|
|
22
|
+
2. **Enumerate the full surface before dispatching.** If the task references audits, plans, checklists, phase lists, or file lists, expand them into a flat set of items in `todo_write`. "Most of them" or "the important ones" is failure. Re-read the source documents — do not work from memory.
|
|
23
|
+
3. **Parallelize maximally.** Every set of edits with disjoint file scope **MUST** ship as one `task` batch. Serialize only when one subagent produces a contract (types, schema, shared module) the next consumes — and state the dependency when you do.
|
|
24
|
+
4. **Each `task` assignment is self-contained.** Subagents have no shared context. Spell out: target files (≤3–5 explicit paths, no globs), the change with APIs and patterns, edge cases, and observable acceptance criteria. Do not assume they read the same plan you did.
|
|
25
|
+
5. **Verify after every phase before launching the next.** Run the appropriate gate: `bun check` for types, package-scoped `bun test` for behavior, `lsp diagnostics` for changed files. If a phase introduced breakage, dispatch fix-up subagents *before* moving on. Never declare a phase done on a red tree.
|
|
26
|
+
6. **Commit policy.** If the task asks for commits or the repo workflow expects them, commit after each green phase with a focused message. Never commit a red tree. Never commit work the user did not ask to commit.
|
|
27
|
+
7. **Respawn, do not absorb.** If a subagent returns incomplete or wrong work, spawn a corrective subagent with the specific gap — do not silently fix it yourself.
|
|
28
|
+
8. **No scope creep, no scope shrink.** Do not add work the user did not ask for. Do not relabel unfinished items as "follow-up", "v1", or "MVP" to imply completion.
|
|
29
|
+
</rules>
|
|
30
|
+
|
|
31
|
+
<workflow>
|
|
32
|
+
1. **Ingest.** Read every referenced file (audits, plans, prior agent output, current branch state). Run `git status` to see uncommitted changes.
|
|
33
|
+
2. **Plan.** Materialize the full work surface in `todo_write` as ordered phases. Within each phase, list the parallelizable units.
|
|
34
|
+
3. **Dispatch phase.** Launch all parallel `task` subagents in one call. Wait for the batch.
|
|
35
|
+
4. **Verify phase.** Run the gates. On failure, dispatch fix-up subagents and re-verify. Do not advance with a red gate.
|
|
36
|
+
5. **Commit phase** (if applicable). Focused message naming the phase.
|
|
37
|
+
6. **Advance.** Mark the phase done in `todo_write`, immediately start the next phase. No summary message between phases — keep going.
|
|
38
|
+
7. **Final verification.** When the last phase is green, run the full gate set once more and confirm every `todo_write` item is closed. Then yield with a terse status, not a recap.
|
|
39
|
+
</workflow>
|
|
40
|
+
|
|
41
|
+
<anti-patterns>
|
|
42
|
+
- Editing files yourself "because it's faster".
|
|
43
|
+
- Yielding after phase 1 with "ready to continue?".
|
|
44
|
+
- Dispatching one subagent at a time when five could run in parallel.
|
|
45
|
+
- Skipping `bun check` between phases because "the change looked safe".
|
|
46
|
+
- Marking todos done based on subagent self-reports without verifying the gate.
|
|
47
|
+
- Summarizing progress in chat instead of advancing to the next phase.
|
|
48
|
+
</anti-patterns>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
Memory consolidation agent.
|
|
2
2
|
Memory root: memory://root
|
|
3
3
|
Input corpus (raw memories):
|
|
4
4
|
{{raw_memories}}
|
|
@@ -19,12 +19,12 @@ Produce strict JSON only with this schema — you **MUST NOT** include any other
|
|
|
19
19
|
]
|
|
20
20
|
}
|
|
21
21
|
Requirements:
|
|
22
|
-
- memory_md:
|
|
23
|
-
- memory_summary:
|
|
24
|
-
- skills: reusable
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- scripts/templates/examples
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
22
|
+
- memory_md: long-term memory document.
|
|
23
|
+
- memory_summary: prompt-time memory guidance.
|
|
24
|
+
- skills: reusable playbooks. Empty array allowed.
|
|
25
|
+
- skill.name maps to skills/<name>/.
|
|
26
|
+
- skill.content maps to skills/<name>/SKILL.md.
|
|
27
|
+
- scripts/templates/examples: optional. Each entry **MUST** write to skills/<name>/<bucket>/<path>.
|
|
28
|
+
- Only include files worth keeping long-term. Omit stale assets so they are pruned.
|
|
29
|
+
- Preserve useful prior themes. Remove stale or contradictory guidance.
|
|
30
|
+
- Treat memory as advisory: current repository state wins.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Memory Guidance
|
|
2
2
|
Memory root: memory://root
|
|
3
3
|
Operational rules:
|
|
4
|
-
1)
|
|
5
|
-
2) If needed,
|
|
6
|
-
3)
|
|
7
|
-
4)
|
|
8
|
-
5)
|
|
9
|
-
6)
|
|
4
|
+
1) Read `memory://root/memory_summary.md` first.
|
|
5
|
+
2) If needed, inspect `memory://root/MEMORY.md` and `memory://root/skills/<name>/SKILL.md`.
|
|
6
|
+
3) Trust memory for heuristics and process context. Trust current repo files, runtime output, and user instruction for factual state and final decisions.
|
|
7
|
+
4) When memory changes your plan, cite the artifact path (e.g. `memory://root/skills/<name>/SKILL.md`) and pair it with current-repo evidence.
|
|
8
|
+
5) If memory disagrees with repo state or user instruction, prefer repo/user. Treat memory as stale. Proceed with corrected behavior, then update/regenerate memory artifacts.
|
|
9
|
+
6) Escalate confidence only after repository verification. Memory alone **MUST NOT** be treated as sufficient proof.
|
|
10
10
|
Memory summary:
|
|
11
11
|
{{memory_summary}}
|