@oh-my-pi/pi-coding-agent 9.4.0 → 9.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 +84 -0
- package/package.json +9 -8
- package/src/capability/index.ts +7 -9
- package/src/cli/config-cli.ts +86 -73
- package/src/cli/update-cli.ts +45 -3
- package/src/commit/agentic/agent.ts +4 -4
- package/src/commit/agentic/index.ts +6 -5
- package/src/commit/agentic/tools/analyze-file.ts +5 -7
- package/src/commit/agentic/tools/index.ts +3 -3
- package/src/commit/model-selection.ts +13 -17
- package/src/commit/pipeline.ts +5 -5
- package/src/config/model-registry.ts +7 -0
- package/src/config/settings-schema.ts +836 -0
- package/src/config/settings.ts +702 -0
- package/src/discovery/helpers.ts +55 -11
- package/src/exa/index.ts +1 -1
- package/src/exec/bash-executor.ts +13 -13
- package/src/exec/shell-session.ts +15 -3
- package/src/export/ttsr.ts +1 -1
- package/src/extensibility/skills.ts +40 -9
- package/src/index.ts +2 -10
- package/src/ipy/gateway-coordinator.ts +5 -143
- package/src/ipy/kernel.ts +6 -171
- package/src/ipy/runtime.ts +198 -0
- package/src/lsp/client.ts +14 -1
- package/src/lsp/defaults.json +0 -6
- package/src/lsp/index.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +26 -48
- package/src/modes/components/extensions/extension-dashboard.ts +22 -11
- package/src/modes/components/index.ts +1 -1
- package/src/modes/components/model-selector.ts +7 -7
- package/src/modes/components/settings-defs.ts +210 -915
- package/src/modes/components/settings-selector.ts +80 -106
- package/src/modes/components/status-line/types.ts +2 -8
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/status-line.ts +26 -3
- package/src/modes/controllers/event-controller.ts +9 -8
- package/src/modes/controllers/input-controller.ts +19 -15
- package/src/modes/controllers/selector-controller.ts +30 -14
- package/src/modes/interactive-mode.ts +10 -10
- package/src/modes/rpc/rpc-mode.ts +10 -0
- package/src/modes/rpc/rpc-types.ts +3 -0
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +4 -3
- package/src/patch/index.ts +7 -7
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/bash.md +12 -2
- package/src/prompts/tools/task.md +180 -73
- package/src/sdk.ts +38 -61
- package/src/session/agent-session.ts +66 -55
- package/src/session/agent-storage.ts +1 -1
- package/src/session/session-manager.ts +10 -10
- package/src/system-prompt.ts +2 -2
- package/src/task/executor.ts +9 -9
- package/src/task/index.ts +2 -2
- package/src/tools/ask.ts +5 -6
- package/src/tools/bash-interceptor.ts +39 -1
- package/src/tools/bash-normalize.ts +126 -0
- package/src/tools/bash.ts +31 -5
- package/src/tools/find.ts +51 -33
- package/src/tools/index.ts +5 -23
- package/src/tools/plan-mode-guard.ts +1 -6
- package/src/tools/python.ts +2 -2
- package/src/tools/read.ts +2 -2
- package/src/tools/write.ts +2 -2
- package/src/utils/ignore-files.ts +119 -0
- package/src/web/search/providers/perplexity.ts +1 -1
- package/examples/sdk/10-settings.ts +0 -37
- package/src/config/settings-manager.ts +0 -2015
|
@@ -3,6 +3,7 @@ import type { OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { getAgentDbPath } from "../../config";
|
|
6
|
+
import { settings } from "../../config/settings";
|
|
6
7
|
import { DebugSelectorComponent } from "../../debug";
|
|
7
8
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
8
9
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
@@ -51,7 +52,6 @@ export class SelectorController {
|
|
|
51
52
|
getAvailableThemes().then(availableThemes => {
|
|
52
53
|
this.showSelector(done => {
|
|
53
54
|
const selector = new SettingsSelectorComponent(
|
|
54
|
-
this.ctx.settingsManager,
|
|
55
55
|
{
|
|
56
56
|
availableThinkingLevels: this.ctx.session.getAvailableThinkingLevels(),
|
|
57
57
|
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
@@ -68,10 +68,16 @@ export class SelectorController {
|
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
},
|
|
71
|
-
onStatusLinePreview:
|
|
71
|
+
onStatusLinePreview: previewSettings => {
|
|
72
72
|
// Update status line with preview settings
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
this.ctx.statusLine.updateSettings({
|
|
74
|
+
preset: settings.get("statusLine.preset"),
|
|
75
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
76
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
77
|
+
separator: settings.get("statusLine.separator"),
|
|
78
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
79
|
+
...previewSettings,
|
|
80
|
+
});
|
|
75
81
|
this.ctx.updateEditorTopBorder();
|
|
76
82
|
this.ctx.ui.requestRender();
|
|
77
83
|
},
|
|
@@ -86,7 +92,13 @@ export class SelectorController {
|
|
|
86
92
|
onCancel: () => {
|
|
87
93
|
done();
|
|
88
94
|
// Restore status line to saved settings
|
|
89
|
-
this.ctx.statusLine.updateSettings(
|
|
95
|
+
this.ctx.statusLine.updateSettings({
|
|
96
|
+
preset: settings.get("statusLine.preset"),
|
|
97
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
98
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
99
|
+
separator: settings.get("statusLine.separator"),
|
|
100
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
101
|
+
});
|
|
90
102
|
this.ctx.updateEditorTopBorder();
|
|
91
103
|
this.ctx.ui.requestRender();
|
|
92
104
|
},
|
|
@@ -123,11 +135,7 @@ export class SelectorController {
|
|
|
123
135
|
* Replaces /status with a unified view of all providers and extensions.
|
|
124
136
|
*/
|
|
125
137
|
async showExtensionsDashboard(): Promise<void> {
|
|
126
|
-
const dashboard = await ExtensionDashboard.create(
|
|
127
|
-
process.cwd(),
|
|
128
|
-
this.ctx.settingsManager,
|
|
129
|
-
this.ctx.ui.terminal.rows,
|
|
130
|
-
);
|
|
138
|
+
const dashboard = await ExtensionDashboard.create(process.cwd(), this.ctx.settings, this.ctx.ui.terminal.rows);
|
|
131
139
|
this.showSelector(done => {
|
|
132
140
|
dashboard.onClose = () => {
|
|
133
141
|
done();
|
|
@@ -142,7 +150,7 @@ export class SelectorController {
|
|
|
142
150
|
* Most settings are saved directly via SettingsManager in the definitions.
|
|
143
151
|
* This handles side effects and session-specific settings.
|
|
144
152
|
*/
|
|
145
|
-
handleSettingChange(id: string, value:
|
|
153
|
+
handleSettingChange(id: string, value: unknown): void {
|
|
146
154
|
// Discovery provider toggles
|
|
147
155
|
if (id.startsWith("discovery.")) {
|
|
148
156
|
const providerId = id.replace("discovery.", "");
|
|
@@ -232,7 +240,15 @@ export class SelectorController {
|
|
|
232
240
|
case "statusLineGitShowUntracked":
|
|
233
241
|
case "statusLineTimeFormat":
|
|
234
242
|
case "statusLineTimeShowSeconds": {
|
|
235
|
-
|
|
243
|
+
const statusLineSettings = {
|
|
244
|
+
preset: settings.get("statusLine.preset"),
|
|
245
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
246
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
247
|
+
separator: settings.get("statusLine.separator"),
|
|
248
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
249
|
+
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
250
|
+
};
|
|
251
|
+
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
236
252
|
this.ctx.updateEditorTopBorder();
|
|
237
253
|
this.ctx.ui.requestRender();
|
|
238
254
|
break;
|
|
@@ -256,7 +272,7 @@ export class SelectorController {
|
|
|
256
272
|
const selector = new ModelSelectorComponent(
|
|
257
273
|
this.ctx.ui,
|
|
258
274
|
this.ctx.session.model,
|
|
259
|
-
this.ctx.
|
|
275
|
+
this.ctx.settings,
|
|
260
276
|
this.ctx.session.modelRegistry,
|
|
261
277
|
this.ctx.session.scopedModels,
|
|
262
278
|
async (model, role) => {
|
|
@@ -369,7 +385,7 @@ export class SelectorController {
|
|
|
369
385
|
let wantsSummary = false;
|
|
370
386
|
let customInstructions: string | undefined;
|
|
371
387
|
|
|
372
|
-
const branchSummariesEnabled =
|
|
388
|
+
const branchSummariesEnabled = settings.get("branchSummary.enabled");
|
|
373
389
|
|
|
374
390
|
while (branchSummariesEnabled) {
|
|
375
391
|
const summaryChoice = await this.ctx.showHookSelector("Summarize branch?", [
|
|
@@ -19,7 +19,7 @@ import { isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
|
19
19
|
import chalk from "chalk";
|
|
20
20
|
import { KeybindingsManager } from "../config/keybindings";
|
|
21
21
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
22
|
-
import type
|
|
22
|
+
import { type Settings, settings } from "../config/settings";
|
|
23
23
|
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../extensibility/extensions";
|
|
24
24
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
25
25
|
import { loadSlashCommands } from "../extensibility/slash-commands";
|
|
@@ -72,7 +72,7 @@ export interface InteractiveModeOptions {
|
|
|
72
72
|
export class InteractiveMode implements InteractiveModeContext {
|
|
73
73
|
public session: AgentSession;
|
|
74
74
|
public sessionManager: SessionManager;
|
|
75
|
-
public
|
|
75
|
+
public settings: Settings;
|
|
76
76
|
public keybindings: KeybindingsManager;
|
|
77
77
|
public agent: AgentSession["agent"];
|
|
78
78
|
public historyStorage?: HistoryStorage;
|
|
@@ -159,7 +159,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
159
159
|
) {
|
|
160
160
|
this.session = session;
|
|
161
161
|
this.sessionManager = session.sessionManager;
|
|
162
|
-
this.
|
|
162
|
+
this.settings = session.settings;
|
|
163
163
|
this.keybindings = KeybindingsManager.inMemory();
|
|
164
164
|
this.agent = session.agent;
|
|
165
165
|
this.version = version;
|
|
@@ -168,7 +168,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
168
168
|
this.lspServers = lspServers;
|
|
169
169
|
this.mcpManager = mcpManager;
|
|
170
170
|
|
|
171
|
-
this.ui = new TUI(new ProcessTerminal(),
|
|
171
|
+
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
172
172
|
setMermaidRenderCallback(() => this.ui.requestRender());
|
|
173
173
|
this.chatContainer = new Container();
|
|
174
174
|
this.pendingMessagesContainer = new Container();
|
|
@@ -193,7 +193,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
193
193
|
this.statusLine = new StatusLineComponent(session);
|
|
194
194
|
this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
195
195
|
|
|
196
|
-
this.hideThinkingBlock =
|
|
196
|
+
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
197
197
|
|
|
198
198
|
// Define slash commands for autocomplete
|
|
199
199
|
const slashCommands: SlashCommand[] = [
|
|
@@ -240,7 +240,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
240
240
|
|
|
241
241
|
// Build skill commands from session.skills (if enabled)
|
|
242
242
|
const skillCommandList: SlashCommand[] = [];
|
|
243
|
-
if (
|
|
243
|
+
if (settings.get("skills.enableSkillCommands")) {
|
|
244
244
|
for (const skill of this.session.skills) {
|
|
245
245
|
const commandName = `skill:${skill.name}`;
|
|
246
246
|
this.skillCommands.set(commandName, skill.filePath);
|
|
@@ -300,7 +300,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
300
300
|
fileTypes: s.fileTypes,
|
|
301
301
|
})) ?? [];
|
|
302
302
|
|
|
303
|
-
const startupQuiet =
|
|
303
|
+
const startupQuiet = settings.get("startup.quiet");
|
|
304
304
|
|
|
305
305
|
if (!startupQuiet) {
|
|
306
306
|
// Add welcome header
|
|
@@ -314,7 +314,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
314
314
|
// Add changelog if provided
|
|
315
315
|
if (this.changelogMarkdown) {
|
|
316
316
|
this.ui.addChild(new DynamicBorder());
|
|
317
|
-
if (
|
|
317
|
+
if (settings.get("collapseChangelog")) {
|
|
318
318
|
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
319
319
|
const latestVersion = versionMatch ? versionMatch[1] : this.version;
|
|
320
320
|
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
@@ -330,7 +330,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// Set terminal title if session already has one (resumed session)
|
|
333
|
-
const existingTitle = this.sessionManager.
|
|
333
|
+
const existingTitle = this.sessionManager.getSessionName();
|
|
334
334
|
if (existingTitle) {
|
|
335
335
|
setTerminalTitle(`pi: ${existingTitle}`);
|
|
336
336
|
}
|
|
@@ -494,7 +494,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
494
494
|
private resolvePlanFilePath(planFilePath: string): string {
|
|
495
495
|
if (planFilePath.startsWith("plan://")) {
|
|
496
496
|
return resolvePlanUrlToPath(planFilePath, {
|
|
497
|
-
getPlansDirectory: this.
|
|
497
|
+
getPlansDirectory: () => this.settings.getPlansDirectory(),
|
|
498
498
|
cwd: this.sessionManager.getCwd(),
|
|
499
499
|
});
|
|
500
500
|
}
|
|
@@ -448,6 +448,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
448
448
|
interruptMode: session.interruptMode,
|
|
449
449
|
sessionFile: session.sessionFile,
|
|
450
450
|
sessionId: session.sessionId,
|
|
451
|
+
sessionName: session.sessionName,
|
|
451
452
|
autoCompactionEnabled: session.autoCompactionEnabled,
|
|
452
453
|
messageCount: session.messages.length,
|
|
453
454
|
queuedMessageCount: session.queuedMessageCount,
|
|
@@ -594,6 +595,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
594
595
|
return success(id, "get_last_assistant_text", { text });
|
|
595
596
|
}
|
|
596
597
|
|
|
598
|
+
case "set_session_name": {
|
|
599
|
+
const name = command.name.trim();
|
|
600
|
+
if (!name) {
|
|
601
|
+
return error(id, "set_session_name", "Session name cannot be empty");
|
|
602
|
+
}
|
|
603
|
+
session.setSessionName(name);
|
|
604
|
+
return success(id, "set_session_name");
|
|
605
|
+
}
|
|
606
|
+
|
|
597
607
|
// =================================================================
|
|
598
608
|
// Messages
|
|
599
609
|
// =================================================================
|
|
@@ -58,6 +58,7 @@ export type RpcCommand =
|
|
|
58
58
|
| { id?: string; type: "branch"; entryId: string }
|
|
59
59
|
| { id?: string; type: "get_branch_messages" }
|
|
60
60
|
| { id?: string; type: "get_last_assistant_text" }
|
|
61
|
+
| { id?: string; type: "set_session_name"; name: string }
|
|
61
62
|
|
|
62
63
|
// Messages
|
|
63
64
|
| { id?: string; type: "get_messages" };
|
|
@@ -76,6 +77,7 @@ export interface RpcSessionState {
|
|
|
76
77
|
interruptMode: "immediate" | "wait";
|
|
77
78
|
sessionFile?: string;
|
|
78
79
|
sessionId: string;
|
|
80
|
+
sessionName?: string;
|
|
79
81
|
autoCompactionEnabled: boolean;
|
|
80
82
|
messageCount: number;
|
|
81
83
|
queuedMessageCount: number;
|
|
@@ -166,6 +168,7 @@ export type RpcResponse =
|
|
|
166
168
|
success: true;
|
|
167
169
|
data: { text: string | null };
|
|
168
170
|
}
|
|
171
|
+
| { id?: string; type: "response"; command: "set_session_name"; success: true }
|
|
169
172
|
|
|
170
173
|
// Messages
|
|
171
174
|
| { id?: string; type: "response"; command: "get_messages"; success: true; data: { messages: AgentMessage[] } }
|
package/src/modes/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { Component, Container, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import type { KeybindingsManager } from "../config/keybindings";
|
|
5
|
-
import type {
|
|
5
|
+
import type { Settings } from "../config/settings";
|
|
6
6
|
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../extensibility/extensions";
|
|
7
7
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
8
8
|
import type { MCPManager } from "../mcp";
|
|
@@ -46,7 +46,7 @@ export interface InteractiveModeContext {
|
|
|
46
46
|
// Session access
|
|
47
47
|
session: AgentSession;
|
|
48
48
|
sessionManager: SessionManager;
|
|
49
|
-
|
|
49
|
+
settings: Settings;
|
|
50
50
|
keybindings: KeybindingsManager;
|
|
51
51
|
agent: AgentSession["agent"];
|
|
52
52
|
historyStorage?: HistoryStorage;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { AssistantMessage, Message } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { Spacer, Text, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { settings } from "../../config/settings";
|
|
4
5
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
5
6
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
6
7
|
import { BranchSummaryMessageComponent } from "../../modes/components/branch-summary-message";
|
|
@@ -224,9 +225,9 @@ export class UiHelpers {
|
|
|
224
225
|
content.name,
|
|
225
226
|
content.arguments,
|
|
226
227
|
{
|
|
227
|
-
showImages:
|
|
228
|
-
editFuzzyThreshold:
|
|
229
|
-
editAllowFuzzy:
|
|
228
|
+
showImages: settings.get("terminal.showImages"),
|
|
229
|
+
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
230
|
+
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
230
231
|
},
|
|
231
232
|
tool,
|
|
232
233
|
this.ctx.ui,
|
package/src/patch/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { outputMeta } from "../tools/output-meta";
|
|
|
26
26
|
import { enforcePlanModeWrite, resolvePlanPath } from "../tools/plan-mode-guard";
|
|
27
27
|
import { applyPatch } from "./applicator";
|
|
28
28
|
import { generateDiffString, generateUnifiedDiffString, replaceText } from "./diff";
|
|
29
|
-
import {
|
|
29
|
+
import { findMatch } from "./fuzzy";
|
|
30
30
|
import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize";
|
|
31
31
|
import { buildNormativeUpdateInput } from "./normative";
|
|
32
32
|
import { type EditToolDetails, getLspBatchRequest } from "./shared";
|
|
@@ -233,14 +233,14 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
233
233
|
this.allowFuzzy = false;
|
|
234
234
|
break;
|
|
235
235
|
case "auto":
|
|
236
|
-
this.allowFuzzy = session.settings
|
|
236
|
+
this.allowFuzzy = session.settings.get("edit.fuzzyMatch");
|
|
237
237
|
break;
|
|
238
238
|
default:
|
|
239
239
|
throw new Error(`Invalid OMP_EDIT_FUZZY: ${editFuzzy}`);
|
|
240
240
|
}
|
|
241
241
|
switch (editFuzzyThreshold) {
|
|
242
242
|
case "auto":
|
|
243
|
-
this.fuzzyThreshold = session.settings
|
|
243
|
+
this.fuzzyThreshold = session.settings.get("edit.fuzzyThreshold");
|
|
244
244
|
break;
|
|
245
245
|
default:
|
|
246
246
|
this.fuzzyThreshold = parseFloat(editFuzzyThreshold);
|
|
@@ -251,8 +251,8 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
const enableLsp = session.enableLsp ?? true;
|
|
254
|
-
const enableDiagnostics = enableLsp
|
|
255
|
-
const enableFormat = enableLsp
|
|
254
|
+
const enableDiagnostics = enableLsp && session.settings.get("lsp.diagnosticsOnEdit");
|
|
255
|
+
const enableFormat = enableLsp && session.settings.get("lsp.formatOnWrite");
|
|
256
256
|
this.writethrough = enableLsp
|
|
257
257
|
? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
|
|
258
258
|
: writethroughNoop;
|
|
@@ -268,11 +268,11 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
268
268
|
|
|
269
269
|
// Auto mode: check model-specific settings
|
|
270
270
|
const activeModel = this.session.getActiveModelString?.();
|
|
271
|
-
const modelVariant = this.session.settings
|
|
271
|
+
const modelVariant = this.session.settings.getEditVariantForModel(activeModel);
|
|
272
272
|
if (modelVariant === "replace") return false;
|
|
273
273
|
if (modelVariant === "patch") return true;
|
|
274
274
|
|
|
275
|
-
return this.session.settings
|
|
275
|
+
return this.session.settings.get("edit.patchMode");
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/**
|
|
@@ -330,7 +330,6 @@ Write what you can defend.
|
|
|
330
330
|
{{#if isCoordinator}}
|
|
331
331
|
{{#has tools "task"}}
|
|
332
332
|
<critical id="coordinator">
|
|
333
|
-
As the coordinator, **ALWAYS prefer** delegation via Task tool for all SUBSTANTIAL work.
|
|
334
333
|
Your context window is limited—especially the output. Work in discrete steps and run each step using Task tool. Avoid putting substantial work in the main context when possible. Run multiple tasks in parallel whenever possible.
|
|
335
334
|
|
|
336
335
|
## Triggers requiring Task tool
|
|
@@ -12,7 +12,7 @@ Executes a bash command in a shell session for terminal operations like git, bun
|
|
|
12
12
|
|
|
13
13
|
<output>
|
|
14
14
|
Returns stdout, stderr, and exit code from command execution.
|
|
15
|
-
- Output truncated after 50KB or 2000 lines (whichever comes first); use
|
|
15
|
+
- Output truncated after 50KB or 2000 lines (whichever comes first); use `head` parameter to limit output
|
|
16
16
|
- If output is truncated, full output is stored under $ARTIFACTS and referenced as `artifact://<id>` in metadata
|
|
17
17
|
- Exit codes shown on non-zero exit; stderr captured
|
|
18
18
|
</output>
|
|
@@ -24,4 +24,14 @@ Do NOT use Bash for these operations—specialized tools exist:
|
|
|
24
24
|
- Finding files by pattern → Find tool
|
|
25
25
|
- Content-addressed edits → Edit tool
|
|
26
26
|
- Writing new files → Write tool
|
|
27
|
-
</critical>
|
|
27
|
+
</critical>
|
|
28
|
+
|
|
29
|
+
<avoid>
|
|
30
|
+
Do NOT pipe through `head` or `tail`—use the `head` and `tail` parameters instead:
|
|
31
|
+
- `command | head -n 50` → use `head: 50` parameter
|
|
32
|
+
- `command | tail -n 100` → use `tail: 100` parameter
|
|
33
|
+
|
|
34
|
+
The pipe pattern breaks streaming output and prevents artifact storage.
|
|
35
|
+
|
|
36
|
+
Do NOT use `2>&1`—stdout and stderr are already merged.
|
|
37
|
+
</avoid>
|
|
@@ -1,22 +1,183 @@
|
|
|
1
1
|
# Task
|
|
2
2
|
|
|
3
|
-
Launch
|
|
3
|
+
Launch agents to handle complex, multi-step tasks autonomously.
|
|
4
4
|
|
|
5
5
|
<critical>
|
|
6
|
-
|
|
6
|
+
## Context is everything
|
|
7
7
|
|
|
8
|
-
Subagents
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
8
|
+
Subagents fail when context is vague. They cannot read your mind or infer project conventions. Every task needs:
|
|
9
|
+
1. **Goal** - What this accomplishes (one sentence)
|
|
10
|
+
2. **Constraints** - Hard requirements, banned approaches, naming conventions
|
|
11
|
+
3. **Existing Code** - File paths and function signatures to use as patterns
|
|
12
|
+
4. **API Contract** - If the task produces or consumes an interface, spell it out
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Subagents CAN grep the parent conversation file for supplementary details. They CANNOT grep for:
|
|
15
|
+
- Decisions you made but didn't write down
|
|
16
|
+
- Conventions that exist only in your head
|
|
17
|
+
- Which of 50 possible approaches you want
|
|
18
|
+
**Rule of thumb:** If you'd need to answer a clarifying question for a junior dev to do this task, that information belongs in context.
|
|
19
|
+
</critical>
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
<context-structure>
|
|
22
|
+
## Required context structure
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
Use this template. Sections can be omitted only if truly N/A.
|
|
25
|
+
|
|
26
|
+
````
|
|
27
|
+
## Goal
|
|
28
|
+
[One sentence: what this task accomplishes]
|
|
29
|
+
|
|
30
|
+
## Constraints
|
|
31
|
+
- [Hard requirements - MUST/MUST NOT style]
|
|
32
|
+
- [API conventions, naming patterns, error handling]
|
|
33
|
+
- [What already exists vs what to create]
|
|
34
|
+
|
|
35
|
+
## Existing Code
|
|
36
|
+
Reference files the agent MUST read or use as patterns:
|
|
37
|
+
- `path/to/file.ts` - [what pattern it demonstrates]
|
|
38
|
+
- `path/to/other.rs` - [what to reuse from it]
|
|
39
|
+
|
|
40
|
+
## API Contract (if applicable)
|
|
41
|
+
```language
|
|
42
|
+
// Exact signatures the agent must implement or consume
|
|
43
|
+
fn example(input: Type) -> Result<Output>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Task
|
|
47
|
+
{{description}}
|
|
48
|
+
|
|
49
|
+
## Files
|
|
50
|
+
{{files}}
|
|
51
|
+
````
|
|
52
|
+
|
|
53
|
+
### Bad context (agent will fail or guess wrong)
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
N-API migration. Keep highlight sync. Use JsString. No WASM.
|
|
57
|
+
Task: {{description}} Files: {{files}}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Why it fails:
|
|
61
|
+
- No existing code to reference - agent doesn't know your patterns
|
|
62
|
+
- No API contract - agent will invent signatures that don't match consumers
|
|
63
|
+
- No goal - agent doesn't know what success looks like
|
|
64
|
+
- "Keep highlight sync" is meaningless without knowing what highlight is or where it lives
|
|
65
|
+
|
|
66
|
+
### Good context (agent can act confidently)
|
|
67
|
+
|
|
68
|
+
````
|
|
69
|
+
## Goal
|
|
70
|
+
Port grep module from WASM to N-API, matching existing text module patterns.
|
|
71
|
+
|
|
72
|
+
## Constraints
|
|
73
|
+
- Use `#[napi]` attribute macro on all exports (not `#[napi(js_name = "...")]`)
|
|
74
|
+
- Return `napi::Result<T>` for fallible ops, never panic
|
|
75
|
+
- Use `spawn_blocking` for any operation that touches filesystem or runs >1ms
|
|
76
|
+
- Accept `JsString` for string params (NOT JsStringUtf8 - it has lifetime issues)
|
|
77
|
+
- Keep all existing function names - TS bindings depend on them
|
|
78
|
+
- No new crate dependencies
|
|
79
|
+
|
|
80
|
+
## Existing Code
|
|
81
|
+
- `crates/pi-natives/src/text.rs` - reference N-API pattern: see how `visible_width` uses JsString
|
|
82
|
+
- `crates/pi-natives/src/lib.rs` - module registration pattern
|
|
83
|
+
- `crates/pi-natives/Cargo.toml` - available dependencies (ignore, regex already present)
|
|
84
|
+
|
|
85
|
+
## API Contract
|
|
86
|
+
Current sync API to convert to async:
|
|
87
|
+
```rust
|
|
88
|
+
// BEFORE (sync, blocks event loop)
|
|
89
|
+
#[napi]
|
|
90
|
+
pub fn search(pattern: String, path: String) -> Vec<Match>
|
|
91
|
+
|
|
92
|
+
// AFTER (async, uses spawn_blocking)
|
|
93
|
+
#[napi]
|
|
94
|
+
pub async fn search(pattern: JsString, path: JsString, env: Env) -> napi::Result<Vec<Match>>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Task
|
|
98
|
+
{{description}}
|
|
99
|
+
|
|
100
|
+
## Files
|
|
101
|
+
{{files}}
|
|
102
|
+
````
|
|
103
|
+
</context-structure>
|
|
104
|
+
|
|
105
|
+
<parallelization>
|
|
106
|
+
## When to parallelize vs sequence
|
|
107
|
+
**The test:** Can agent B write correct code without seeing agent A's output?
|
|
108
|
+
- If YES → parallelize
|
|
109
|
+
- If NO → sequence (A completes, then B runs with A's output in context)
|
|
110
|
+
|
|
111
|
+
### Dependency patterns that MUST be sequential
|
|
112
|
+
|
|
113
|
+
|First|Then|Why|
|
|
114
|
+
|---|---|---|
|
|
115
|
+
|Create Rust API|Update TS bindings|Bindings need to know export names and signatures|
|
|
116
|
+
|Define interface/types|Implement consumers|Consumers need the contract|
|
|
117
|
+
|Scaffold with signatures|Implement bodies|Implementations need the shape|
|
|
118
|
+
|Core module|Dependent modules|Dependents import from core|
|
|
119
|
+
|
|
120
|
+
### Safe to parallelize
|
|
121
|
+
- Independent modules that don't import each other
|
|
122
|
+
- Tests for already-implemented code
|
|
123
|
+
- Documentation for stable APIs
|
|
124
|
+
- Refactors in isolated file scopes
|
|
125
|
+
|
|
126
|
+
### Phased execution pattern
|
|
127
|
+
|
|
128
|
+
For migrations/refactors with layers:
|
|
129
|
+
**Phase 1 - Foundation (do yourself or single task):**
|
|
130
|
+
Create the scaffold, define interfaces, establish API shape. Never fan out until the contract is known.
|
|
131
|
+
**Phase 2 - Parallel implementation:**
|
|
132
|
+
Fan out to independent tasks that all consume the same known interface. Include the API contract from Phase 1 in every task's context.
|
|
133
|
+
**Phase 3 - Integration (do yourself):**
|
|
134
|
+
Wire things together, update build/CI, fix any mismatches.
|
|
135
|
+
**Phase 4 - Dependent layer:**
|
|
136
|
+
Fan out again for work that consumes Phase 2 outputs.
|
|
137
|
+
|
|
138
|
+
### Example: WASM to N-API migration
|
|
139
|
+
**WRONG** (launched together, will fail):
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
tasks: [
|
|
143
|
+
{ id: "RustApi", description: "Implement N-API exports" },
|
|
144
|
+
{ id: "TsBindings", description: "Update TS to use N-API" }, // ← needs RustApi output!
|
|
145
|
+
]
|
|
146
|
+
```
|
|
147
|
+
**RIGHT** (phased):
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
// Phase 1: You create scaffold with signatures in lib.rs
|
|
151
|
+
|
|
152
|
+
// Phase 2: Fan out Rust implementation
|
|
153
|
+
tasks: [
|
|
154
|
+
{ id: "Grep", description: "Implement grep module", args: { files: "src/grep.rs" } },
|
|
155
|
+
{ id: "Text", description: "Implement text module", args: { files: "src/text.rs" } },
|
|
156
|
+
// Each task gets the API contract you defined in Phase 1
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
// Phase 3: You verify Rust compiles, exports are correct
|
|
160
|
+
|
|
161
|
+
// Phase 4: Fan out TS bindings (now they know what Rust exports)
|
|
162
|
+
tasks: [
|
|
163
|
+
{ id: "GrepBindings", description: "Update grep TS", args: { files: "src/grep/index.ts" } },
|
|
164
|
+
// Context includes actual export names from Phase 2
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
</parallelization>
|
|
168
|
+
|
|
169
|
+
<parameters>
|
|
170
|
+
- `agent`: Agent type for all tasks
|
|
171
|
+
- `context`: Template with `{{placeholders}}`. **Must follow the structure above.** Include Goal, Constraints, Existing Code references. Subagents can search parent context for background, but core requirements must be explicit here.
|
|
172
|
+
- `isolated`: (optional) Run in git worktree, return patches
|
|
173
|
+
- `tasks`: Array of `{id, description, args}`
|
|
174
|
+
- `id`: CamelCase identifier (max 32 chars)
|
|
175
|
+
- `description`: What the task does (for logging)
|
|
176
|
+
- `args`: Object with keys matching `{{placeholders}}` in context
|
|
177
|
+
- `skills`: (optional) Skill names to preload
|
|
178
|
+
- `schema`: JTD schema for response structure. **Required.** Use typed properties, not `{ "type": "string" }`.
|
|
179
|
+
**Schema goes in `schema` parameter. Never describe output format in `context`.**
|
|
180
|
+
</parameters>
|
|
20
181
|
|
|
21
182
|
<agents>
|
|
22
183
|
{{#list agents join="\n"}}
|
|
@@ -27,66 +188,12 @@ Agents with `output="structured"` enforce their own schema; the `schema` paramet
|
|
|
27
188
|
{{/list}}
|
|
28
189
|
</agents>
|
|
29
190
|
|
|
30
|
-
<instruction>
|
|
31
|
-
This matters. Be thorough.
|
|
32
|
-
1. Plan before acting. Define the goal, acceptance criteria, and scope per task.
|
|
33
|
-
2. Put shared constraints and decisions in `context`; keep each task request short and unambiguous. **Do not describe response format here.**
|
|
34
|
-
3. State whether each task is research-only or should modify files.
|
|
35
|
-
4. **Always provide a `schema`** with typed properties. Avoid `{ "type": "string" }`—if data has any structure (list, fields, categories), model it. Plain text is almost never the right choice.
|
|
36
|
-
5. Assign distinct file scopes per task to avoid conflicts.
|
|
37
|
-
6. Trust the returned data, then verify with tools when correctness matters.
|
|
38
|
-
7. For critical constraints, be explicit in `context`. For general background, subagents can search the parent context file themselves.
|
|
39
|
-
</instruction>
|
|
40
|
-
|
|
41
|
-
<parameters>
|
|
42
|
-
- `agent`: Agent type to use for all tasks
|
|
43
|
-
- `context`: Template with `\{{placeholders}}` for multi-task. Include critical constraints and task-specific decisions. Subagents have access to parent conversation context via a searchable file, so don't repeat everything—focus on what matters. `\{{id}}` and `\{{description}}` are always available.
|
|
44
|
-
- `isolated`: (optional) Run each task in its own git worktree and return patches; patches are applied only if all apply cleanly.
|
|
45
|
-
- `tasks`: Array of `{id, description, args}` - tasks to run in parallel
|
|
46
|
-
- `id`: Short CamelCase identifier (max 32 chars, e.g., "SessionStore", "LspRefactor")
|
|
47
|
-
- `description`: Short human-readable description of what the task does
|
|
48
|
-
- `args`: Object with keys matching `\{{placeholders}}` in context (always include this, even if empty)
|
|
49
|
-
- `skills`: (optional) Array of skill names to preload into this task's system prompt. When set, the skills index section is omitted and the full SKILL.md contents are embedded.
|
|
50
|
-
- `schema`: JTD schema defining expected response structure. **Required.** Use objects with typed properties—e.g., `{ "properties": { "items": { "elements": { "type": "string" } } } }` for lists.
|
|
51
|
-
</parameters>
|
|
52
|
-
|
|
53
|
-
<output>
|
|
54
|
-
Returns task results for each spawned agent:
|
|
55
|
-
- Truncated preview of agent output (use `read agent://<id>` for full content if truncated)
|
|
56
|
-
- Summary with line/character counts
|
|
57
|
-
- For agents with `schema`: structured JSON accessible via `agent://<id>?q=<query>` or `agent://<id>/<path>`
|
|
58
|
-
|
|
59
|
-
Results are keyed by task `id` (e.g., "AuthProvider", "AuthApi").
|
|
60
|
-
</output>
|
|
61
|
-
|
|
62
|
-
<example>
|
|
63
|
-
user: "Looks good, execute the plan"
|
|
64
|
-
assistant: I'll execute the refactoring plan.
|
|
65
|
-
assistant: Uses the Task tool:
|
|
66
|
-
{
|
|
67
|
-
"agent": "task",
|
|
68
|
-
"context": "Refactoring the auth module into separate concerns.\n\nPlan:\n1. AuthProvider - Extract React context and provider from src/auth/index.tsx\n2. AuthApi - Extract API calls to src/auth/api.ts, use existing fetchJson helper\n3. AuthTypes - Move types to types.ts, re-export from index\n\nConstraints:\n- Preserve all existing exports from src/auth/index.tsx\n- Use project's fetchJson (src/utils/http.ts), don't use raw fetch\n- No new dependencies\n\nTask: \{{step}}\n\nFiles: \{{files}}",
|
|
69
|
-
"schema": {
|
|
70
|
-
"properties": {
|
|
71
|
-
"summary": { "type": "string" },
|
|
72
|
-
"decisions": { "elements": { "type": "string" } },
|
|
73
|
-
"concerns": { "elements": { "type": "string" } }
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
"tasks": [
|
|
77
|
-
{ "id": "AuthProvider", "description": "Extract React context", "args": { "step": "Execute step 1: Extract AuthProvider and AuthContext", "files": "src/auth/index.tsx" } },
|
|
78
|
-
{ "id": "AuthApi", "description": "Extract API layer", "args": { "step": "Execute step 2: Extract API calls to api.ts", "files": "src/auth/api.ts" } },
|
|
79
|
-
{ "id": "AuthTypes", "description": "Extract types", "args": { "step": "Execute step 3: Move types to types.ts", "files": "src/auth/types.ts" } }
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
</example>
|
|
83
|
-
|
|
84
191
|
<avoid>
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
192
|
+
- Terse context that requires agents to guess conventions
|
|
193
|
+
- Launching dependent tasks in parallel (bindings + API, consumer + producer)
|
|
194
|
+
- Missing "Existing Code" references - agents need patterns to follow
|
|
195
|
+
- Assuming agents know your codebase - they start fresh each time
|
|
196
|
+
- Describing output format in context instead of schema
|
|
197
|
+
- Single tasks doing too much - prefer focused, file-scoped tasks
|
|
198
|
+
</avoid>
|
|
199
|
+
````
|