@oh-my-pi/pi-coding-agent 15.5.15 → 15.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/tiny-models-cli.d.ts +9 -0
- package/dist/types/commands/tiny-models.d.ts +22 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/model-id-affixes.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +232 -7
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
- package/dist/types/internal-urls/local-protocol.d.ts +2 -1
- package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
- package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
- package/dist/types/internal-urls/router.d.ts +8 -1
- package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
- package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
- package/dist/types/internal-urls/types.d.ts +26 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/resolve.d.ts +2 -1
- package/dist/types/memory-backend/types.d.ts +7 -1
- package/dist/types/mnemosyne/backend.d.ts +4 -0
- package/dist/types/mnemosyne/config.d.ts +29 -0
- package/dist/types/mnemosyne/index.d.ts +3 -0
- package/dist/types/mnemosyne/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -3
- package/dist/types/modes/components/hook-selector.d.ts +27 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
- package/dist/types/modes/gradient-highlight.d.ts +23 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
- package/dist/types/modes/orchestrate.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
- package/dist/types/modes/ultrathink.d.ts +3 -3
- package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +33 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/render.d.ts +5 -1
- package/dist/types/tiny/models.d.ts +185 -0
- package/dist/types/tiny/text.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +24 -0
- package/dist/types/tiny/title-protocol.d.ts +74 -0
- package/dist/types/tiny/worker.d.ts +2 -0
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/memory-edit.d.ts +40 -0
- package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
- package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
- package/dist/types/tools/memory-render.d.ts +60 -0
- package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
- package/dist/types/tools/todo-write.d.ts +8 -0
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +3 -0
- package/package.json +18 -14
- package/scripts/build-binary.ts +1 -0
- package/src/cli/tiny-models-cli.ts +127 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +8 -8
- package/src/commands/tiny-models.ts +36 -0
- package/src/config/model-equivalence.ts +43 -2
- package/src/config/model-id-affixes.ts +64 -0
- package/src/config/model-registry.ts +84 -10
- package/src/config/settings-schema.ts +205 -4
- package/src/edit/hashline/diff.ts +5 -7
- package/src/eval/__tests__/shared-executors.test.ts +36 -0
- package/src/eval/js/shared/local-module-loader.ts +13 -1
- package/src/eval/js/shared/rewrite-imports.ts +31 -26
- package/src/internal-urls/agent-protocol.ts +18 -1
- package/src/internal-urls/artifact-protocol.ts +19 -1
- package/src/internal-urls/docs-index.generated.ts +3 -1
- package/src/internal-urls/local-protocol.ts +14 -1
- package/src/internal-urls/memory-protocol.ts +6 -1
- package/src/internal-urls/omp-protocol.ts +5 -1
- package/src/internal-urls/router.ts +20 -1
- package/src/internal-urls/rule-protocol.ts +8 -1
- package/src/internal-urls/skill-protocol.ts +8 -1
- package/src/internal-urls/types.ts +27 -0
- package/src/lsp/render.ts +1 -1
- package/src/mcp/oauth-flow.ts +2 -2
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/resolve.ts +4 -1
- package/src/memory-backend/types.ts +8 -1
- package/src/mnemosyne/backend.ts +374 -0
- package/src/mnemosyne/config.ts +160 -0
- package/src/mnemosyne/index.ts +3 -0
- package/src/mnemosyne/state.ts +548 -0
- package/src/modes/acp/acp-agent.ts +11 -6
- package/src/modes/components/agent-dashboard.ts +4 -4
- package/src/modes/components/custom-editor.ts +3 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/extensions/extension-list.ts +3 -2
- package/src/modes/components/footer.ts +5 -6
- package/src/modes/components/history-search.ts +3 -3
- package/src/modes/components/hook-selector.ts +94 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/mcp-add-wizard.ts +3 -3
- package/src/modes/components/model-selector.ts +5 -4
- package/src/modes/components/oauth-selector.ts +3 -3
- package/src/modes/components/session-observer-overlay.ts +19 -13
- package/src/modes/components/session-selector.ts +3 -3
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/context-thresholds.ts +11 -0
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +12 -0
- package/src/modes/components/tool-execution.ts +67 -3
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +3 -3
- package/src/modes/components/welcome.ts +55 -1
- package/src/modes/controllers/command-controller.ts +16 -1
- package/src/modes/controllers/extension-ui-controller.ts +3 -1
- package/src/modes/controllers/input-controller.ts +57 -0
- package/src/modes/gradient-highlight.ts +70 -0
- package/src/modes/interactive-mode.ts +58 -109
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/orchestrate.ts +36 -0
- package/src/modes/prompt-action-autocomplete.ts +12 -0
- package/src/modes/ultrathink.ts +9 -53
- package/src/modes/utils/keybinding-matchers.ts +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
- package/src/prompts/system/system-prompt.md +2 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/task.md +4 -7
- package/src/sdk.ts +8 -6
- package/src/session/agent-session.ts +128 -44
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +8 -0
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +69 -26
- package/src/tiny/models.ts +217 -0
- package/src/tiny/text.ts +19 -0
- package/src/tiny/title-client.ts +340 -0
- package/src/tiny/title-protocol.ts +51 -0
- package/src/tiny/worker.ts +523 -0
- package/src/tools/bash.ts +58 -16
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/index.ts +17 -11
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +100 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +185 -0
- package/src/tools/memory-retain.ts +91 -0
- package/src/tools/renderers.ts +4 -0
- package/src/tools/todo-write.ts +128 -29
- package/src/tools/tool-result.ts +8 -0
- package/src/utils/title-generator.ts +115 -13
- package/src/tools/hindsight-recall.ts +0 -69
- package/src/tools/hindsight-reflect.ts +0 -58
- package/src/tools/hindsight-retain.ts +0 -57
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { APP_NAME, adjustHsv, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@oh-my-pi/pi-utils";
|
|
36
36
|
import chalk from "chalk";
|
|
37
37
|
import { KeybindingsManager } from "../config/keybindings";
|
|
38
|
+
import { MODEL_ROLES, type ModelRole } from "../config/model-registry";
|
|
38
39
|
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
39
40
|
import type {
|
|
40
41
|
ExtensionUIContext,
|
|
@@ -57,7 +58,7 @@ import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" wit
|
|
|
57
58
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
58
59
|
type: "text",
|
|
59
60
|
};
|
|
60
|
-
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
61
|
+
import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../session/agent-session";
|
|
61
62
|
import { HistoryStorage } from "../session/history-storage";
|
|
62
63
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
63
64
|
import { getRecentSessions } from "../session/session-manager";
|
|
@@ -80,7 +81,7 @@ import { DynamicBorder } from "./components/dynamic-border";
|
|
|
80
81
|
import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
81
82
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
82
83
|
import type { HookInputComponent } from "./components/hook-input";
|
|
83
|
-
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
84
|
+
import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector";
|
|
84
85
|
import { StatusLineComponent } from "./components/status-line";
|
|
85
86
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
86
87
|
import { WelcomeComponent, type LspServerInfo as WelcomeLspServerInfo } from "./components/welcome";
|
|
@@ -324,8 +325,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
324
325
|
#eventBus?: EventBus;
|
|
325
326
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
326
327
|
#welcomeComponent?: WelcomeComponent;
|
|
327
|
-
#todoClosingTimeout?: NodeJS.Timeout;
|
|
328
|
-
#todoClosingState: "idle" | "playing" | "done" = "idle";
|
|
329
328
|
|
|
330
329
|
constructor(
|
|
331
330
|
session: AgentSession,
|
|
@@ -1035,28 +1034,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1035
1034
|
#renderTodoList(): void {
|
|
1036
1035
|
this.todoContainer.clear();
|
|
1037
1036
|
const phases = this.todoPhases.filter(phase => phase.tasks.length > 0);
|
|
1038
|
-
if (phases.length === 0)
|
|
1039
|
-
this.#stopTodoClosingAnimation();
|
|
1040
|
-
this.#todoClosingState = "idle";
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// When every visible task is completed or abandoned, fold the panel
|
|
1045
|
-
// away with a brief celebratory animation (see
|
|
1046
|
-
// #startTodoClosingAnimation). State machine guards against replaying
|
|
1047
|
-
// on every re-render once the animation has finished.
|
|
1048
|
-
const allClosed = phases.every(phase =>
|
|
1049
|
-
phase.tasks.every(t => t.status === "completed" || t.status === "abandoned"),
|
|
1050
|
-
);
|
|
1051
|
-
if (allClosed) {
|
|
1052
|
-
if (this.#todoClosingState === "done") return;
|
|
1053
|
-
if (this.#todoClosingState === "idle") this.#startTodoClosingAnimation(phases);
|
|
1054
|
-
return;
|
|
1055
|
-
}
|
|
1056
|
-
// Any open task here means the close animation is no longer applicable.
|
|
1057
|
-
this.#stopTodoClosingAnimation();
|
|
1058
|
-
this.#todoClosingState = "idle";
|
|
1059
|
-
|
|
1037
|
+
if (phases.length === 0) return;
|
|
1060
1038
|
const indent = " ";
|
|
1061
1039
|
const hook = theme.tree.hook;
|
|
1062
1040
|
const lines = ["", indent + theme.bold(theme.fg("accent", "Todos"))];
|
|
@@ -1098,87 +1076,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1098
1076
|
this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
1099
1077
|
}
|
|
1100
1078
|
|
|
1101
|
-
/**
|
|
1102
|
-
* Play a short "all done" close animation: a celebratory bright frame,
|
|
1103
|
-
* a brief dim transition, then a row-by-row vertical collapse until the
|
|
1104
|
-
* panel is empty. Triggered from #renderTodoList exactly once per
|
|
1105
|
-
* open-to-all-closed transition; #todoClosingState gates re-entry.
|
|
1106
|
-
*
|
|
1107
|
-
* While playing, the animator owns the panel container; #renderTodoList
|
|
1108
|
-
* returns early. Subsequent renders with state === "done" keep the
|
|
1109
|
-
* panel hidden until a fresh open task flips state back to "idle".
|
|
1110
|
-
*/
|
|
1111
|
-
#startTodoClosingAnimation(phases: TodoPhase[]): void {
|
|
1112
|
-
this.#stopTodoClosingAnimation();
|
|
1113
|
-
this.#todoClosingState = "playing";
|
|
1114
|
-
|
|
1115
|
-
const indent = " ";
|
|
1116
|
-
const hook = theme.tree.hook;
|
|
1117
|
-
const snapshot: string[] = ["", `${indent}Todos ${theme.status.success}`];
|
|
1118
|
-
for (let i = 0; i < phases.length; i++) {
|
|
1119
|
-
const phase = phases[i];
|
|
1120
|
-
snapshot.push(`${indent}${hook} ${formatPhaseDisplayName(phase.name, i + 1)}`);
|
|
1121
|
-
for (let j = 0; j < phase.tasks.length; j++) {
|
|
1122
|
-
const task = phase.tasks[j];
|
|
1123
|
-
const mark = task.status === "abandoned" ? theme.status.aborted : theme.status.success;
|
|
1124
|
-
const prefix = `${indent}${j === 0 ? hook : " "} `;
|
|
1125
|
-
snapshot.push(`${prefix}${mark} ${task.content}`);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// Frame schedule (tint, drop-from-bottom, hold-ms). Frame 0 holds long
|
|
1130
|
-
// enough for the user to actually read the final checkmarks before the
|
|
1131
|
-
// fade starts; later frames fade and progressively drop rows from the
|
|
1132
|
-
// bottom for the collapse effect. Total runtime ≈ 1.4s.
|
|
1133
|
-
const frames = [
|
|
1134
|
-
{ tint: "success" as const, drop: 0, holdMs: 900 },
|
|
1135
|
-
{ tint: "success" as const, drop: 0, holdMs: 150 },
|
|
1136
|
-
{ tint: "muted" as const, drop: 1, holdMs: 90 },
|
|
1137
|
-
{ tint: "muted" as const, drop: 2, holdMs: 90 },
|
|
1138
|
-
{ tint: "dim" as const, drop: 3, holdMs: 80 },
|
|
1139
|
-
{ tint: "dim" as const, drop: 4, holdMs: 80 },
|
|
1140
|
-
];
|
|
1141
|
-
|
|
1142
|
-
let frameIdx = 0;
|
|
1143
|
-
const tick = (): void => {
|
|
1144
|
-
if (this.#todoClosingState !== "playing") return;
|
|
1145
|
-
if (frameIdx >= frames.length) {
|
|
1146
|
-
this.todoContainer.clear();
|
|
1147
|
-
this.#stopTodoClosingAnimation();
|
|
1148
|
-
this.#todoClosingState = "done";
|
|
1149
|
-
this.ui.requestRender();
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
const { tint, drop, holdMs } = frames[frameIdx];
|
|
1153
|
-
const visibleCount = Math.max(0, snapshot.length - drop);
|
|
1154
|
-
this.todoContainer.clear();
|
|
1155
|
-
if (visibleCount > 0) {
|
|
1156
|
-
const visible = snapshot.slice(0, visibleCount);
|
|
1157
|
-
const painted = visible.map((line, idx) => {
|
|
1158
|
-
if (idx === 1) {
|
|
1159
|
-
// Header row gets a bold flourish on the opening tick.
|
|
1160
|
-
const colored = theme.fg(tint, line);
|
|
1161
|
-
return frameIdx === 0 ? theme.bold(colored) : colored;
|
|
1162
|
-
}
|
|
1163
|
-
return theme.fg(tint, line);
|
|
1164
|
-
});
|
|
1165
|
-
this.todoContainer.addChild(new Text(painted.join("\n"), 1, 0));
|
|
1166
|
-
}
|
|
1167
|
-
this.ui.requestRender();
|
|
1168
|
-
frameIdx++;
|
|
1169
|
-
this.#todoClosingTimeout = setTimeout(tick, holdMs);
|
|
1170
|
-
};
|
|
1171
|
-
|
|
1172
|
-
tick();
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
#stopTodoClosingAnimation(): void {
|
|
1176
|
-
if (this.#todoClosingTimeout) {
|
|
1177
|
-
clearTimeout(this.#todoClosingTimeout);
|
|
1178
|
-
this.#todoClosingTimeout = undefined;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
1079
|
async #loadTodoList(): Promise<void> {
|
|
1183
1080
|
this.todoPhases = this.session.getTodoPhases();
|
|
1184
1081
|
this.#renderTodoList();
|
|
@@ -1702,6 +1599,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1702
1599
|
}
|
|
1703
1600
|
}
|
|
1704
1601
|
|
|
1602
|
+
async #applyPlanExecutionModel(entry: ResolvedRoleModel | undefined): Promise<void> {
|
|
1603
|
+
if (!entry) return;
|
|
1604
|
+
try {
|
|
1605
|
+
await this.session.applyRoleModel(entry);
|
|
1606
|
+
this.statusLine.invalidate();
|
|
1607
|
+
this.updateEditorBorderColor();
|
|
1608
|
+
this.showStatus(`Continuing with ${entry.role}: ${entry.model.name || entry.model.id}`);
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
this.showWarning(
|
|
1611
|
+
`Could not switch to the ${entry.role} model: ${error instanceof Error ? error.message : String(error)}`,
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1705
1616
|
async #approvePlan(
|
|
1706
1617
|
planContent: string,
|
|
1707
1618
|
options: {
|
|
@@ -1710,6 +1621,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1710
1621
|
title: string;
|
|
1711
1622
|
preserveContext?: boolean;
|
|
1712
1623
|
compactBeforeExecute?: boolean;
|
|
1624
|
+
executionModel?: ResolvedRoleModel;
|
|
1713
1625
|
},
|
|
1714
1626
|
): Promise<void> {
|
|
1715
1627
|
await renameApprovedPlanFile({
|
|
@@ -1791,6 +1703,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1791
1703
|
return;
|
|
1792
1704
|
}
|
|
1793
1705
|
|
|
1706
|
+
await this.#applyPlanExecutionModel(options.executionModel);
|
|
1707
|
+
|
|
1794
1708
|
// Approved plans land in a fresh (or compacted) session whose first user-visible
|
|
1795
1709
|
// turn is the synthetic plan-approved prompt — that path bypasses the
|
|
1796
1710
|
// input-controller's title generation. Seed an auto-name from the plan title
|
|
@@ -2106,13 +2020,38 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2106
2020
|
contextUsage?.percent != null
|
|
2107
2021
|
? `Approve and keep context (${contextUsage.percent.toFixed(1)}%)`
|
|
2108
2022
|
: "Approve and keep context";
|
|
2023
|
+
|
|
2024
|
+
// Model-tier slider: let the operator pick which configured role model
|
|
2025
|
+
// (smol/default/slow/…) executes the approved plan. Left/right move it from
|
|
2026
|
+
// any list position. Hidden when fewer than two role models resolve — a lone
|
|
2027
|
+
// tier is no choice. `selectedTierIndex` tracks the live slider position.
|
|
2028
|
+
const cycle = this.session.getRoleModelCycle(this.session.settings.get("cycleOrder"));
|
|
2029
|
+
let selectedTierIndex = cycle?.currentIndex ?? 0;
|
|
2030
|
+
const slider: HookSelectorSlider | undefined =
|
|
2031
|
+
cycle && cycle.models.length > 1
|
|
2032
|
+
? {
|
|
2033
|
+
caption: "continue with",
|
|
2034
|
+
index: cycle.currentIndex,
|
|
2035
|
+
segments: cycle.models.map(entry => ({
|
|
2036
|
+
label: entry.role,
|
|
2037
|
+
color: MODEL_ROLES[entry.role as ModelRole]?.color,
|
|
2038
|
+
detail: entry.model.name || entry.model.id,
|
|
2039
|
+
})),
|
|
2040
|
+
onChange: index => {
|
|
2041
|
+
selectedTierIndex = index;
|
|
2042
|
+
},
|
|
2043
|
+
}
|
|
2044
|
+
: undefined;
|
|
2045
|
+
const helpText = slider ? `${this.#getPlanReviewHelpText()} ◂/▸ model` : this.#getPlanReviewHelpText();
|
|
2046
|
+
|
|
2109
2047
|
const choice = await this.showHookSelector(
|
|
2110
2048
|
"Plan mode - next step",
|
|
2111
2049
|
["Approve and execute", "Approve and compact context", keepContextLabel, "Refine plan"],
|
|
2112
2050
|
{
|
|
2113
|
-
helpText
|
|
2051
|
+
helpText,
|
|
2114
2052
|
onExternalEditor: () => void this.#openPlanInExternalEditor(planFilePath),
|
|
2115
2053
|
},
|
|
2054
|
+
{ slider },
|
|
2116
2055
|
);
|
|
2117
2056
|
|
|
2118
2057
|
if (choice === "Approve and execute" || choice === "Approve and compact context" || choice === keepContextLabel) {
|
|
@@ -2123,12 +2062,21 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2123
2062
|
this.showError(`Plan file not found at ${planFilePath}`);
|
|
2124
2063
|
return;
|
|
2125
2064
|
}
|
|
2065
|
+
// Capture the operator's tier choice and hand it to #approvePlan, which
|
|
2066
|
+
// applies it AFTER #exitPlanMode. #exitPlanMode restores
|
|
2067
|
+
// #planModePreviousModelState (the model from before plan mode), so
|
|
2068
|
+
// applying the slider choice any earlier would be silently reverted —
|
|
2069
|
+
// the bug that made "continue with slow" keep executing on the default
|
|
2070
|
+
// model. Deferred application also survives newSession()/compaction.
|
|
2071
|
+
const executionModel =
|
|
2072
|
+
cycle && selectedTierIndex !== cycle.currentIndex ? cycle.models[selectedTierIndex] : undefined;
|
|
2126
2073
|
await this.#approvePlan(latestPlanContent, {
|
|
2127
2074
|
planFilePath,
|
|
2128
2075
|
finalPlanFilePath,
|
|
2129
2076
|
title: details.title,
|
|
2130
2077
|
preserveContext: choice !== "Approve and execute",
|
|
2131
2078
|
compactBeforeExecute: choice === "Approve and compact context",
|
|
2079
|
+
executionModel,
|
|
2132
2080
|
});
|
|
2133
2081
|
} catch (error) {
|
|
2134
2082
|
this.showError(
|
|
@@ -2904,8 +2852,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2904
2852
|
title: string,
|
|
2905
2853
|
options: string[],
|
|
2906
2854
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
2855
|
+
extra?: { slider?: HookSelectorSlider },
|
|
2907
2856
|
): Promise<string | undefined> {
|
|
2908
|
-
return this.#extensionUiController.showHookSelector(title, options, dialogOptions);
|
|
2857
|
+
return this.#extensionUiController.showHookSelector(title, options, dialogOptions, extra);
|
|
2909
2858
|
}
|
|
2910
2859
|
|
|
2911
2860
|
hideHookSelector(): void {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete for internal-url schemes (skill://, rule://, omp://, local://,
|
|
3
|
+
* memory://, agent://, artifact://) while composing a prompt.
|
|
4
|
+
*
|
|
5
|
+
* Detection here MUST stay in sync with the generic URL-scheme trigger in the
|
|
6
|
+
* TUI editor (`packages/tui/src/components/editor.ts`); the editor fires the
|
|
7
|
+
* popup, this module decides whether there are candidates to show.
|
|
8
|
+
*/
|
|
9
|
+
import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { InternalUrlRouter } from "../internal-urls/router";
|
|
11
|
+
|
|
12
|
+
/** Upper bound on candidates surfaced in the dropdown. */
|
|
13
|
+
const MAX_URL_SUGGESTIONS = 25;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A URL token ending at the cursor: a known internal scheme followed by one or
|
|
17
|
+
* two slashes and the partially typed host/path. The boundary/rest character
|
|
18
|
+
* classes mirror the editor trigger so both agree on what counts as a token.
|
|
19
|
+
*/
|
|
20
|
+
const URL_TOKEN_RE = /(?:^|[\s"'`(<=])([a-z][a-z0-9+.-]*:\/{1,2}[^\s"'`()<>]*)$/i;
|
|
21
|
+
const SCHEME_SPLIT_RE = /^([a-z][a-z0-9+.-]*):\/{1,2}(.*)$/i;
|
|
22
|
+
|
|
23
|
+
export interface InternalUrlContext {
|
|
24
|
+
/** Lowercased scheme (e.g. `local`). */
|
|
25
|
+
scheme: string;
|
|
26
|
+
/** Text typed after the slashes so far (host + path); may be empty. */
|
|
27
|
+
query: string;
|
|
28
|
+
/** Exact buffer token from its boundary to the cursor (the completion prefix). */
|
|
29
|
+
token: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Subsequence fuzzy match: `hum` matches `humanizer`, `lp` matches `local-plan`.
|
|
33
|
+
function fuzzyMatch(query: string, target: string): boolean {
|
|
34
|
+
if (query.length === 0) return true;
|
|
35
|
+
if (query.length > target.length) return false;
|
|
36
|
+
let q = 0;
|
|
37
|
+
for (let t = 0; t < target.length && q < query.length; t += 1) {
|
|
38
|
+
if (query[q] === target[t]) q += 1;
|
|
39
|
+
}
|
|
40
|
+
return q === query.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Higher is better: exact > prefix > substring > scattered subsequence.
|
|
44
|
+
function fuzzyScore(query: string, target: string): number {
|
|
45
|
+
if (query.length === 0) return 1;
|
|
46
|
+
if (target === query) return 100;
|
|
47
|
+
if (target.startsWith(query)) return 80;
|
|
48
|
+
if (target.includes(query)) return 60;
|
|
49
|
+
let q = 0;
|
|
50
|
+
let gaps = 0;
|
|
51
|
+
let last = -1;
|
|
52
|
+
for (let t = 0; t < target.length && q < query.length; t += 1) {
|
|
53
|
+
if (query[q] === target[t]) {
|
|
54
|
+
if (last >= 0 && t - last > 1) gaps += 1;
|
|
55
|
+
last = t;
|
|
56
|
+
q += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (q !== query.length) return 0;
|
|
60
|
+
return Math.max(1, 40 - gaps * 5);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Detect a completable internal-url token immediately before the cursor.
|
|
65
|
+
* Returns `null` when the text is not a `scheme://` token whose scheme is
|
|
66
|
+
* registered with a completion-capable handler.
|
|
67
|
+
*/
|
|
68
|
+
export function extractInternalUrlContext(textBeforeCursor: string): InternalUrlContext | null {
|
|
69
|
+
const tokenMatch = URL_TOKEN_RE.exec(textBeforeCursor);
|
|
70
|
+
if (!tokenMatch) return null;
|
|
71
|
+
const token = tokenMatch[1]!;
|
|
72
|
+
const parts = SCHEME_SPLIT_RE.exec(token);
|
|
73
|
+
if (!parts) return null;
|
|
74
|
+
const scheme = parts[1]!.toLowerCase();
|
|
75
|
+
if (!InternalUrlRouter.instance().completionSchemes().includes(scheme)) return null;
|
|
76
|
+
return { scheme, query: parts[2] ?? "", token };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Suggestions for the internal-url token ending at the cursor, or `null` when
|
|
81
|
+
* the text is not such a token or no candidate matches the typed query.
|
|
82
|
+
*/
|
|
83
|
+
export async function getInternalUrlSuggestions(
|
|
84
|
+
textBeforeCursor: string,
|
|
85
|
+
): Promise<{ items: AutocompleteItem[]; prefix: string } | null> {
|
|
86
|
+
const ctx = extractInternalUrlContext(textBeforeCursor);
|
|
87
|
+
if (!ctx) return null;
|
|
88
|
+
|
|
89
|
+
const candidates = await InternalUrlRouter.instance().complete(ctx.scheme, ctx.query);
|
|
90
|
+
if (!candidates || candidates.length === 0) return null;
|
|
91
|
+
|
|
92
|
+
const query = ctx.query.toLowerCase();
|
|
93
|
+
const scored: Array<{ item: AutocompleteItem; score: number }> = [];
|
|
94
|
+
for (const candidate of candidates) {
|
|
95
|
+
const target = candidate.value.toLowerCase();
|
|
96
|
+
if (!fuzzyMatch(query, target)) continue;
|
|
97
|
+
scored.push({
|
|
98
|
+
item: {
|
|
99
|
+
value: `${ctx.scheme}://${candidate.value}`,
|
|
100
|
+
label: candidate.label ?? candidate.value,
|
|
101
|
+
...(candidate.description ? { description: candidate.description } : {}),
|
|
102
|
+
},
|
|
103
|
+
score: fuzzyScore(query, target),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (scored.length === 0) return null;
|
|
107
|
+
|
|
108
|
+
scored.sort((a, b) => b.score - a.score);
|
|
109
|
+
return {
|
|
110
|
+
items: scored.slice(0, MAX_URL_SUGGESTIONS).map(entry => entry.item),
|
|
111
|
+
prefix: ctx.token,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Whether `prefix` (the token a completion was offered for) is an internal-url token. */
|
|
116
|
+
export function isInternalUrlPrefix(prefix: string): boolean {
|
|
117
|
+
return extractInternalUrlContext(prefix) !== null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Replace the internal-url token with the selected candidate, appending a
|
|
122
|
+
* trailing space (matching `@` file-reference behavior) so the user can keep
|
|
123
|
+
* typing.
|
|
124
|
+
*/
|
|
125
|
+
export function applyInternalUrlCompletion(
|
|
126
|
+
lines: string[],
|
|
127
|
+
cursorLine: number,
|
|
128
|
+
cursorCol: number,
|
|
129
|
+
item: AutocompleteItem,
|
|
130
|
+
prefix: string,
|
|
131
|
+
): { lines: string[]; cursorLine: number; cursorCol: number } {
|
|
132
|
+
const currentLine = lines[cursorLine] || "";
|
|
133
|
+
const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
|
|
134
|
+
const afterCursor = currentLine.slice(cursorCol);
|
|
135
|
+
const insert = `${item.value} `;
|
|
136
|
+
const newLines = [...lines];
|
|
137
|
+
newLines[cursorLine] = beforePrefix + insert + afterCursor;
|
|
138
|
+
return {
|
|
139
|
+
lines: newLines,
|
|
140
|
+
cursorLine,
|
|
141
|
+
cursorCol: beforePrefix.length + insert.length,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import orchestrateNotice from "../prompts/system/orchestrate-notice.md" with { type: "text" };
|
|
2
|
+
import { createGradientHighlighter } from "./gradient-highlight";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* "orchestrate" keyword support.
|
|
6
|
+
*
|
|
7
|
+
* Typing the standalone word in the input editor paints it with a cool
|
|
8
|
+
* teal→violet gradient ({@link highlightOrchestrate}); submitting a message that
|
|
9
|
+
* mentions it appends a hidden {@link ORCHESTRATE_NOTICE} that switches the model
|
|
10
|
+
* into multi-agent orchestration mode. Matching is word-bounded and
|
|
11
|
+
* case-insensitive, so "orchestrated"/"orchestrating" never trigger either
|
|
12
|
+
* behavior. Replaces the former `/orchestrate` slash command.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Detection: standalone keyword, any case. Non-global so `.test` stays stateless.
|
|
16
|
+
const ORCHESTRATE_WORD = /\borchestrate\b/i;
|
|
17
|
+
|
|
18
|
+
/** Hidden system notice appended after a user message that mentions "orchestrate". */
|
|
19
|
+
export const ORCHESTRATE_NOTICE: string = orchestrateNotice.trim();
|
|
20
|
+
|
|
21
|
+
/** Whether `text` contains the standalone keyword "orchestrate" (any case). */
|
|
22
|
+
export function containsOrchestrate(text: string): boolean {
|
|
23
|
+
return ORCHESTRATE_WORD.test(text);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Highlight every standalone "orchestrate" in `text` for editor display with a
|
|
28
|
+
* cool teal→violet gradient (hue 150..280), visually distinct from ultrathink's
|
|
29
|
+
* full-spectrum rainbow.
|
|
30
|
+
*/
|
|
31
|
+
export const highlightOrchestrate: (text: string) => string = createGradientHighlighter({
|
|
32
|
+
probe: /orchestrate/i,
|
|
33
|
+
highlight: /\borchestrate\b/gi,
|
|
34
|
+
stops: 14,
|
|
35
|
+
hue: t => 150 + t * 130,
|
|
36
|
+
});
|
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
import { formatKeyHints, type KeybindingsManager } from "../config/keybindings";
|
|
9
9
|
import { isSettingsInitialized, settings } from "../config/settings";
|
|
10
10
|
import { applyEmojiCompletion, getEmojiSuggestions, isEmojiPrefix, tryEmojiInlineReplace } from "./emoji-autocomplete";
|
|
11
|
+
import {
|
|
12
|
+
applyInternalUrlCompletion,
|
|
13
|
+
getInternalUrlSuggestions,
|
|
14
|
+
isInternalUrlPrefix,
|
|
15
|
+
} from "./internal-url-autocomplete";
|
|
11
16
|
|
|
12
17
|
interface PromptActionDefinition {
|
|
13
18
|
id: string;
|
|
@@ -128,6 +133,9 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
128
133
|
}
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
const urlSuggestions = await getInternalUrlSuggestions(textBeforeCursor);
|
|
137
|
+
if (urlSuggestions) return urlSuggestions;
|
|
138
|
+
|
|
131
139
|
if (!isSettingsInitialized() || settings.get("emojiAutocomplete")) {
|
|
132
140
|
const emojiSuggestions = getEmojiSuggestions(textBeforeCursor);
|
|
133
141
|
if (emojiSuggestions) return emojiSuggestions;
|
|
@@ -170,6 +178,10 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
170
178
|
};
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
if (isInternalUrlPrefix(prefix)) {
|
|
182
|
+
return applyInternalUrlCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
183
|
+
}
|
|
184
|
+
|
|
173
185
|
if (isEmojiPrefix(prefix)) {
|
|
174
186
|
return applyEmojiCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
175
187
|
}
|
package/src/modes/ultrathink.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ultrathinkNotice from "../prompts/system/ultrathink-notice.md" with { type: "text" };
|
|
2
|
-
import {
|
|
2
|
+
import { createGradientHighlighter } from "./gradient-highlight";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* "ultrathink" keyword support, mirroring Claude Code's affordance.
|
|
@@ -11,12 +11,8 @@ import { theme } from "./theme/theme";
|
|
|
11
11
|
* "ultrathinking"/"ultrathinks" never trigger either behavior.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
// Cheap, stateless presence probe used to skip the boundary regex on most lines.
|
|
15
|
-
const ULTRATHINK_PROBE = /ultrathink/i;
|
|
16
14
|
// Detection: standalone keyword, any case. Non-global so `.test` stays stateless.
|
|
17
15
|
const ULTRATHINK_WORD = /\bultrathink\b/i;
|
|
18
|
-
// Highlight: global so `.replace` walks every occurrence.
|
|
19
|
-
const ULTRATHINK_HIGHLIGHT = /\bultrathink\b/gi;
|
|
20
16
|
|
|
21
17
|
/** Hidden system notice appended after a user message that mentions "ultrathink". */
|
|
22
18
|
export const ULTRATHINK_NOTICE: string = ultrathinkNotice.trim();
|
|
@@ -26,54 +22,14 @@ export function containsUltrathink(text: string): boolean {
|
|
|
26
22
|
return ULTRATHINK_WORD.test(text);
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
const FG_RESET = "\x1b[39m";
|
|
30
|
-
// Hue stops swept across the visible spectrum. More stops than the keyword has
|
|
31
|
-
// letters so the gradient resolves smoothly regardless of casing/match length.
|
|
32
|
-
const RAINBOW_STOPS = 14;
|
|
33
|
-
|
|
34
|
-
let cachedMode: string | undefined;
|
|
35
|
-
let cachedPalette: readonly string[] | undefined;
|
|
36
|
-
|
|
37
|
-
/** Rainbow foreground escapes for the active color mode, compiled once per mode. */
|
|
38
|
-
function rainbowPalette(): readonly string[] {
|
|
39
|
-
const mode = theme.getColorMode();
|
|
40
|
-
if (cachedPalette && cachedMode === mode) return cachedPalette;
|
|
41
|
-
const format = mode === "truecolor" ? "ansi-16m" : "ansi-256";
|
|
42
|
-
const palette: string[] = [];
|
|
43
|
-
for (let i = 0; i < RAINBOW_STOPS; i++) {
|
|
44
|
-
// Sweep red→violet (0..330°), stopping short of the wrap back to red.
|
|
45
|
-
const hue = Math.round((i / RAINBOW_STOPS) * 330);
|
|
46
|
-
palette.push(Bun.color(`hsl(${hue}, 90%, 62%)`, format) ?? "");
|
|
47
|
-
}
|
|
48
|
-
cachedMode = mode;
|
|
49
|
-
cachedPalette = palette;
|
|
50
|
-
return palette;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Paint each character of `word` with the next rainbow stop, resetting fg after. */
|
|
54
|
-
function rainbow(word: string): string {
|
|
55
|
-
const palette = rainbowPalette();
|
|
56
|
-
const n = word.length;
|
|
57
|
-
let out = "";
|
|
58
|
-
let prev = "";
|
|
59
|
-
for (let i = 0; i < n; i++) {
|
|
60
|
-
const color = palette[Math.floor((i / n) * palette.length)] ?? palette[0] ?? "";
|
|
61
|
-
// Coalesce consecutive characters that resolve to the same stop.
|
|
62
|
-
if (color !== prev) {
|
|
63
|
-
out += color;
|
|
64
|
-
prev = color;
|
|
65
|
-
}
|
|
66
|
-
out += word[i];
|
|
67
|
-
}
|
|
68
|
-
return `${out}${FG_RESET}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
25
|
/**
|
|
72
26
|
* Rainbow-highlight every standalone "ultrathink" in `text` for editor display.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
27
|
+
* Sweeps red→violet (hue 0..330), stopping short of the wrap back to red so the
|
|
28
|
+
* gradient resolves smoothly regardless of casing or match length.
|
|
75
29
|
*/
|
|
76
|
-
export
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
30
|
+
export const highlightUltrathink: (text: string) => string = createGradientHighlighter({
|
|
31
|
+
probe: /ultrathink/i,
|
|
32
|
+
highlight: /\bultrathink\b/gi,
|
|
33
|
+
stops: 14,
|
|
34
|
+
hue: t => t * 330,
|
|
35
|
+
});
|
|
@@ -16,10 +16,21 @@ export function matchesAppInterrupt(data: string): boolean {
|
|
|
16
16
|
return matchesKey(data, "escape") || matchesKey(data, "esc");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/** Match the generic selector cancel keybinding. */
|
|
19
20
|
export function matchesSelectCancel(data: string): boolean {
|
|
20
21
|
return getKeybindings().matches(data, "tui.select.cancel");
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/** Match the generic selector up-navigation keybinding. */
|
|
25
|
+
export function matchesSelectUp(data: string): boolean {
|
|
26
|
+
return getKeybindings().matches(data, "tui.select.up");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Match the generic selector down-navigation keybinding. */
|
|
30
|
+
export function matchesSelectDown(data: string): boolean {
|
|
31
|
+
return getKeybindings().matches(data, "tui.select.down");
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
export function matchesAppExternalEditor(data: string): boolean {
|
|
24
35
|
const keybindings = getKeybindings();
|
|
25
36
|
const externalEditorKeys = keybindings.getKeys("app.editor.external");
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Summarize the memories below into 1-3 concise sentences.
|
|
2
|
+
|
|
3
|
+
Preserve every fact, name, number, version, date, and decision exactly. Merge duplicates and near-duplicates; never repeat the same point. When memories conflict, state only the most recent as current. Do not invent, infer, or add anything that is not present in the memories. Output only the summary sentences, nothing else.
|
|
4
|
+
|
|
5
|
+
Memories:
|
|
6
|
+
{memories}
|
|
7
|
+
|
|
8
|
+
Summary:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Extract durable, long-term memory items from the user message below.
|
|
2
|
+
|
|
3
|
+
Output ONE item per line as a short plain-text statement: no JSON, no bullets, no numbering, no field labels.
|
|
4
|
+
Capture only persistent, reusable information:
|
|
5
|
+
- facts (name, role, employer, config, ports, versions, numbers)
|
|
6
|
+
- explicit instructions to the assistant
|
|
7
|
+
- stable preferences
|
|
8
|
+
- dated events or deadlines
|
|
9
|
+
|
|
10
|
+
Keep names, numbers, versions, and dates exact, in the message's original language. When a value is updated, output only the latest value. Ignore greetings, acknowledgements, small talk, weather, and one-off remarks.
|
|
11
|
+
If nothing qualifies, output exactly: NO_FACTS
|
|
12
|
+
|
|
13
|
+
Example
|
|
14
|
+
Message: My name is Sam, I work at Globex, and I always use 2-space indents.
|
|
15
|
+
Items:
|
|
16
|
+
name is Sam
|
|
17
|
+
works at Globex
|
|
18
|
+
prefers 2-space indents
|
|
19
|
+
|
|
20
|
+
Example
|
|
21
|
+
Message: lol nice weather today, might grab a coffee later
|
|
22
|
+
Items:
|
|
23
|
+
NO_FACTS
|
|
24
|
+
|
|
25
|
+
Message: {text}
|
|
26
|
+
Items:
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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.
|
|
1
|
+
<system-notice>
|
|
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.
|
|
15
3
|
|
|
16
4
|
<role>
|
|
17
5
|
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.
|
|
@@ -19,11 +7,11 @@ You decompose, dispatch, verify, and iterate. You do **not** edit code. Every fi
|
|
|
19
7
|
|
|
20
8
|
<rules>
|
|
21
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.
|
|
22
|
-
2. **Enumerate the full surface before dispatching.** If the
|
|
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.
|
|
23
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.
|
|
24
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.
|
|
25
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.
|
|
26
|
-
6. **Commit policy.** If the
|
|
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.
|
|
27
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.
|
|
28
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.
|
|
29
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.
|
|
@@ -47,3 +35,4 @@ You decompose, dispatch, verify, and iterate. You do **not** edit code. Every fi
|
|
|
47
35
|
- Marking todos done based on subagent self-reports without verifying the gate.
|
|
48
36
|
- Summarizing progress in chat instead of advancing to the next phase.
|
|
49
37
|
</anti-patterns>
|
|
38
|
+
</system-notice>
|
|
@@ -54,7 +54,9 @@ With most FS/bash-like tools, static references to them will automatically resol
|
|
|
54
54
|
- `skill://<name>`: Skill instructions
|
|
55
55
|
- `/<path>`: File within a skill
|
|
56
56
|
- `rule://<name>`: Rule details
|
|
57
|
+
{{#if hasMemoryRoot}}
|
|
57
58
|
- `memory://root`: Project memory summary
|
|
59
|
+
{{/if}}
|
|
58
60
|
- `agent://<id>`: Full agent output artifact
|
|
59
61
|
- `/<path>`: JSON field extraction
|
|
60
62
|
- `artifact://<id>`: Artifact content
|