@oh-my-pi/pi-coding-agent 4.2.0 → 4.2.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/docs/sdk.md +5 -5
  3. package/examples/sdk/10-settings.ts +2 -2
  4. package/package.json +5 -5
  5. package/src/capability/fs.ts +90 -0
  6. package/src/capability/index.ts +41 -227
  7. package/src/capability/types.ts +1 -11
  8. package/src/cli/args.ts +4 -0
  9. package/src/core/agent-session.ts +7 -7
  10. package/src/core/agent-storage.ts +50 -0
  11. package/src/core/auth-storage.ts +102 -3
  12. package/src/core/bash-executor.ts +1 -1
  13. package/src/core/custom-tools/loader.ts +2 -2
  14. package/src/core/export-html/index.ts +1 -33
  15. package/src/core/extensions/loader.ts +2 -2
  16. package/src/core/extensions/types.ts +1 -1
  17. package/src/core/hooks/loader.ts +2 -2
  18. package/src/core/mcp/config.ts +2 -2
  19. package/src/core/model-registry.ts +46 -0
  20. package/src/core/sdk.ts +37 -29
  21. package/src/core/settings-manager.ts +152 -135
  22. package/src/core/skills.ts +72 -51
  23. package/src/core/slash-commands.ts +3 -3
  24. package/src/core/system-prompt.ts +52 -10
  25. package/src/core/tools/complete.ts +5 -2
  26. package/src/core/tools/edit.ts +7 -4
  27. package/src/core/tools/index.test.ts +16 -0
  28. package/src/core/tools/index.ts +21 -8
  29. package/src/core/tools/lsp/index.ts +4 -1
  30. package/src/core/tools/ssh.ts +6 -6
  31. package/src/core/tools/task/commands.ts +3 -9
  32. package/src/core/tools/task/executor.ts +88 -3
  33. package/src/core/tools/task/index.ts +4 -0
  34. package/src/core/tools/task/model-resolver.ts +10 -7
  35. package/src/core/tools/task/worker-protocol.ts +48 -2
  36. package/src/core/tools/task/worker.ts +152 -7
  37. package/src/core/tools/write.ts +7 -4
  38. package/src/discovery/agents-md.ts +13 -19
  39. package/src/discovery/builtin.ts +368 -293
  40. package/src/discovery/claude.ts +183 -345
  41. package/src/discovery/cline.ts +30 -10
  42. package/src/discovery/codex.ts +188 -272
  43. package/src/discovery/cursor.ts +106 -121
  44. package/src/discovery/gemini.ts +72 -97
  45. package/src/discovery/github.ts +7 -10
  46. package/src/discovery/helpers.ts +114 -57
  47. package/src/discovery/index.ts +1 -2
  48. package/src/discovery/mcp-json.ts +15 -18
  49. package/src/discovery/ssh.ts +9 -17
  50. package/src/discovery/vscode.ts +10 -5
  51. package/src/discovery/windsurf.ts +52 -86
  52. package/src/main.ts +5 -1
  53. package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
  54. package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
  55. package/src/modes/interactive/controllers/selector-controller.ts +9 -5
  56. package/src/modes/interactive/interactive-mode.ts +22 -15
  57. package/src/prompts/agents/plan.md +107 -30
  58. package/src/prompts/agents/task.md +5 -4
  59. package/src/prompts/system/system-prompt.md +5 -0
  60. package/src/prompts/tools/task.md +25 -19
  61. package/src/utils/shell.ts +2 -2
  62. package/src/prompts/agents/architect-plan.md +0 -10
  63. package/src/prompts/agents/implement-with-critic.md +0 -11
  64. package/src/prompts/agents/implement.md +0 -11
