@oh-my-pi/pi-coding-agent 15.7.6 → 15.8.2
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 +172 -198
- package/dist/types/async/job-manager.d.ts +3 -3
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/claude-trace-cli.d.ts +54 -0
- package/dist/types/cli/session-picker.d.ts +10 -3
- package/dist/types/cli/update-cli.d.ts +17 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +10 -1
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/config/settings.d.ts +13 -0
- package/dist/types/edit/index.d.ts +6 -0
- package/dist/types/edit/streaming.d.ts +8 -0
- package/dist/types/eval/concurrency-bridge.d.ts +26 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/export/ttsr.d.ts +9 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +11 -0
- package/dist/types/main.d.ts +5 -0
- package/dist/types/mcp/transports/stdio.d.ts +19 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -1
- package/dist/types/modes/components/hook-selector.d.ts +3 -0
- package/dist/types/modes/components/session-selector.d.ts +32 -5
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -2
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +9 -2
- package/dist/types/modes/types.d.ts +4 -2
- package/dist/types/registry/agent-registry.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/history-storage.d.ts +16 -1
- package/dist/types/session/session-manager.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +6 -15
- package/dist/types/tools/find.d.ts +0 -9
- package/dist/types/tools/index.d.ts +1 -1
- package/dist/types/tools/path-utils.d.ts +16 -0
- package/dist/types/tools/sqlite-reader.d.ts +25 -8
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/clipboard.d.ts +4 -0
- package/dist/types/utils/jj.d.ts +49 -0
- package/dist/types/web/kagi.d.ts +76 -0
- package/dist/types/web/search/providers/exa.d.ts +7 -1
- package/dist/types/web/search/providers/kagi.d.ts +1 -0
- package/package.json +9 -9
- package/src/async/job-manager.ts +3 -3
- package/src/cli/args.ts +6 -2
- package/src/cli/claude-trace-cli.ts +783 -0
- package/src/cli/session-picker.ts +36 -10
- package/src/cli/update-cli.ts +35 -2
- package/src/commands/launch.ts +3 -0
- package/src/config/keybindings.ts +14 -1
- package/src/config/model-registry.ts +18 -7
- package/src/config/settings-schema.ts +2 -2
- package/src/config/settings.ts +23 -0
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
- package/src/discovery/claude-plugins.ts +7 -9
- package/src/edit/index.ts +10 -0
- package/src/edit/streaming.ts +65 -0
- package/src/eval/__tests__/agent-bridge.test.ts +58 -4
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/js/shared/prelude.txt +20 -17
- package/src/eval/js/tool-bridge.ts +5 -0
- package/src/eval/py/prelude.py +23 -15
- package/src/export/ttsr.ts +18 -1
- package/src/extensibility/custom-commands/bundled/review/index.ts +74 -45
- package/src/extensibility/plugins/legacy-pi-compat.ts +115 -131
- package/src/extensibility/skills.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +12 -11
- package/src/main.ts +92 -24
- package/src/mcp/transports/stdio.ts +55 -22
- package/src/modes/acp/acp-event-mapper.ts +54 -4
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/hook-selector.ts +89 -31
- package/src/modes/components/oauth-selector.ts +12 -6
- package/src/modes/components/session-selector.ts +179 -24
- package/src/modes/components/tool-execution.ts +16 -3
- package/src/modes/controllers/command-controller.ts +2 -11
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +19 -1
- package/src/modes/controllers/selector-controller.ts +61 -21
- package/src/modes/interactive-mode.ts +125 -15
- package/src/modes/types.ts +5 -2
- package/src/prompts/agents/reviewer.md +2 -2
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/empty-stop-retry.md +6 -0
- package/src/prompts/system/orchestrate-notice.md +5 -3
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +5 -5
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/irc.md +6 -6
- package/src/prompts/tools/search-tool-bm25.md +9 -2
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -1
- package/src/registry/agent-registry.ts +1 -1
- package/src/sdk.ts +85 -31
- package/src/session/agent-session.ts +209 -54
- package/src/session/history-storage.ts +56 -12
- package/src/session/session-manager.ts +34 -0
- package/src/task/output-manager.ts +40 -48
- package/src/task/render.ts +3 -8
- package/src/tools/browser/tab-worker.ts +8 -5
- package/src/tools/find.ts +5 -29
- package/src/tools/index.ts +1 -1
- package/src/tools/path-utils.ts +144 -1
- package/src/tools/read.ts +47 -0
- package/src/tools/search-tool-bm25.ts +7 -1
- package/src/tools/search.ts +2 -27
- package/src/tools/sqlite-reader.ts +92 -9
- package/src/tools/write.ts +6 -0
- package/src/utils/clipboard.ts +38 -1
- package/src/utils/git.ts +19 -23
- package/src/utils/jj.ts +225 -0
- package/src/utils/open.ts +37 -2
- package/src/web/kagi.ts +168 -49
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/exa.ts +20 -86
- package/src/web/search/providers/kagi.ts +4 -0
|
@@ -32,12 +32,26 @@ import {
|
|
|
32
32
|
TUI,
|
|
33
33
|
visibleWidth,
|
|
34
34
|
} from "@oh-my-pi/pi-tui";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
APP_NAME,
|
|
37
|
+
adjustHsv,
|
|
38
|
+
formatNumber,
|
|
39
|
+
getProjectDir,
|
|
40
|
+
hsvToRgb,
|
|
41
|
+
isEnoent,
|
|
42
|
+
logger,
|
|
43
|
+
postmortem,
|
|
44
|
+
prompt,
|
|
45
|
+
setProjectDir,
|
|
46
|
+
} from "@oh-my-pi/pi-utils";
|
|
36
47
|
import chalk from "chalk";
|
|
48
|
+
import { reset as resetCapabilities } from "../capability";
|
|
37
49
|
import { KeybindingsManager } from "../config/keybindings";
|
|
38
50
|
import { MODEL_ROLES, type ModelRole } from "../config/model-registry";
|
|
39
51
|
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
52
|
+
import { clearClaudePluginRootsCache } from "../discovery/helpers";
|
|
40
53
|
import type {
|
|
54
|
+
ContextUsage,
|
|
41
55
|
ExtensionUIContext,
|
|
42
56
|
ExtensionUIDialogOptions,
|
|
43
57
|
ExtensionUISelectItem,
|
|
@@ -123,6 +137,7 @@ import type {
|
|
|
123
137
|
CompactionQueuedMessage,
|
|
124
138
|
InteractiveModeContext,
|
|
125
139
|
InteractiveModeInitOptions,
|
|
140
|
+
InteractiveSelectorDialogOptions,
|
|
126
141
|
SubmittedUserInput,
|
|
127
142
|
TodoItem,
|
|
128
143
|
TodoPhase,
|
|
@@ -198,6 +213,8 @@ function formatHudNoteMarker(count: number): string {
|
|
|
198
213
|
type GoalSubcommand = "set" | "show" | "pause" | "resume" | "drop" | "budget";
|
|
199
214
|
|
|
200
215
|
const GOAL_SUBCOMMANDS = new Set<GoalSubcommand>(["set", "show", "pause", "resume", "drop", "budget"]);
|
|
216
|
+
const PLAN_KEEP_CONTEXT_OPTION_INDEX = 2;
|
|
217
|
+
const PLAN_KEEP_CONTEXT_DISABLE_THRESHOLD_PERCENT = 95;
|
|
201
218
|
|
|
202
219
|
function parseGoalSubcommand(args: string): { sub: GoalSubcommand | undefined; rest: string } {
|
|
203
220
|
const trimmed = args.trim();
|
|
@@ -211,6 +228,10 @@ function parseGoalSubcommand(args: string): { sub: GoalSubcommand | undefined; r
|
|
|
211
228
|
return { sub: undefined, rest: trimmed };
|
|
212
229
|
}
|
|
213
230
|
|
|
231
|
+
function formatContextTokenCount(value: number): string {
|
|
232
|
+
return formatNumber(Math.max(0, Math.round(value))).toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
|
|
214
235
|
/** Options for creating an InteractiveMode instance (for future API use) */
|
|
215
236
|
export interface InteractiveModeOptions {
|
|
216
237
|
/** Providers that were migrated during startup */
|
|
@@ -260,6 +281,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
260
281
|
loopPrompt: string | undefined = undefined;
|
|
261
282
|
loopLimit: LoopLimitRuntime | undefined = undefined;
|
|
262
283
|
#loopAutoSubmitTimer: NodeJS.Timeout | undefined;
|
|
284
|
+
#todoAutoClearTimer: NodeJS.Timeout | undefined;
|
|
263
285
|
todoPhases: TodoPhase[] = [];
|
|
264
286
|
hideThinkingBlock = false;
|
|
265
287
|
pendingImages: ImageContent[] = [];
|
|
@@ -392,6 +414,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
392
414
|
try {
|
|
393
415
|
this.historyStorage = HistoryStorage.open();
|
|
394
416
|
this.editor.setHistoryStorage(this.historyStorage);
|
|
417
|
+
this.historyStorage.setSessionResolver(() => this.sessionManager.getSessionId());
|
|
395
418
|
} catch (error) {
|
|
396
419
|
logger.warn("History storage unavailable", { error: String(error) });
|
|
397
420
|
}
|
|
@@ -555,6 +578,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
555
578
|
// subagent is doing the work for a still-pending todo) updates as
|
|
556
579
|
// subagents start, finish, or fail.
|
|
557
580
|
this.#reconcileTodosWithSubagents();
|
|
581
|
+
this.#syncTodoAutoClearTimer();
|
|
558
582
|
this.#renderTodoList();
|
|
559
583
|
this.ui.requestRender();
|
|
560
584
|
});
|
|
@@ -641,6 +665,31 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
641
665
|
this.session.setSlashCommands(fileCommands);
|
|
642
666
|
}
|
|
643
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Re-point the process and every cwd-derived cache at `newCwd` after the
|
|
670
|
+
* active session's working directory changed (`/move` relocation or resuming
|
|
671
|
+
* a session from another project). The SessionManager's cwd MUST already
|
|
672
|
+
* reflect `newCwd` before this is called.
|
|
673
|
+
*/
|
|
674
|
+
async applyCwdChange(newCwd: string): Promise<void> {
|
|
675
|
+
setProjectDir(newCwd);
|
|
676
|
+
// Re-scope project settings (`.claude/settings.yml` etc.) to the new
|
|
677
|
+
// directory in place so the active session and every settings reader pick
|
|
678
|
+
// up the destination project's configuration.
|
|
679
|
+
if (isSettingsInitialized()) {
|
|
680
|
+
await settings.reloadForCwd(newCwd);
|
|
681
|
+
}
|
|
682
|
+
// Re-warm plugin roots, capabilities, slash commands, and the ssh tool so
|
|
683
|
+
// the next prompt sees everything scoped to the new project directory.
|
|
684
|
+
clearClaudePluginRootsCache();
|
|
685
|
+
resetCapabilities();
|
|
686
|
+
await this.refreshSlashCommandState(newCwd);
|
|
687
|
+
await this.session.refreshSshTool({ activateIfAvailable: true });
|
|
688
|
+
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
689
|
+
this.statusLine.invalidate();
|
|
690
|
+
this.updateEditorTopBorder();
|
|
691
|
+
}
|
|
692
|
+
|
|
644
693
|
async getUserInput(): Promise<SubmittedUserInput> {
|
|
645
694
|
if (this.session.getGoalModeState()?.mode === "exiting") {
|
|
646
695
|
await this.#exitGoalMode({ reason: "completed", silent: true });
|
|
@@ -1043,6 +1092,47 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1043
1092
|
this.session.setTodoPhases(next);
|
|
1044
1093
|
}
|
|
1045
1094
|
|
|
1095
|
+
#cancelTodoAutoClearTimer(): void {
|
|
1096
|
+
if (!this.#todoAutoClearTimer) return;
|
|
1097
|
+
clearTimeout(this.#todoAutoClearTimer);
|
|
1098
|
+
this.#todoAutoClearTimer = undefined;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
#isClosedTodo(task: TodoItem): boolean {
|
|
1102
|
+
return task.status === "completed" || task.status === "abandoned";
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
#hasClosedTodos(phases: TodoPhase[]): boolean {
|
|
1106
|
+
return phases.some(phase => phase.tasks.some(task => this.#isClosedTodo(task)));
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
#removeClosedTodos(phases: TodoPhase[]): TodoPhase[] {
|
|
1110
|
+
const next: TodoPhase[] = [];
|
|
1111
|
+
for (const phase of phases) {
|
|
1112
|
+
const tasks = phase.tasks.filter(task => !this.#isClosedTodo(task));
|
|
1113
|
+
if (tasks.length > 0) next.push({ name: phase.name, tasks });
|
|
1114
|
+
}
|
|
1115
|
+
return next;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
#syncTodoAutoClearTimer(): void {
|
|
1119
|
+
this.#cancelTodoAutoClearTimer();
|
|
1120
|
+
const delaySeconds = this.settings.get("tasks.todoClearDelay");
|
|
1121
|
+
if (!Number.isFinite(delaySeconds) || delaySeconds < 0 || !this.#hasClosedTodos(this.todoPhases)) return;
|
|
1122
|
+
if (delaySeconds === 0) {
|
|
1123
|
+
this.todoPhases = this.#removeClosedTodos(this.todoPhases);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
this.#todoAutoClearTimer = setTimeout(() => {
|
|
1128
|
+
this.#todoAutoClearTimer = undefined;
|
|
1129
|
+
this.todoPhases = this.#removeClosedTodos(this.todoPhases);
|
|
1130
|
+
this.#renderTodoList();
|
|
1131
|
+
this.ui.requestRender();
|
|
1132
|
+
}, delaySeconds * 1000);
|
|
1133
|
+
this.#todoAutoClearTimer.unref?.();
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1046
1136
|
#getActivePhase(phases: TodoPhase[]): TodoPhase | undefined {
|
|
1047
1137
|
const nonEmpty = phases.filter(phase => phase.tasks.length > 0);
|
|
1048
1138
|
const active = nonEmpty.find(phase =>
|
|
@@ -1098,6 +1188,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1098
1188
|
|
|
1099
1189
|
async #loadTodoList(): Promise<void> {
|
|
1100
1190
|
this.todoPhases = this.session.getTodoPhases();
|
|
1191
|
+
this.#syncTodoAutoClearTimer();
|
|
1101
1192
|
this.#renderTodoList();
|
|
1102
1193
|
}
|
|
1103
1194
|
|
|
@@ -1569,6 +1660,28 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1569
1660
|
return `up/down navigate enter select ${externalEditorKey.toLowerCase()} open in editor esc cancel`;
|
|
1570
1661
|
}
|
|
1571
1662
|
|
|
1663
|
+
#getPlanApprovalContextUsage(): ContextUsage | undefined {
|
|
1664
|
+
const executionModel = this.#planModePreviousModelState?.model ?? this.session.model;
|
|
1665
|
+
const contextWindow = executionModel?.contextWindow;
|
|
1666
|
+
if (typeof contextWindow === "number") {
|
|
1667
|
+
return this.session.getContextUsage({ contextWindow });
|
|
1668
|
+
}
|
|
1669
|
+
return this.session.getContextUsage();
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
#formatKeepContextLabel(contextUsage: ContextUsage | undefined): string {
|
|
1673
|
+
if (contextUsage?.tokens == null) {
|
|
1674
|
+
return "Approve and keep context";
|
|
1675
|
+
}
|
|
1676
|
+
const tokens = formatContextTokenCount(contextUsage.tokens);
|
|
1677
|
+
const contextWindow = formatContextTokenCount(contextUsage.contextWindow);
|
|
1678
|
+
return `Approve and keep context (~${tokens} / ${contextWindow})`;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
#isKeepContextDisabled(contextUsage: ContextUsage | undefined): boolean {
|
|
1682
|
+
return contextUsage?.percent != null && contextUsage.percent > PLAN_KEEP_CONTEXT_DISABLE_THRESHOLD_PERCENT;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1572
1685
|
async #openPlanInExternalEditor(planFilePath: string): Promise<void> {
|
|
1573
1686
|
const editorCmd = getEditorCommand();
|
|
1574
1687
|
if (!editorCmd) {
|
|
@@ -2035,11 +2148,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2035
2148
|
}
|
|
2036
2149
|
|
|
2037
2150
|
this.#renderPlanPreview(planContent, { append: true });
|
|
2038
|
-
const contextUsage = this
|
|
2039
|
-
const keepContextLabel =
|
|
2040
|
-
|
|
2041
|
-
? `Approve and keep context (${contextUsage.percent.toFixed(1)}%)`
|
|
2042
|
-
: "Approve and keep context";
|
|
2151
|
+
const contextUsage = this.#getPlanApprovalContextUsage();
|
|
2152
|
+
const keepContextLabel = this.#formatKeepContextLabel(contextUsage);
|
|
2153
|
+
const keepContextDisabled = this.#isKeepContextDisabled(contextUsage);
|
|
2043
2154
|
|
|
2044
2155
|
// Model-tier slider: let the operator pick which configured role model
|
|
2045
2156
|
// (smol/default/slow/…) executes the approved plan. The slider always starts
|
|
@@ -2074,6 +2185,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2074
2185
|
{
|
|
2075
2186
|
helpText,
|
|
2076
2187
|
onExternalEditor: () => void this.#openPlanInExternalEditor(planFilePath),
|
|
2188
|
+
disabledIndices: keepContextDisabled ? [PLAN_KEEP_CONTEXT_OPTION_INDEX] : undefined,
|
|
2077
2189
|
},
|
|
2078
2190
|
{ slider },
|
|
2079
2191
|
);
|
|
@@ -2176,6 +2288,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2176
2288
|
this.loadingAnimation = undefined;
|
|
2177
2289
|
}
|
|
2178
2290
|
this.#cleanupMicAnimation();
|
|
2291
|
+
this.#cancelTodoAutoClearTimer();
|
|
2179
2292
|
this.#cancelGoalContinuation();
|
|
2180
2293
|
if (this.#sttController) {
|
|
2181
2294
|
this.#sttController.dispose();
|
|
@@ -2231,14 +2344,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2231
2344
|
// Emit shutdown event to hooks
|
|
2232
2345
|
await this.session.dispose();
|
|
2233
2346
|
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
// Wait for any pending renders to complete
|
|
2239
|
-
// requestRender() uses process.nextTick(), so we wait one tick
|
|
2240
|
-
await new Promise(resolve => process.nextTick(resolve));
|
|
2241
|
-
|
|
2347
|
+
// Do not force a final render during teardown: disposed session/UI state can
|
|
2348
|
+
// collapse to an empty frame, clearing the viewport and leaving the parent
|
|
2349
|
+
// shell prompt at row 0. Stop from the last committed frame so the terminal
|
|
2350
|
+
// hands Bash the cursor immediately after visible OMP content.
|
|
2242
2351
|
// Drain any in-flight Kitty key release events before stopping.
|
|
2243
2352
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
2244
2353
|
await this.ui.terminal.drainInput(1000);
|
|
@@ -2858,6 +2967,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2858
2967
|
},
|
|
2859
2968
|
];
|
|
2860
2969
|
}
|
|
2970
|
+
this.#syncTodoAutoClearTimer();
|
|
2861
2971
|
this.#renderTodoList();
|
|
2862
2972
|
this.ui.requestRender();
|
|
2863
2973
|
}
|
|
@@ -2898,7 +3008,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2898
3008
|
showHookSelector(
|
|
2899
3009
|
title: string,
|
|
2900
3010
|
options: ExtensionUISelectItem[],
|
|
2901
|
-
dialogOptions?:
|
|
3011
|
+
dialogOptions?: InteractiveSelectorDialogOptions,
|
|
2902
3012
|
extra?: { slider?: HookSelectorSlider },
|
|
2903
3013
|
): Promise<string | undefined> {
|
|
2904
3014
|
return this.#extensionUiController.showHookSelector(title, options, dialogOptions, extra);
|
package/src/modes/types.ts
CHANGED
|
@@ -25,7 +25,7 @@ import type { CustomEditor } from "./components/custom-editor";
|
|
|
25
25
|
import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
26
26
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
27
27
|
import type { HookInputComponent } from "./components/hook-input";
|
|
28
|
-
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
28
|
+
import type { HookSelectorComponent, HookSelectorOptions } from "./components/hook-selector";
|
|
29
29
|
import type { StatusLineComponent } from "./components/status-line";
|
|
30
30
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
31
31
|
import type { LoopLimitRuntime } from "./loop-limit";
|
|
@@ -64,6 +64,8 @@ export interface InteractiveModeInitOptions {
|
|
|
64
64
|
suppressWelcomeIntro?: boolean;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
export type InteractiveSelectorDialogOptions = ExtensionUIDialogOptions & Pick<HookSelectorOptions, "disabledIndices">;
|
|
68
|
+
|
|
67
69
|
export interface InteractiveModeContext {
|
|
68
70
|
// UI access
|
|
69
71
|
ui: TUI;
|
|
@@ -242,6 +244,7 @@ export interface InteractiveModeContext {
|
|
|
242
244
|
): Promise<CompactionOutcome>;
|
|
243
245
|
openInBrowser(urlOrPath: string): void;
|
|
244
246
|
refreshSlashCommandState(cwd?: string): Promise<void>;
|
|
247
|
+
applyCwdChange(newCwd: string): Promise<void>;
|
|
245
248
|
|
|
246
249
|
// Selector handling
|
|
247
250
|
showSettingsSelector(): void;
|
|
@@ -299,7 +302,7 @@ export interface InteractiveModeContext {
|
|
|
299
302
|
showHookSelector(
|
|
300
303
|
title: string,
|
|
301
304
|
options: ExtensionUISelectItem[],
|
|
302
|
-
dialogOptions?:
|
|
305
|
+
dialogOptions?: InteractiveSelectorDialogOptions,
|
|
303
306
|
): Promise<string | undefined>;
|
|
304
307
|
hideHookSelector(): void;
|
|
305
308
|
showHookInput(title: string, placeholder?: string): Promise<string | undefined>;
|
|
@@ -59,12 +59,12 @@ output:
|
|
|
59
59
|
Identify bugs the author would want fixed before merge.
|
|
60
60
|
|
|
61
61
|
<procedure>
|
|
62
|
-
1. Run `git diff`
|
|
62
|
+
1. Run `git diff`, `jj diff --git`, or `gh pr diff <number>` to view patch
|
|
63
63
|
2. Read modified files for full context
|
|
64
64
|
3. Call `report_finding` per issue
|
|
65
65
|
4. Call `yield` with verdict
|
|
66
66
|
|
|
67
|
-
Bash is read-only: `git diff`, `git log`, `git show`, `gh pr diff`. You NEVER make file edits or trigger builds.
|
|
67
|
+
Bash is read-only: `git diff`, `git log`, `git show`, `jj diff --git`, `gh pr diff`. You NEVER make file edits or trigger builds.
|
|
68
68
|
</procedure>
|
|
69
69
|
|
|
70
70
|
<criteria>
|
|
@@ -36,7 +36,7 @@ Group files by locality, e.g.:
|
|
|
36
36
|
|
|
37
37
|
Reviewer MUST:
|
|
38
38
|
1. Focus ONLY on assigned files
|
|
39
|
-
2. {{#if skipDiff}}
|
|
39
|
+
2. {{#if skipDiff}}{{diffInstruction}}{{else}}MUST use diff hunks below (NEVER re-run git diff){{/if}}
|
|
40
40
|
3. MAY read full file context as needed via `read`
|
|
41
41
|
4. Call `report_finding` per issue
|
|
42
42
|
5. Call `yield` with verdict when done
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<system-reminder>
|
|
2
|
+
The previous assistant turn ended with no text, reasoning, or tool call.
|
|
3
|
+
Continue the active task from the current context. If the work is complete, reply with a concise final summary instead of an empty response.
|
|
4
|
+
|
|
5
|
+
(Empty response retry {{retryCount}}/{{maxRetries}})
|
|
6
|
+
</system-reminder>
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
The user's message above is an **orchestration request**. Execute it as the orchestrator under the contract below. This contract overrides any default tendency to yield early, narrate, or do the work yourself.
|
|
3
3
|
|
|
4
4
|
<role>
|
|
5
|
-
You decompose, dispatch, verify, and iterate.
|
|
5
|
+
You decompose, dispatch, verify, and iterate. Substantial and parallelizable work goes through `task` subagents — that is the whole point of orchestrating. But you are not forbidden from touching the tree: a trivial, self-contained edit is yours to make directly when spawning a subagent for it would cost more than the edit itself. Your tool budget is: reading for planning, `task` for dispatch, `edit`/`write` for trivial inline fixes only, verification (`bun check`, `bun test`, `lsp diagnostics`), git via `bash`, and `todo_write` for tracking.
|
|
6
6
|
</role>
|
|
7
7
|
|
|
8
8
|
<rules>
|
|
9
9
|
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.
|
|
10
10
|
2. **Enumerate the full surface before dispatching.** If the request 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.
|
|
11
|
-
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.
|
|
11
|
+
3. **Parallelize maximally; never launch a one-off task.** Every set of edits with disjoint file scope MUST ship as one `task` batch — fan the work as wide as it decomposes. A single-task batch for divisible work is a failure: split it. If you are about to dispatch exactly one subagent, stop — either there is more to run alongside it (find it and batch them) or the change is small enough to make inline yourself (do it). Serialize only when one subagent produces a contract (types, schema, shared module) the next consumes — and state the dependency when you do.
|
|
12
12
|
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.
|
|
13
13
|
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.
|
|
14
14
|
6. **Commit policy.** If the request 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.
|
|
15
15
|
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.
|
|
16
16
|
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.
|
|
17
17
|
9. **Subagents do not verify, lint, or format.** Every `task` assignment MUST instruct the subagent to skip all gates and formatters. Their job is the edit only. You — the orchestrator — run verification and formatting **once** at the end of the phase across the union of changed files. Avoids redundant runs and racing formatter passes.
|
|
18
|
+
10. **Right-size the offload — do not micro-task.** Subagents are for substantial or parallelizable chunks, not every keystroke. A trivial, self-contained mechanical edit — deleting a redundant glob, fixing one line in a config, renaming a single symbol in one file — costs less to *do* than to describe in a Goal/Constraints assignment. Make those yourself with `edit`/`write` and move on; reserve `task`/`quick_task` for work large enough to justify the dispatch overhead. Wrapping a one-line change in a full subagent with scaffolding is pure waste.
|
|
18
19
|
</rules>
|
|
19
20
|
|
|
20
21
|
<workflow>
|
|
@@ -28,7 +29,8 @@ You decompose, dispatch, verify, and iterate. You do **not** edit code. Every fi
|
|
|
28
29
|
</workflow>
|
|
29
30
|
|
|
30
31
|
<anti-patterns>
|
|
31
|
-
-
|
|
32
|
+
- Doing substantial or parallelizable work yourself instead of fanning it out to subagents.
|
|
33
|
+
- Wrapping a single trivial edit (e.g. removing one redundant config line) in a `task`/`quick_task` with full Goal/Constraints scaffolding — just make the edit inline.
|
|
32
34
|
- Yielding after phase 1 with "ready to continue?".
|
|
33
35
|
- Dispatching one subagent at a time when five could run in parallel.
|
|
34
36
|
- Skipping `bun check` between phases because "the change looked safe".
|
|
@@ -14,8 +14,8 @@ Worth it when the task benefits from decomposition + parallel coverage, or from
|
|
|
14
14
|
State persists across cells, so scout in one cell and fan out in the next. Every cell has:
|
|
15
15
|
|
|
16
16
|
- `agent(prompt, *, agent_type="task", model=None, context=None, label=None, schema=None)` — run ONE subagent; returns its final text, or the validated object when `schema` (a JSON Schema dict) is given. With `schema` the subagent is forced to emit structured output that is validated for you — branch on the object, not on parsed prose. `agent_type` picks a discovered agent ("explore", "reviewer", "oracle", …); `context` is shared background; `label` names the artifact. Subagents are told their final text IS the return value, so they hand back raw data. `agent()` blocks until the subagent finishes; eval-spawned agents nest at most 3 deep.
|
|
17
|
-
- `parallel(thunks
|
|
18
|
-
- `pipeline(items, *stages
|
|
17
|
+
- `parallel(thunks)` — run zero-arg callables concurrently through a bounded pool, preserving input order; returns once all finish. The pool runs as wide as a `task` tool batch (the `task.maxConcurrency` setting; don't hand-tune it — fan out as wide as the work divides). A thunk that raises propagates — wrap risky work in `try/except` inside the thunk to keep partial results. In a loop, bind each closure's value with a default arg (`lambda d=d: …`) or every thunk captures the last one.
|
|
18
|
+
- `pipeline(items, *stages)` — map items through `stages` left-to-right. There is a BARRIER between stages: ALL items clear stage N before stage N+1 begins. Each stage is a one-arg callable; stage 1 gets the original item, later stages get the previous result. Same pool width as `parallel()`.
|
|
19
19
|
- `llm(prompt, *, model="default", system=None, schema=None)` — oneshot, stateless model call (no tools, no history). Tiers: "smol", "default", "slow". Cheap classification/scoring inside a fan-out.
|
|
20
20
|
- `log(message)` — emit a progress line above the status tree. `phase(title)` — start a phase; the status lines that follow group under it.
|
|
21
21
|
- `budget` — `budget.total` (output-token ceiling, or `None` when none is set), `budget.spent()` (tokens spent this turn — main loop + eval subagents), `budget.remaining()` (`math.inf` when total is `None`), `budget.hard` (whether it's enforced). A ceiling is set by the user: `+Nk` in their message is advisory (you self-limit via `budget.remaining()`), `+Nk!` (or Goal Mode) is hard — `agent()` refuses to spawn once spent reaches it. Gate loops on `budget.total` first, since it's `None` when the user set no budget.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Run code in a persistent kernel using a list of cells.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
Each call submits one or more cells. Cells run in array order. State persists within each language across cells, tool calls, and subagents spawned with `task`; variables a parent or subagent declares are visible to the other on the same shared executor.
|
|
4
|
+
Each call submits one or more cells. Cells run in array order. State persists within each language across cells, tool calls, and subagents spawned with `task`; variables a parent or subagent declares are visible to the other on the same shared executor. Lean on this: stage helpers, loaded datasets, or live clients once, then fan out `task` subagents that call them directly — no re-importing, re-fetching, or serializing across the boundary.
|
|
5
5
|
|
|
6
6
|
Cell fields:
|
|
7
7
|
|
|
@@ -48,10 +48,10 @@ llm(prompt, model?="default", system?=None, schema?=None) → str | dict
|
|
|
48
48
|
Oneshot, stateless LLM call (no history, no tools). `model` picks a tier: "smol" (fast), "default" (this session's model), "slow" (most capable). Pass `system` for a system prompt. Pass a JSON-Schema `schema` to force structured output and get the parsed object back; otherwise returns the completion text.
|
|
49
49
|
agent(prompt, agent_type?="task", model?=None, context?=None, label?=None, schema?=None) → str | dict
|
|
50
50
|
Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back.
|
|
51
|
-
parallel(thunks
|
|
52
|
-
Run thunks (callables) through a bounded pool (
|
|
53
|
-
pipeline(items, ...stages
|
|
54
|
-
Map each item through stages left-to-right; a barrier runs between stages (every item clears stage N before stage N+1). Each stage is a one-arg callable: stage 1 gets the original item, later stages get the previous result.
|
|
51
|
+
parallel(thunks) → list
|
|
52
|
+
Run thunks (callables) through a bounded pool, preserving input order. The pool is as wide as a `task` tool batch (tracks the `task.maxConcurrency` setting), so fan out as wide as the work divides — don't pre-shrink it. Barrier: returns once all finish; a thunk that throws propagates.
|
|
53
|
+
pipeline(items, ...stages) → list
|
|
54
|
+
Map each item through stages left-to-right; a barrier runs between stages (every item clears stage N before stage N+1). Each stage is a one-arg callable: stage 1 gets the original item, later stages get the previous result. Same pool width as parallel().
|
|
55
55
|
log(message) → None
|
|
56
56
|
Emit a progress line above the status tree.
|
|
57
57
|
phase(title) → None
|
|
@@ -2,7 +2,7 @@ Finds files and directories using fast pattern matching that works with any code
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- `paths` is required and accepts an array of globs, files, or directories
|
|
5
|
-
- Pass multiple targets as **separate array elements** (`paths: ["a", "b"]`)
|
|
5
|
+
- Pass multiple targets as **separate array elements** (`paths: ["a", "b"]`).
|
|
6
6
|
- `gitignore` defaults to `true` and hides files matched by `.gitignore`. Set `gitignore: false` to find `.env*`, `*.log`, freshly-created build outputs, or anything else your repo ignores
|
|
7
7
|
- `hidden` defaults to `true`; combine with `gitignore: false` to surface dotfiles that are also gitignored
|
|
8
8
|
- `limit` is clamped to 1-200 (default 200). Narrow the pattern instead of raising the limit
|
package/src/prompts/tools/irc.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Sends short text messages to other live agents in this process and receives their prose replies.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- The main agent is addressable as `
|
|
4
|
+
- The main agent is addressable as `Main`. Subagents reuse their task id (e.g. `AuthLoader`, or `AuthLoader-2` when the name repeats).
|
|
5
5
|
- `op: "list"` returns the current set of visible peers. Use it before sending if you are not sure who is live.
|
|
6
6
|
- `op: "send"` delivers `message` to `to`. `to` may be a specific id or `"all"` to broadcast.
|
|
7
7
|
- The recipient generates the reply via an ephemeral side-channel turn that uses their current model, system prompt, and history — it does **not** wait for the recipient's main loop to be free, so it is safe to IRC an agent that is currently inside a long-running tool call.
|
|
@@ -10,7 +10,7 @@ Sends short text messages to other live agents in this process and receives thei
|
|
|
10
10
|
|
|
11
11
|
<when_to_use>
|
|
12
12
|
You SHOULD reach for `irc` proactively when continuing alone is wasteful or wrong. When in doubt, prefer messaging.
|
|
13
|
-
- **Unexpected state.** You hit something the original task did not describe — a missing file, a config that contradicts the assignment, an API behaving differently than you were told, a tool failing in a way that suggests the spec is wrong. DM `
|
|
13
|
+
- **Unexpected state.** You hit something the original task did not describe — a missing file, a config that contradicts the assignment, an API behaving differently than you were told, a tool failing in a way that suggests the spec is wrong. DM `Main` (or the spawning agent) for guidance instead of guessing.
|
|
14
14
|
- **Blocked by another agent.** A peer holds the file/branch/resource you need, has already started the change you are about to make, or owns a decision you depend on. DM that peer (or broadcast to discover who) before duplicating or stepping on work.
|
|
15
15
|
- **Decision points outside your scope.** A genuine fork in the road that the assignment did not pre-decide (e.g. which of two viable APIs to use, whether to refactor adjacent code). Ask the requester rather than picking unilaterally.
|
|
16
16
|
- **Coordination opportunities.** You realize a peer's in-flight work would benefit from yours, or vice-versa.
|
|
@@ -25,7 +25,7 @@ These rules apply to both sending and replying.
|
|
|
25
25
|
- **Use IRC, not terminal tools, to learn about peers.** Do not `grep` artifacts, read other sessions' JSONL files, or shell-poke around to figure out what another agent is doing. DM them — they have the live answer and you do not.
|
|
26
26
|
- **One round-trip is enough.** Replies arrive synchronously when the recipient is reachable. Do not follow up with "did you get my message?" — they did. If `delivered` is empty or the result was `failed`, the peer is unavailable; move on or report the blocker, do not retry in a loop.
|
|
27
27
|
- **Stay terse.** A DM is a chat message, not a memo. One question per send when you can. Share file paths and artifacts via `local://` / `memory://` / `artifact://` URLs instead of pasting blobs.
|
|
28
|
-
- **Address peers by id.** Use the exact id from `op: "list"` (e.g. `
|
|
28
|
+
- **Address peers by id.** Use the exact id from `op: "list"` (e.g. `AuthLoader`, `Main`). Do not invent friendly names.
|
|
29
29
|
- **Do not IRC for things a tool would answer.** If a `read`, `grep`, or build command would resolve the question, do that first.
|
|
30
30
|
- **When you receive an IRC message, answer it before continuing.** The recipient injects the question + your auto-reply into your history; address it directly, do not repeat it back to the user.
|
|
31
31
|
</etiquette>
|
|
@@ -39,11 +39,11 @@ These rules apply to both sending and replying.
|
|
|
39
39
|
# List peers
|
|
40
40
|
`{"op": "list"}`
|
|
41
41
|
# Direct message to the main agent (waits for prose reply)
|
|
42
|
-
`{"op": "send", "to": "
|
|
42
|
+
`{"op": "send", "to": "Main", "message": "Should I prefer JWT or session cookies for the auth flow?"}`
|
|
43
43
|
# Unexpected state — ask the originator
|
|
44
|
-
`{"op": "send", "to": "
|
|
44
|
+
`{"op": "send", "to": "Main", "message": "Assignment says edit src/auth/jwt.ts but the file does not exist. Is the new path src/server/auth/jwt.ts?"}`
|
|
45
45
|
# Blocked by a peer — ask them directly
|
|
46
|
-
`{"op": "send", "to": "
|
|
46
|
+
`{"op": "send", "to": "AuthLoader", "message": "Are you still touching src/server/auth.ts? I need to add a 401 path; OK to proceed or should I wait?"}`
|
|
47
47
|
# Broadcast to discover who owns something (no replies, just informs them)
|
|
48
48
|
`{"op": "send", "to": "all", "message": "About to refactor src/server/middleware/*. Anyone already in there?", "awaitReply": false}`
|
|
49
49
|
</examples>
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
Search hidden tool metadata to discover and activate tools.
|
|
2
2
|
|
|
3
3
|
Activate hidden tools (MCP and built-in) when you need a capability not in your active tool set.
|
|
4
|
-
{{#if hasDiscoverableMCPServers}}
|
|
5
|
-
|
|
4
|
+
{{#if hasDiscoverableMCPServers}}
|
|
5
|
+
Discoverable MCP servers in this session: {{#list discoverableMCPServerSummaries join=", "}}{{this}}{{/list}}.
|
|
6
|
+
{{/if}}
|
|
7
|
+
{{#if hasDiscoverableBuiltinTools}}
|
|
8
|
+
Discoverable built-in tools: {{#list discoverableBuiltinToolNames join=", "}}{{this}}{{/list}}.
|
|
9
|
+
{{/if}}
|
|
10
|
+
{{#if discoverableToolCount}}
|
|
11
|
+
Total discoverable tools available: {{discoverableToolCount}}.
|
|
12
|
+
{{/if}}
|
|
6
13
|
Input:
|
|
7
14
|
- `query` — required natural-language or keyword query
|
|
8
15
|
- `limit` — optional maximum number of tools to return and activate (default `8`)
|
|
@@ -3,7 +3,7 @@ Searches files using powerful regex matching.
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- Supports Rust regex syntax (RE2-style — no lookaround or backreferences). Use line anchors or post-filters instead of (?!…)/(?<!…)
|
|
5
5
|
- `paths` is required and accepts either one string or an array of files, directories, globs, or internal URLs
|
|
6
|
-
- For multiple targets, pass an array with one target per element
|
|
6
|
+
- For multiple targets, pass an array with one target per element: `["src", "tests"]`.
|
|
7
7
|
- Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
|
|
8
8
|
</instruction>
|
|
9
9
|
|
|
@@ -2,7 +2,7 @@ Launches subagents to parallelize workflows.
|
|
|
2
2
|
|
|
3
3
|
{{#if asyncEnabled}}
|
|
4
4
|
- Results are delivered automatically when complete.
|
|
5
|
-
- The tool result lists the assigned task ids (e.g. `
|
|
5
|
+
- The tool result lists the assigned task ids (e.g. `AuthLoader`) — those are the live agent ids.
|
|
6
6
|
{{#if ircEnabled}}
|
|
7
7
|
- Coordinate with running tasks via `irc` using those ids. `job cancel` terminates a task and **cannot carry a message** — only use it for stalled/abandoned work.
|
|
8
8
|
- If genuinely blocked on completion, wait with `job poll`; otherwise keep working.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { AgentSession } from "../session/agent-session";
|
|
10
10
|
|
|
11
|
-
export const MAIN_AGENT_ID = "
|
|
11
|
+
export const MAIN_AGENT_ID = "Main";
|
|
12
12
|
|
|
13
13
|
export type AgentStatus = "running" | "idle" | "completed" | "aborted";
|
|
14
14
|
export type AgentKind = "main" | "sub";
|