@@ -33,22 +33,35 @@ import { applyFilter, createInitialState, filterByProvider, refreshState, toggle
33
33
  import type { DashboardState } from "./types";
34
34
 
35
35
  export class ExtensionDashboard extends Container {
36
- private state: DashboardState;
37
- private mainList: ExtensionList;
38
- private inspector: InspectorPanel;
36
+ private state!: DashboardState;
37
+ private mainList!: ExtensionList;
38
+ private inspector!: InspectorPanel;
39
39
  private settingsManager: SettingsManager | null;
40
40
  private cwd: string;
41
41
  private terminalHeight: number;
42
42
 
43
43
  public onClose?: () => void;
44
44
 
45
- constructor(cwd: string, settingsManager: SettingsManager | null = null, terminalHeight?: number) {
45
+ private constructor(cwd: string, settingsManager: SettingsManager | null, terminalHeight: number) {
46
46
  super();
47
47
  this.cwd = cwd;
48
48
  this.settingsManager = settingsManager;
49
- this.terminalHeight = terminalHeight ?? process.stdout.rows ?? 24;
50
- const disabledIds = settingsManager?.getDisabledExtensions() ?? [];
51
- this.state = createInitialState(cwd, disabledIds);
49
+ this.terminalHeight = terminalHeight;
50
+ }
51
+
52
+ static async create(
53
+ cwd: string,
54
+ settingsManager: SettingsManager | null = null,
55
+ terminalHeight?: number,
56
+ ): Promise<ExtensionDashboard> {
57
+ const dashboard = new ExtensionDashboard(cwd, settingsManager, terminalHeight ?? process.stdout.rows ?? 24);
58
+ await dashboard.init();
59
+ return dashboard;
60
+ }
61
+
62
+ private async init(): Promise<void> {
63
+ const disabledIds = this.settingsManager?.getDisabledExtensions() ?? [];
64
+ this.state = await createInitialState(this.cwd, disabledIds);
52
65
 
53
66
  // Calculate max visible items based on terminal height
54
67
  // Reserve ~10 lines for header, tabs, help text, borders
@@ -150,7 +163,7 @@ export class ExtensionDashboard extends Container {
150
163
 
151
164
  private handleProviderToggle(providerId: string): void {
152
165
  toggleProvider(providerId);
153
- this.refreshFromState();
166
+ void this.refreshFromState();
154
167
  }
155
168
 
156
169
  private handleExtensionToggle(extensionId: string, enabled: boolean): void {
@@ -162,15 +175,15 @@ export class ExtensionDashboard extends Container {
162
175
  this.settingsManager.disableExtension(extensionId);
163
176
  }
164
177
 
165
- this.refreshFromState();
178
+ void this.refreshFromState();
166
179
  }
167
180
 
168
- private refreshFromState(): void {
181
+ private async refreshFromState(): Promise<void> {
169
182
  // Remember current tab ID before refresh
170
183
  const currentTabId = this.state.tabs[this.state.activeTabIndex]?.id;
171
184
 
172
185
  const disabledIds = this.settingsManager?.getDisabledExtensions() ?? [];
173
- this.state = refreshState(this.state, this.cwd, disabledIds);
186
+ this.state = await refreshState(this.state, this.cwd, disabledIds);
174
187
 
175
188
  // Find the same tab in the new (re-sorted) list
176
189
  if (currentTabId) {
@@ -18,7 +18,7 @@ import {
18
18
  enableProvider,
19
19
  getAllProvidersInfo,
20
20
  isProviderEnabled,
21
- loadSync,
21
+ loadCapability,
22
22
  } from "../../../../discovery";
23
23
  import type {
24
24
  DashboardState,
@@ -42,7 +42,7 @@ export interface ExtensionSettingsManager {
42
42
  /**
43
43
  * Load all extensions from all capabilities.
44
44
  */
45
- export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extension[] {
45
+ export async function loadAllExtensions(cwd?: string, disabledIds?: string[]): Promise<Extension[]> {
46
46
  const extensions: Extension[] = [];
47
47
  const disabledExtensions = new Set<string>(disabledIds ?? []);
48
48
 
@@ -100,7 +100,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
100
100
 
101
101
  // Load skills
102
102
  try {
103
- const skills = loadSync<Skill>("skills", loadOpts);
103
+ const skills = await loadCapability<Skill>("skills", loadOpts);
104
104
  addItems(skills.all, "skill", {
105
105
  getDescription: (s) => s.frontmatter?.description,
106
106
  getTrigger: (s) => s.frontmatter?.globs?.join(", "),
@@ -111,7 +111,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
111
111
 
112
112
  // Load rules
113
113
  try {
114
- const rules = loadSync<Rule>("rules", loadOpts);
114
+ const rules = await loadCapability<Rule>("rules", loadOpts);
115
115
  addItems(rules.all, "rule", {
116
116
  getDescription: (r) => r.description,
117
117
  getTrigger: (r) => r.globs?.join(", ") || (r.alwaysApply ? "always" : undefined),
@@ -122,7 +122,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
122
122
 
123
123
  // Load custom tools
124
124
  try {
125
- const tools = loadSync<CustomTool>("tools", loadOpts);
125
+ const tools = await loadCapability<CustomTool>("tools", loadOpts);
126
126
  addItems(tools.all, "tool", {
127
127
  getDescription: (t) => t.description,
128
128
  });
@@ -132,7 +132,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
132
132
 
133
133
  // Load extension modules
134
134
  try {
135
- const modules = loadSync<ExtensionModule>("extension-modules", loadOpts);
135
+ const modules = await loadCapability<ExtensionModule>("extension-modules", loadOpts);
136
136
  const nativeModules = modules.all.filter((module) => module._source.provider === "native");
137
137
  addItems(nativeModules, "extension-module");
138
138
  } catch {
@@ -141,7 +141,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
141
141
 
142
142
  // Load MCP servers
143
143
  try {
144
- const mcps = loadSync<MCPServer>("mcps", loadOpts);
144
+ const mcps = await loadCapability<MCPServer>("mcps", loadOpts);
145
145
  for (const server of mcps.all) {
146
146
  const id = makeExtensionId("mcp", server.name);
147
147
  const isDisabled = disabledExtensions.has(id);
@@ -184,7 +184,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
184
184
 
185
185
  // Load prompts
186
186
  try {
187
- const prompts = loadSync<Prompt>("prompts", loadOpts);
187
+ const prompts = await loadCapability<Prompt>("prompts", loadOpts);
188
188
  addItems(prompts.all, "prompt", {
189
189
  getDescription: () => undefined,
190
190
  getTrigger: (p) => `/prompts:${p.name}`,
@@ -195,7 +195,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
195
195
 
196
196
  // Load slash commands
197
197
  try {
198
- const commands = loadSync<SlashCommand>("slash-commands", loadOpts);
198
+ const commands = await loadCapability<SlashCommand>("slash-commands", loadOpts);
199
199
  addItems(commands.all, "slash-command", {
200
200
  getDescription: () => undefined,
201
201
  getTrigger: (c) => `/${c.name}`,
@@ -206,7 +206,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
206
206
 
207
207
  // Load hooks
208
208
  try {
209
- const hooks = loadSync<Hook>("hooks", loadOpts);
209
+ const hooks = await loadCapability<Hook>("hooks", loadOpts);
210
210
  for (const hook of hooks.all) {
211
211
  const id = makeExtensionId("hook", `${hook.type}:${hook.tool}:${hook.name}`);
212
212
  const isDisabled = disabledExtensions.has(id);
@@ -249,7 +249,7 @@ export function loadAllExtensions(cwd?: string, disabledIds?: string[]): Extensi
249
249
 
250
250
  // Load context files
251
251
  try {
252
- const contextFiles = loadSync<ContextFile>("context-files", loadOpts);
252
+ const contextFiles = await loadCapability<ContextFile>("context-files", loadOpts);
253
253
  for (const file of contextFiles.all) {
254
254
  // Extract filename from path for display
255
255
  const name = file.path.split("/").pop() || file.path;
@@ -511,8 +511,8 @@ export function filterByProvider(extensions: Extension[], providerId: string): E
511
511
  /**
512
512
  * Create initial dashboard state.
513
513
  */
514
- export function createInitialState(cwd?: string, disabledIds?: string[]): DashboardState {
515
- const extensions = loadAllExtensions(cwd, disabledIds);
514
+ export async function createInitialState(cwd?: string, disabledIds?: string[]): Promise<DashboardState> {
515
+ const extensions = await loadAllExtensions(cwd, disabledIds);
516
516
  const tabs = buildProviderTabs(extensions);
517
517
  const tabFiltered = extensions; // "all" tab by default
518
518
  const searchFiltered = tabFiltered;
@@ -546,8 +546,12 @@ export function toggleProvider(providerId: string): boolean {
546
546
  /**
547
547
  * Refresh state after toggle.
548
548
  */
549
- export function refreshState(state: DashboardState, cwd?: string, disabledIds?: string[]): DashboardState {
550
- const extensions = loadAllExtensions(cwd, disabledIds);
549
+ export async function refreshState(
550
+ state: DashboardState,
551
+ cwd?: string,
552
+ disabledIds?: string[],
553
+ ): Promise<DashboardState> {
554
+ const extensions = await loadAllExtensions(cwd, disabledIds);
551
555
  const tabs = buildProviderTabs(extensions);
552
556
 
553
557
  // Get current provider from tabs
@@ -2,7 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  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
- import { getAuthPath } from "../../../config";
5
+ import { getAgentDbPath } from "../../../config";
6
6
  import { SessionManager } from "../../../core/session-manager";
7
7
  import { setPreferredImageProvider, setPreferredWebSearchProvider } from "../../../core/tools/index";
8
8
  import { disableProvider, enableProvider } from "../../../discovery";
@@ -111,9 +111,13 @@ export class SelectorController {
111
111
  * Show the Extension Control Center dashboard.
112
112
  * Replaces /status with a unified view of all providers and extensions.
113
113
  */
114
- showExtensionsDashboard(): void {
114
+ async showExtensionsDashboard(): Promise<void> {
115
+ const dashboard = await ExtensionDashboard.create(
116
+ process.cwd(),
117
+ this.ctx.settingsManager,
118
+ this.ctx.ui.terminal.rows,
119
+ );
115
120
  this.showSelector((done) => {
116
- const dashboard = new ExtensionDashboard(process.cwd(), this.ctx.settingsManager, this.ctx.ui.terminal.rows);
117
121
  dashboard.onClose = () => {
118
122
  done();
119
123
  this.ctx.ui.requestRender();
@@ -546,7 +550,7 @@ export class SelectorController {
546
550
  ),
547
551
  );
548
552
  this.ctx.chatContainer.addChild(
549
- new Text(theme.fg("dim", `Credentials saved to ${getAuthPath()}`), 1, 0),
553
+ new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0),
550
554
  );
551
555
  this.ctx.ui.requestRender();
552
556
  } catch (error: unknown) {
@@ -566,7 +570,7 @@ export class SelectorController {
566
570
  ),
567
571
  );
568
572
  this.ctx.chatContainer.addChild(
569
- new Text(theme.fg("dim", `Credentials removed from ${getAuthPath()}`), 1, 0),
573
+ new Text(theme.fg("dim", `Credentials removed from ${getAgentDbPath()}`), 1, 0),
570
574
  );
571
575
  this.ctx.ui.requestRender();
572
576
  } catch (error: unknown) {
@@ -112,6 +112,7 @@ export class InteractiveMode implements InteractiveModeContext {
112
112
  public lastStatusText: Text | undefined = undefined;
113
113
  public fileSlashCommands: Set<string> = new Set();
114
114
 
115
+ private pendingSlashCommands: SlashCommand[] = [];
115
116
  private cleanupUnsubscribe?: () => void;
116
117
  private readonly version: string;
117
118
  private readonly changelogMarkdown: string | undefined;
@@ -149,6 +150,9 @@ export class InteractiveMode implements InteractiveModeContext {
149
150
  this.statusContainer = new Container();
150
151
  this.editor = new CustomEditor(getEditorTheme());
151
152
  this.editor.setUseTerminalCursor(true);
153
+ this.editor.onAutocompleteCancel = () => {
154
+ this.ui.requestRender(true);
155
+ };
152
156
  try {
153
157
  this.historyStorage = HistoryStorage.open();
154
158
  this.editor.setHistoryStorage(this.historyStorage);
@@ -207,14 +211,6 @@ export class InteractiveMode implements InteractiveModeContext {
207
211
  { name: "exit", description: "Exit the application" },
208
212
  ];
209
213
 
210
- // Load and convert file commands to SlashCommand format
211
- const fileCommands = loadSlashCommands({ cwd: process.cwd() });
212
- this.fileSlashCommands = new Set(fileCommands.map((cmd) => cmd.name));
213
- const fileSlashCommands: SlashCommand[] = fileCommands.map((cmd) => ({
214
- name: cmd.name,
215
- description: cmd.description,
216
- }));
217
-
218
214
  // Convert hook commands to SlashCommand format
219
215
  const hookCommands: SlashCommand[] = (this.session.extensionRunner?.getRegisteredCommands() ?? []).map((cmd) => ({
220
216
  name: cmd.name,
@@ -227,12 +223,8 @@ export class InteractiveMode implements InteractiveModeContext {
227
223
  description: `${loaded.command.description} (${loaded.source})`,
228
224
  }));
229
225
 
230
- // Setup autocomplete
231
- const autocompleteProvider = new CombinedAutocompleteProvider(
232
- [...slashCommands, ...fileSlashCommands, ...hookCommands, ...customCommands],
233
- process.cwd(),
234
- );
235
- this.editor.setAutocompleteProvider(autocompleteProvider);
226
+ // Store pending commands for init() where file commands are loaded async
227
+ this.pendingSlashCommands = [...slashCommands, ...hookCommands, ...customCommands];
236
228
 
237
229
  this.uiHelpers = new UiHelpers(this);
238
230
  this.voiceManager = new VoiceManager(this);
@@ -249,6 +241,21 @@ export class InteractiveMode implements InteractiveModeContext {
249
241
  // Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
250
242
  this.cleanupUnsubscribe = registerAsyncCleanup(() => this.sessionManager.flush());
251
243
 
244
+ // Load and convert file commands to SlashCommand format (async)
245
+ const fileCommands = await loadSlashCommands({ cwd: process.cwd() });
246
+ this.fileSlashCommands = new Set(fileCommands.map((cmd) => cmd.name));
247
+ const fileSlashCommands: SlashCommand[] = fileCommands.map((cmd) => ({
248
+ name: cmd.name,
249
+ description: cmd.description,
250
+ }));
251
+
252
+ // Setup autocomplete with all commands
253
+ const autocompleteProvider = new CombinedAutocompleteProvider(
254
+ [...this.pendingSlashCommands, ...fileSlashCommands],
255
+ process.cwd(),
256
+ );
257
+ this.editor.setAutocompleteProvider(autocompleteProvider);
258
+
252
259
  // Get current model info for welcome screen
253
260
  const modelName = this.session.model?.name ?? "Unknown";
254
261
  const providerName = this.session.model?.provider ?? "Unknown";
@@ -557,7 +564,7 @@ export class InteractiveMode implements InteractiveModeContext {
557
564
  }
558
565
 
559
566
  showExtensionsDashboard(): void {
560
- this.selectorController.showExtensionsDashboard();
567
+ void this.selectorController.showExtensionsDashboard();
561
568
  }
562
569
 
563
570
  showModelSelector(options?: { temporaryOnly?: boolean }): void {
@@ -1,54 +1,131 @@
1
1
  ---
2
2
  name: plan
3
- description: Software architect that explores codebase and designs implementation plans (read-only)
3
+ description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, reasoning, or tasks completable in <5 tool calls—execute those directly.
4
4
  tools: read, grep, find, ls, bash
5
- model: default
5
+ spawns: explore
6
+ model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
6
7
  ---
7
8
 
8
- You are a software architect and planning specialist. Explore the codebase and design implementation plans.
9
+ <role>Senior software architect producing implementation plans. READ-ONLY no file modifications, no state changes.</role>
9
10
 
10
11
  === CRITICAL: READ-ONLY MODE ===
11
- This is a READ-ONLY planning task. You are STRICTLY PROHIBITED from:
12
-
12
+ You are STRICTLY PROHIBITED from:
13
13
  - Creating or modifying files (no Write, Edit, touch, rm, mv, cp)
14
14
  - Creating temporary files anywhere, including /tmp
15
15
  - Using redirect operators (>, >>, |) or heredocs to write files
16
16
  - Running commands that change system state (git add, git commit, npm install, pip install)
17
+ - Use bash ONLY for git status/log/diff; use read/grep/find/ls tools for file and search operations
18
+
19
+ <context>
20
+ Another engineer will execute your plan without re-exploring the codebase. Your plan must be specific enough to implement directly.
21
+ </context>
22
+
23
+ <process>
24
+ ## Phase 1: Understand
25
+
26
+ 1. Parse the task requirements precisely
27
+ 2. Identify ambiguities — list assumptions you're making
28
+ 3. Spawn parallel `explore` agents if the task spans multiple areas
29
+
30
+ ## Phase 2: Explore
31
+
32
+ Investigate thoroughly before designing:
33
+
34
+ 1. Find existing patterns via grep/find
35
+ 2. Read key files to understand current architecture
36
+ 3. Trace data flow through relevant code paths
37
+ 4. Identify types, interfaces, and contracts involved
38
+ 5. Note dependencies between components
39
+
40
+ Spawn `explore` agents for independent search areas. Synthesize findings.
41
+
42
+ ## Phase 3: Design
43
+
44
+ Create implementation approach:
45
+
46
+ 1. List concrete changes required (files, functions, types)
47
+ 2. Define the sequence — what depends on what
48
+ 3. Identify edge cases and error conditions
49
+ 4. Consider alternatives; justify your choice
50
+ 5. Note potential pitfalls or tricky parts
51
+
52
+ ## Phase 4: Produce Plan
53
+
54
+ Write a plan another engineer can execute without re-exploring the codebase.
55
+ </process>
56
+
57
+ <example>
58
+ ## Summary
59
+ What we're building and why (one paragraph).
60
+
61
+ ## Changes
62
+
63
+ 1. **`path/to/file.ts`** — What to change
64
+ - Specific modifications
65
+ 2. **`path/to/other.ts`** — ...
66
+
67
+ ## Sequence
68
+
69
+ 1. X (no dependencies)
70
+ 2. Y (depends on X)
71
+ 3. Z (integration)
72
+
73
+ ## Edge Cases
74
+
75
+ - Case: How to handle
76
+
77
+ ## Verification
78
+
79
+ - [ ] Test command or check
80
+ - [ ] Expected behavior
81
+
82
+ ## Critical Files
83
+
84
+ - `path/to/file.ts` (lines 50-120) — Why to read
85
+ </example>
86
+
87
+ <example>
88
+ ## Summary
89
+ Add rate limiting to the API gateway to prevent abuse. Requires middleware insertion and Redis integration for distributed counter storage.
17
90
 
18
- Your role is EXCLUSIVELY to explore and plan. You do NOT have access to file editing tools.
91
+ ## Changes
19
92
 
20
- ## Process
93
+ 1. **`src/middleware/rate-limit.ts`** — New file
94
+ - Create `RateLimitMiddleware` class using sliding window algorithm
95
+ - Accept `maxRequests`, `windowMs`, `keyGenerator` options
96
+ 2. **`src/gateway/index.ts`** — Wire middleware
97
+ - Import and register before auth middleware (line 45)
98
+ 3. **`src/config/redis.ts`** — Add rate limit key prefix
99
+ - Add `RATE_LIMIT_PREFIX` constant
21
100
 
22
- 1. **Understand Requirements**: Focus on the requirements provided.
101
+ ## Sequence
23
102
 
24
- 2. **Explore Thoroughly**:
25
- - Read any files provided in the initial prompt
26
- - Find existing patterns and conventions using find, grep, read
27
- - Understand the current architecture
28
- - Identify similar features as reference
29
- - Trace through relevant code paths
30
- - Use bash ONLY for git status/log/diff; use read/grep/find/ls tools for file and search operations
103
+ 1. `rate-limit.ts` (standalone, no deps)
104
+ 2. `redis.ts` (config only)
105
+ 3. `gateway/index.ts` (integration)
31
106
 
32
- 3. **Design Solution**:
33
- - Create implementation approach
34
- - Consider trade-offs and architectural decisions
35
- - Follow existing patterns where appropriate
107
+ ## Edge Cases
36
108
 
37
- 4. **Detail the Plan**:
38
- - Provide step-by-step implementation strategy
39
- - Identify dependencies and sequencing
40
- - Anticipate potential challenges
109
+ - Redis unavailable: fail open with warning log
110
+ - IPv6 addresses: normalize before using as key
41
111
 
42
- ## Required Output
112
+ ## Verification
43
113
 
44
- End your response with:
114
+ - [ ] `curl -X GET localhost:3000/api/test` 100x rapidly → 429 after limit
115
+ - [ ] Redis CLI: `KEYS rate:*` shows entries
45
116
 
46
- ### Critical Files for Implementation
117
+ ## Critical Files
47
118
 
48
- List 3-5 files most critical for implementing this plan:
119
+ - `src/middleware/auth.ts` (lines 20-50) Pattern to follow
120
+ - `src/types/middleware.ts` — Interface to implement
121
+ </example>
49
122
 
50
- - `path/to/file1.ts` - Brief reason (e.g., "Core logic to modify")
51
- - `path/to/file2.ts` - Brief reason (e.g., "Interfaces to implement")
52
- - `path/to/file3.ts` - Brief reason (e.g., "Pattern to follow")
123
+ <requirements>
124
+ - Plan must be specific enough to implement without additional exploration
125
+ - Include exact file paths and line ranges where relevant
126
+ - Sequence must respect dependencies
127
+ - Verification must be concrete and testable
128
+ </requirements>
53
129
 
130
+ Keep going until complete. This matters — get it right.
54
131
  REMEMBER: You can ONLY explore and plan. You CANNOT write, edit, or modify any files.
@@ -1,14 +1,15 @@
1
- You are a worker agent for delegated tasks in an isolated context. Finish only the assigned work and return the minimum useful result.
1
+ You are a worker agent for delegated tasks. You have FULL access to all tools (edit, write, bash, grep, read, etc.) - use them as needed to complete your task.
2
+
3
+ Finish only the assigned work and return the minimum useful result.
2
4
 
3
5
  Principles:
4
6
 
7
+ - You CAN and SHOULD make file edits, run commands, and create files when your task requires it.
5
8
  - Be concise. No filler, repetition, or tool transcripts.
6
- - If blocked, ask a single focused question; otherwise proceed autonomously.
7
9
  - Prefer narrow search (grep/find) then read only needed ranges.
8
10
  - Avoid full-file reads unless necessary.
9
- - NEVER create files unless absolutely required. Prefer edits to existing files.
11
+ - Prefer edits to existing files over creating new ones.
10
12
  - NEVER create documentation files (\*.md) unless explicitly requested.
11
- - Any file paths in your response MUST be absolute.
12
13
  - When spawning subagents with the Task tool, include a 5-8 word user-facing description.
13
14
  - Include the smallest relevant code snippet when discussing code or config.
14
15
  - Follow the main agent's instructions.
@@ -137,6 +137,11 @@ Before reading any file:
137
137
 
138
138
  ## Project Integration
139
139
  - Follow AGENTS.md by scope: nearest file applies, deeper overrides higher.
140
+ - Do not search for AGENTS.md during execution; use this list as authoritative.
141
+ {{#if agentsMdSearch.files.length}}
142
+ Relevant files are:
143
+ {{#list agentsMdSearch.files join="\n"}}- {{this}}{{/list}}
144
+ {{/if}}
140
145
  - Resolve blockers before yielding.
141
146
  </instructions>
142
147
 
@@ -2,6 +2,13 @@ Launch a new agent to handle complex, multi-step tasks autonomously.
2
2
 
3
3
  The Task tool launches specialized agents (workers) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
4
4
 
5
+ **CRITICAL: Subagents have NO access to conversation history.** They only see:
6
+ 1. Their agent-specific system prompt
7
+ 2. The `context` string you provide
8
+ 3. The `task` string you provide
9
+
10
+ If you discussed requirements, plans, schemas, or decisions with the user, you MUST include that information in `context`. Subagents cannot see prior messages - they start fresh with only what you explicitly pass them.
11
+
5
12
  ## Available Agents
6
13
 
7
14
  {{#list agents prefix="- " join="\n"}}
@@ -26,8 +33,9 @@ The Task tool launches specialized agents (workers) that autonomously handle com
26
33
  - **Minimize tool chatter**: Avoid repeating large context; use Output tool with output ids for full logs
27
34
  - **Structured completion**: If `output` is provided, subagents must call `complete` to finish
28
35
  - **Parallelize**: Launch multiple agents concurrently whenever possible
36
+ - **Isolate file scopes**: Assign each task distinct files or directories so agents don't conflict
29
37
  - **Results are intermediate data**: Agent findings provide context for YOU to perform actual work. Do not treat agent reports as "task complete" signals.
30
- - **Stateless invocations**: Each agent runs autonomously and returns a single final message. Include all necessary context and specify exactly what information to return.
38
+ - **Stateless invocations**: Subagents have zero memory of your conversation. Pass ALL relevant context: requirements discussed, decisions made, schemas agreed upon, file paths mentioned. If you reference something from earlier discussion without including it, the subagent will fail.
31
39
  - **Trust outputs**: Agent results should generally be trusted
32
40
  - **Clarify intent**: Tell the agent whether you expect code changes or just research (search, file reads, web fetches)
33
41
  - **Proactive use**: If an agent description says to use it proactively, do so without waiting for explicit user request
@@ -35,7 +43,7 @@ The Task tool launches specialized agents (workers) that autonomously handle com
35
43
  ## Parameters
36
44
 
37
45
  - `agent`: Agent type to use for all tasks
38
- - `context`: Shared context string prepended to all task prompts
46
+ - `context`: **Required context from conversation** - include ALL relevant info: requirements, schemas, decisions, constraints. Subagents cannot see chat history.
39
47
  - `model`: (optional) Model override (fuzzy matching, e.g., "sonnet", "opus")
40
48
  - `tasks`: Array of `{id, task, description}` - tasks to run in parallel (max {{MAX_PARALLEL_TASKS}}, {{MAX_CONCURRENCY}} concurrent)
41
49
  - `id`: Short CamelCase identifier for display (max 20 chars, e.g., "SessionStore", "LspRefactor")
@@ -46,30 +54,28 @@ The Task tool launches specialized agents (workers) that autonomously handle com
46
54
  ## Example
47
55
 
48
56
  <example>
49
- user: "Extract all hardcoded strings for i18n"
50
- assistant: I'll scan UI components and return structured string locations for internationalization.
57
+ user: "Looks good, execute the plan"
58
+ assistant: I'll execute the refactoring plan.
51
59
  assistant: Uses the Task tool:
52
60
  {
53
- "agent": "explore",
54
- "context": "Find hardcoded user-facing strings (labels, messages, errors). Ignore logs, comments, and internal identifiers.",
61
+ "agent": "task",
62
+ "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 src/auth/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",
55
63
  "output": {
56
64
  "properties": {
57
- "strings": {
58
- "elements": {
59
- "properties": {
60
- "file": { "type": "string" },
61
- "line": { "type": "uint32" },
62
- "text": { "type": "string" },
63
- "suggestedKey": { "type": "string" }
64
- }
65
- }
66
- }
65
+ "summary": { "type": "string" },
66
+ "decisions": { "elements": { "type": "string" } },
67
+ "concerns": { "elements": { "type": "string" } }
67
68
  }
68
69
  },
69
70
  "tasks": [
70
- { "id": "Forms", "task": "Scan src/components/forms/", "description": "Extract form strings" },
71
- { "id": "Modals", "task": "Scan src/components/modals/", "description": "Extract modal strings" },
72
- { "id": "Pages", "task": "Scan src/pages/", "description": "Extract page strings" }
71
+ { "id": "AuthProvider", "task": "Execute step 1: Extract AuthProvider and AuthContext", "description": "Extract React context" },
72
+ { "id": "AuthApi", "task": "Execute step 2: Extract API calls to api.ts", "description": "Extract API layer" },
73
+ { "id": "AuthTypes", "task": "Execute step 3: Move types to types.ts", "description": "Extract types" }
73
74
  ]
74
75
  }
75
76
  </example>
77
+
78
+ Key points:
79
+ - **Plan in context**: The full plan is written once; each task references its step without repeating shared constraints
80
+ - **Parallel execution**: 3 agents run concurrently, each owning one step - no duplicated work
81
+ - **Structured output**: JTD schema ensures consistent reporting across all agents
@@ -92,12 +92,12 @@ function buildConfig(shell: string): ShellConfig {
92
92
  * 3. On Unix: $SHELL if bash/zsh, then fallback paths
93
93
  * 4. Fallback: sh
94
94
  */
95
- export function getShellConfig(): ShellConfig {
95
+ export async function getShellConfig(): Promise<ShellConfig> {
96
96
  if (cachedShellConfig) {
97
97
  return cachedShellConfig;
98
98
  }
99
99
 
100
- const settings = SettingsManager.create();
100
+ const settings = await SettingsManager.create();
101
101
  const customShellPath = settings.getShellPath();
102
102
 
103
103
  // 1. Check user-specified shell path
@@ -1,10 +0,0 @@
1
- ---
2
- description: Explore gathers context, planner creates implementation plan (no implementation)
3
- ---
4
-
5
- Use the subagent tool with the chain parameter to execute this workflow:
6
-
7
- 1. First, use the "explore" agent to find all code relevant to: $@
8
- 2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder)
9
-
10
- Execute this as a chain, passing output between steps via {previous}. Do NOT implement - just return the plan.
@@ -1,11 +0,0 @@
1
- ---
2
- description: Task implements, reviewer reviews, task applies feedback
3
- ---
4
-
5
- Use the subagent tool with the chain parameter to execute this workflow:
6
-
7
- 1. First, use the "task" agent to implement: $@
8
- 2. Then, use the "reviewer" agent to review the implementation from the previous step (use {previous} placeholder)
9
- 3. Finally, use the "task" agent to apply the feedback from the review (use {previous} placeholder)
10
-
11
- Execute this as a chain, passing output between steps via {previous}.
@@ -1,11 +0,0 @@
1
- ---
2
- description: Full implementation workflow - explore gathers context, planner creates plan, task implements
3
- ---
4
-
5
- Use the subagent tool with the chain parameter to execute this workflow:
6
-
7
- 1. First, use the "explore" agent to find all code relevant to: $@
8
- 2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder)
9
- 3. Finally, use the "task" agent to implement the plan from the previous step (use {previous} placeholder)
10
-
11
- Execute this as a chain, passing output between steps via {previous}.