@oh-my-pi/pi-coding-agent 4.0.1 → 4.2.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +2 -1
  3. package/docs/sdk.md +0 -3
  4. package/package.json +6 -5
  5. package/src/config.ts +9 -0
  6. package/src/core/agent-storage.ts +450 -0
  7. package/src/core/auth-storage.ts +111 -184
  8. package/src/core/compaction/branch-summarization.ts +5 -4
  9. package/src/core/compaction/compaction.ts +7 -6
  10. package/src/core/compaction/utils.ts +6 -11
  11. package/src/core/custom-commands/bundled/review/index.ts +22 -94
  12. package/src/core/custom-share.ts +66 -0
  13. package/src/core/history-storage.ts +174 -0
  14. package/src/core/index.ts +1 -0
  15. package/src/core/keybindings.ts +3 -0
  16. package/src/core/prompt-templates.ts +271 -1
  17. package/src/core/sdk.ts +14 -3
  18. package/src/core/settings-manager.ts +100 -34
  19. package/src/core/slash-commands.ts +4 -1
  20. package/src/core/storage-migration.ts +215 -0
  21. package/src/core/system-prompt.ts +87 -289
  22. package/src/core/title-generator.ts +3 -2
  23. package/src/core/tools/ask.ts +2 -2
  24. package/src/core/tools/bash.ts +2 -1
  25. package/src/core/tools/calculator.ts +2 -1
  26. package/src/core/tools/edit.ts +2 -1
  27. package/src/core/tools/find.ts +2 -1
  28. package/src/core/tools/gemini-image.ts +2 -1
  29. package/src/core/tools/git.ts +2 -2
  30. package/src/core/tools/grep.ts +2 -1
  31. package/src/core/tools/index.test.ts +0 -28
  32. package/src/core/tools/index.ts +0 -6
  33. package/src/core/tools/lsp/index.ts +2 -1
  34. package/src/core/tools/output.ts +2 -1
  35. package/src/core/tools/read.ts +4 -1
  36. package/src/core/tools/ssh.ts +4 -2
  37. package/src/core/tools/task/agents.ts +56 -30
  38. package/src/core/tools/task/commands.ts +9 -8
  39. package/src/core/tools/task/index.ts +7 -15
  40. package/src/core/tools/web-fetch.ts +2 -1
  41. package/src/core/tools/web-search/auth.ts +106 -16
  42. package/src/core/tools/web-search/index.ts +3 -2
  43. package/src/core/tools/web-search/providers/anthropic.ts +44 -6
  44. package/src/core/tools/write.ts +2 -1
  45. package/src/core/voice.ts +3 -1
  46. package/src/main.ts +1 -1
  47. package/src/migrations.ts +20 -20
  48. package/src/modes/interactive/components/custom-editor.ts +7 -0
  49. package/src/modes/interactive/components/history-search.ts +158 -0
  50. package/src/modes/interactive/controllers/command-controller.ts +527 -0
  51. package/src/modes/interactive/controllers/event-controller.ts +340 -0
  52. package/src/modes/interactive/controllers/extension-ui-controller.ts +600 -0
  53. package/src/modes/interactive/controllers/input-controller.ts +585 -0
  54. package/src/modes/interactive/controllers/selector-controller.ts +585 -0
  55. package/src/modes/interactive/interactive-mode.ts +370 -3115
  56. package/src/modes/interactive/theme/theme.ts +5 -5
  57. package/src/modes/interactive/types.ts +189 -0
  58. package/src/modes/interactive/utils/ui-helpers.ts +449 -0
  59. package/src/modes/interactive/utils/voice-manager.ts +96 -0
  60. package/src/prompts/{explore.md → agents/explore.md} +7 -5
  61. package/src/prompts/agents/frontmatter.md +7 -0
  62. package/src/prompts/{plan.md → agents/plan.md} +3 -3
  63. package/src/prompts/{task.md → agents/task.md} +1 -1
  64. package/src/prompts/review-request.md +44 -8
  65. package/src/prompts/system/custom-system-prompt.md +80 -0
  66. package/src/prompts/system/file-operations.md +12 -0
  67. package/src/prompts/system/system-prompt.md +232 -0
  68. package/src/prompts/system/title-system.md +2 -0
  69. package/src/prompts/tools/bash.md +1 -1
  70. package/src/prompts/tools/read.md +1 -1
  71. package/src/prompts/tools/task.md +9 -3
  72. package/src/core/tools/rulebook.ts +0 -132
  73. package/src/prompts/system-prompt.md +0 -43
  74. package/src/prompts/title-system.md +0 -8
  75. /package/src/prompts/{architect-plan.md → agents/architect-plan.md} +0 -0
  76. /package/src/prompts/{implement-with-critic.md → agents/implement-with-critic.md} +0 -0
  77. /package/src/prompts/{implement.md → agents/implement.md} +0 -0
  78. /package/src/prompts/{init.md → agents/init.md} +0 -0
  79. /package/src/prompts/{reviewer.md → agents/reviewer.md} +0 -0
  80. /package/src/prompts/{branch-summary-preamble.md → compaction/branch-summary-preamble.md} +0 -0
  81. /package/src/prompts/{branch-summary.md → compaction/branch-summary.md} +0 -0
  82. /package/src/prompts/{compaction-summary.md → compaction/compaction-summary.md} +0 -0
  83. /package/src/prompts/{compaction-turn-prefix.md → compaction/compaction-turn-prefix.md} +0 -0
  84. /package/src/prompts/{compaction-update-summary.md → compaction/compaction-update-summary.md} +0 -0
  85. /package/src/prompts/{summarization-system.md → system/summarization-system.md} +0 -0
@@ -4,12 +4,13 @@
4
4
 
5
5
  import type { Api, Model } from "@oh-my-pi/pi-ai";
6
6
  import { completeSimple } from "@oh-my-pi/pi-ai";
7
- import titleSystemPrompt from "../prompts/title-system.md" with { type: "text" };
7
+ import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
8
8
  import { logger } from "./logger";
9
9
  import type { ModelRegistry } from "./model-registry";
10
10
  import { parseModelString, SMOL_MODEL_PRIORITY } from "./model-resolver";
11
+ import { renderPromptTemplate } from "./prompt-templates";
11
12
 
12
- const TITLE_SYSTEM_PROMPT = titleSystemPrompt;
13
+ const TITLE_SYSTEM_PROMPT = renderPromptTemplate(titleSystemPrompt);
13
14
 
14
15
  const MAX_INPUT_CHARS = 2000;
15
16
 
@@ -22,6 +22,7 @@ import { Type } from "@sinclair/typebox";
22
22
  import { type Theme, theme } from "../../modes/interactive/theme/theme";
23
23
  import askDescription from "../../prompts/tools/ask.md" with { type: "text" };
24
24
  import type { RenderResultOptions } from "../custom-tools/types";
25
+ import { renderPromptTemplate } from "../prompt-templates";
25
26
  import type { ToolSession } from "./index";
26
27
  import { createToolUIKit } from "./render-utils";
27
28
 
@@ -75,7 +76,7 @@ export function createAskTool(session: ToolSession): null | AgentTool<typeof ask
75
76
  return {
76
77
  name: "ask",
77
78
  label: "Ask",
78
- description: askDescription,
79
+ description: renderPromptTemplate(askDescription),
79
80
  parameters: askSchema,
80
81
 
81
82
  async execute(
@@ -201,7 +202,6 @@ export function createAskTool(session: ToolSession): null | AgentTool<typeof ask
201
202
  export const askTool = createAskTool({
202
203
  cwd: process.cwd(),
203
204
  hasUI: false,
204
- rulebookRules: [],
205
205
  getSessionFile: () => null,
206
206
  getSessionSpawns: () => "*",
207
207
  });
@@ -8,6 +8,7 @@ import type { Theme } from "../../modes/interactive/theme/theme";
8
8
  import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
9
9
  import { type BashExecutorOptions, executeBash, executeBashWithOperations } from "../bash-executor";
10
10
  import type { RenderResultOptions } from "../custom-tools/types";
11
+ import { renderPromptTemplate } from "../prompt-templates";
11
12
  import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor";
12
13
  import type { ToolSession } from "./index";
13
14
  import { resolveToCwd } from "./path-utils";
@@ -52,7 +53,7 @@ export function createBashTool(session: ToolSession, options?: BashToolOptions):
52
53
  return {
53
54
  name: "bash",
54
55
  label: "Bash",
55
- description: bashDescription,
56
+ description: renderPromptTemplate(bashDescription),
56
57
  parameters: bashSchema,
57
58
  execute: async (
58
59
  _toolCallId: string,
@@ -5,6 +5,7 @@ import { Type } from "@sinclair/typebox";
5
5
  import type { Theme } from "../../modes/interactive/theme/theme";
6
6
  import calculatorDescription from "../../prompts/tools/calculator.md" with { type: "text" };
7
7
  import type { RenderResultOptions } from "../custom-tools/types";
8
+ import { renderPromptTemplate } from "../prompt-templates";
8
9
  import { untilAborted } from "../utils";
9
10
  import type { ToolSession } from "./index";
10
11
  import {
@@ -393,7 +394,7 @@ export function createCalculatorTool(_session: ToolSession): AgentTool<typeof ca
393
394
  return {
394
395
  name: "calc",
395
396
  label: "Calc",
396
- description: calculatorDescription,
397
+ description: renderPromptTemplate(calculatorDescription),
397
398
  parameters: calculatorSchema,
398
399
  execute: async (
399
400
  _toolCallId: string,
@@ -5,6 +5,7 @@ import { Type } from "@sinclair/typebox";
5
5
  import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
6
6
  import editDescription from "../../prompts/tools/edit.md" with { type: "text" };
7
7
  import type { RenderResultOptions } from "../custom-tools/types";
8
+ import { renderPromptTemplate } from "../prompt-templates";
8
9
  import {
9
10
  DEFAULT_FUZZY_THRESHOLD,
10
11
  detectLineEnding,
@@ -48,7 +49,7 @@ export function createEditTool(session: ToolSession): AgentTool<typeof editSchem
48
49
  return {
49
50
  name: "edit",
50
51
  label: "Edit",
51
- description: editDescription,
52
+ description: renderPromptTemplate(editDescription),
52
53
  parameters: editSchema,
53
54
  execute: async (
54
55
  _toolCallId: string,
@@ -7,6 +7,7 @@ import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/t
7
7
  import findDescription from "../../prompts/tools/find.md" with { type: "text" };
8
8
  import { ensureTool } from "../../utils/tools-manager";
9
9
  import type { RenderResultOptions } from "../custom-tools/types";
10
+ import { renderPromptTemplate } from "../prompt-templates";
10
11
  import { ScopeSignal, untilAborted } from "../utils";
11
12
  import type { ToolSession } from "./index";
12
13
  import { resolveToCwd } from "./path-utils";
@@ -113,7 +114,7 @@ export function createFindTool(session: ToolSession, options?: FindToolOptions):
113
114
  return {
114
115
  name: "find",
115
116
  label: "Find",
116
- description: findDescription,
117
+ description: renderPromptTemplate(findDescription),
117
118
  parameters: findSchema,
118
119
  execute: async (
119
120
  _toolCallId: string,
@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
5
5
  import geminiImageDescription from "../../prompts/tools/gemini-image.md" with { type: "text" };
6
6
  import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime";
7
7
  import type { CustomTool } from "../custom-tools/types";
8
+ import { renderPromptTemplate } from "../prompt-templates";
8
9
  import { untilAborted } from "../utils";
9
10
  import { resolveReadPath } from "./path-utils";
10
11
  import { getEnv } from "./web-search/auth";
@@ -367,7 +368,7 @@ function createRequestSignal(signal: AbortSignal | undefined, timeoutSeconds: nu
367
368
  export const geminiImageTool: CustomTool<typeof geminiImageSchema, GeminiImageToolDetails> = {
368
369
  name: "generate_image",
369
370
  label: "GenerateImage",
370
- description: geminiImageDescription,
371
+ description: renderPromptTemplate(geminiImageDescription),
371
372
  parameters: geminiImageSchema,
372
373
  async execute(_toolCallId, params, _onUpdate, ctx, signal) {
373
374
  return untilAborted(signal, async () => {
@@ -2,6 +2,7 @@ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
2
  import { type GitParams, gitTool as gitToolCore, type ToolResponse } from "@oh-my-pi/pi-git-tool";
3
3
  import { type Static, Type } from "@sinclair/typebox";
4
4
  import gitDescription from "../../prompts/tools/git.md" with { type: "text" };
5
+ import { renderPromptTemplate } from "../prompt-templates";
5
6
  import type { ToolSession } from "./index";
6
7
 
7
8
  const gitSchema = Type.Object({
@@ -200,7 +201,7 @@ export function createGitTool(session: ToolSession): AgentTool<typeof gitSchema,
200
201
  return {
201
202
  name: "git",
202
203
  label: "Git",
203
- description: gitDescription,
204
+ description: renderPromptTemplate(gitDescription),
204
205
  parameters: gitSchema,
205
206
  execute: async (_toolCallId, params: Static<typeof gitSchema>, _signal?: AbortSignal) => {
206
207
  if (params.operation === "commit" && !params.message) {
@@ -224,7 +225,6 @@ export function createGitTool(session: ToolSession): AgentTool<typeof gitSchema,
224
225
  export const gitTool = createGitTool({
225
226
  cwd: process.cwd(),
226
227
  hasUI: false,
227
- rulebookRules: [],
228
228
  getSessionFile: () => null,
229
229
  getSessionSpawns: () => null,
230
230
  })!;
@@ -8,6 +8,7 @@ import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/t
8
8
  import grepDescription from "../../prompts/tools/grep.md" with { type: "text" };
9
9
  import { ensureTool } from "../../utils/tools-manager";
10
10
  import type { RenderResultOptions } from "../custom-tools/types";
11
+ import { renderPromptTemplate } from "../prompt-templates";
11
12
  import { ScopeSignal, untilAborted } from "../utils";
12
13
  import type { ToolSession } from "./index";
13
14
  import { resolveToCwd } from "./path-utils";
@@ -96,7 +97,7 @@ export function createGrepTool(session: ToolSession, options?: GrepToolOptions):
96
97
  return {
97
98
  name: "grep",
98
99
  label: "Grep",
99
- description: grepDescription,
100
+ description: renderPromptTemplate(grepDescription),
100
101
  parameters: grepSchema,
101
102
  execute: async (
102
103
  _toolCallId: string,
@@ -5,7 +5,6 @@ function createTestSession(overrides: Partial<ToolSession> = {}): ToolSession {
5
5
  return {
6
6
  cwd: "/tmp/test",
7
7
  hasUI: false,
8
- rulebookRules: [],
9
8
  getSessionFile: () => null,
10
9
  getSessionSpawns: () => "*",
11
10
  ...overrides,
@@ -75,32 +74,6 @@ describe("createTools", () => {
75
74
  expect(names).toContain("ask");
76
75
  });
77
76
 
78
- it("excludes rulebook tool when no rules provided", async () => {
79
- const session = createTestSession({ rulebookRules: [] });
80
- const tools = await createTools(session);
81
- const names = tools.map((t) => t.name);
82
-
83
- expect(names).not.toContain("rulebook");
84
- });
85
-
86
- it("includes rulebook tool when rules provided", async () => {
87
- const session = createTestSession({
88
- rulebookRules: [
89
- {
90
- path: "/test/rule.md",
91
- name: "Test Rule",
92
- content: "Test content",
93
- description: "A test rule",
94
- _source: { provider: "test", providerName: "Test", path: "/test", level: "project" },
95
- },
96
- ],
97
- });
98
- const tools = await createTools(session);
99
- const names = tools.map((t) => t.name);
100
-
101
- expect(names).toContain("rulebook");
102
- });
103
-
104
77
  it("excludes git tool when disabled in settings", async () => {
105
78
  const session = createTestSession({
106
79
  settings: {
@@ -174,7 +147,6 @@ describe("createTools", () => {
174
147
  "notebook",
175
148
  "output",
176
149
  "read",
177
- "rulebook",
178
150
  "task",
179
151
  "web_fetch",
180
152
  "web_search",
@@ -26,7 +26,6 @@ export { createNotebookTool, type NotebookToolDetails } from "./notebook";
26
26
  export { createOutputTool, type OutputToolDetails } from "./output";
27
27
  export { createReadTool, type ReadToolDetails } from "./read";
28
28
  export { reportFindingTool, type SubmitReviewDetails } from "./review";
29
- export { filterRulebookRules, formatRulesForPrompt, type RulebookToolDetails } from "./rulebook";
30
29
  export { createSshTool, type SSHToolDetails } from "./ssh";
31
30
  export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index";
32
31
  export {
@@ -62,7 +61,6 @@ export {
62
61
  export { createWriteTool, type WriteToolDetails } from "./write";
63
62
 
64
63
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
65
- import type { Rule } from "../../capability/rule";
66
64
  import type { EventBus } from "../event-bus";
67
65
  import type { BashInterceptorRule } from "../settings-manager";
68
66
  import { createAskTool } from "./ask";
@@ -79,7 +77,6 @@ import { createNotebookTool } from "./notebook";
79
77
  import { createOutputTool } from "./output";
80
78
  import { createReadTool } from "./read";
81
79
  import { reportFindingTool } from "./review";
82
- import { createRulebookTool } from "./rulebook";
83
80
  import { createSshTool } from "./ssh";
84
81
  import { createTaskTool } from "./task/index";
85
82
  import { createWebFetchTool } from "./web-fetch";
@@ -95,8 +92,6 @@ export interface ToolSession {
95
92
  cwd: string;
96
93
  /** Whether UI is available */
97
94
  hasUI: boolean;
98
- /** Rulebook rules */
99
- rulebookRules: Rule[];
100
95
  /** Event bus for tool/extension communication */
101
96
  eventBus?: EventBus;
102
97
  /** Output schema for structured completion (subagents) */
@@ -141,7 +136,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
141
136
  notebook: createNotebookTool,
142
137
  output: createOutputTool,
143
138
  read: createReadTool,
144
- rulebook: createRulebookTool,
145
139
  task: createTaskTool,
146
140
  web_fetch: createWebFetchTool,
147
141
  web_search: createWebSearchTool,
@@ -6,6 +6,7 @@ import type { BunFile } from "bun";
6
6
  import { type Theme, theme } from "../../../modes/interactive/theme/theme";
7
7
  import lspDescription from "../../../prompts/tools/lsp.md" with { type: "text" };
8
8
  import { logger } from "../../logger";
9
+ import { renderPromptTemplate } from "../../prompt-templates";
9
10
  import { once, untilAborted } from "../../utils";
10
11
  import type { ToolSession } from "../index";
11
12
  import { resolveToCwd } from "../path-utils";
@@ -737,7 +738,7 @@ export function createLspTool(session: ToolSession): AgentTool<typeof lspSchema,
737
738
  return {
738
739
  name: "lsp",
739
740
  label: "LSP",
740
- description: lspDescription,
741
+ description: renderPromptTemplate(lspDescription),
741
742
  parameters: lspSchema,
742
743
  renderCall,
743
744
  renderResult,
@@ -14,6 +14,7 @@ import { Type } from "@sinclair/typebox";
14
14
  import type { Theme } from "../../modes/interactive/theme/theme";
15
15
  import outputDescription from "../../prompts/tools/output.md" with { type: "text" };
16
16
  import type { RenderResultOptions } from "../custom-tools/types";
17
+ import { renderPromptTemplate } from "../prompt-templates";
17
18
  import type { ToolSession } from "./index";
18
19
  import {
19
20
  formatCount,
@@ -236,7 +237,7 @@ export function createOutputTool(session: ToolSession): AgentTool<typeof outputS
236
237
  return {
237
238
  name: "output",
238
239
  label: "Output",
239
- description: outputDescription,
240
+ description: renderPromptTemplate(outputDescription),
240
241
  parameters: outputSchema,
241
242
  execute: async (
242
243
  _toolCallId: string,
@@ -12,6 +12,7 @@ import { formatDimensionNote, resizeImage } from "../../utils/image-resize";
12
12
  import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime";
13
13
  import { ensureTool } from "../../utils/tools-manager";
14
14
  import type { RenderResultOptions } from "../custom-tools/types";
15
+ import { renderPromptTemplate } from "../prompt-templates";
15
16
  import type { ToolSession } from "../sdk";
16
17
  import { ScopeSignal, untilAborted } from "../utils";
17
18
  import { createLsTool } from "./ls";
@@ -429,7 +430,9 @@ export function createReadTool(session: ToolSession): AgentTool<typeof readSchem
429
430
  return {
430
431
  name: "read",
431
432
  label: "Read",
432
- description: readDescription.replace("{{DEFAULT_MAX_LINES}}", String(DEFAULT_MAX_LINES)),
433
+ description: renderPromptTemplate(readDescription, {
434
+ DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
435
+ }),
433
436
  parameters: readSchema,
434
437
  execute: async (
435
438
  toolCallId: string,
@@ -8,6 +8,7 @@ import { loadSync } from "../../discovery/index";
8
8
  import type { Theme } from "../../modes/interactive/theme/theme";
9
9
  import sshDescriptionBase from "../../prompts/tools/ssh.md" with { type: "text" };
10
10
  import type { RenderResultOptions } from "../custom-tools/types";
11
+ import { renderPromptTemplate } from "../prompt-templates";
11
12
  import type { SSHHostInfo } from "../ssh/connection-manager";
12
13
  import { ensureHostInfo, getHostInfoForHost } from "../ssh/connection-manager";
13
14
  import { executeSSH } from "../ssh/ssh-executor";
@@ -54,11 +55,12 @@ function formatHostEntry(host: SSHHost): string {
54
55
  }
55
56
 
56
57
  function formatDescription(hosts: SSHHost[]): string {
58
+ const baseDescription = renderPromptTemplate(sshDescriptionBase);
57
59
  if (hosts.length === 0) {
58
- return sshDescriptionBase;
60
+ return baseDescription;
59
61
  }
60
62
  const hostList = hosts.map(formatHostEntry).join("\n");
61
- return `${sshDescriptionBase}\n\nAvailable hosts:\n${hostList}`;
63
+ return `${baseDescription}\n\nAvailable hosts:\n${hostList}`;
62
64
  }
63
65
 
64
66
  function quoteRemotePath(value: string): string {
@@ -4,47 +4,73 @@
4
4
  * Agents are embedded at build time via Bun's import with { type: "text" }.
5
5
  */
6
6
 
7
+ import exploreMd from "../../../prompts/agents/explore.md" with { type: "text" };
7
8
  // Embed agent markdown files at build time
8
- import exploreMd from "../../../prompts/explore.md" with { type: "text" };
9
- import planMd from "../../../prompts/plan.md" with { type: "text" };
10
- import reviewerMd from "../../../prompts/reviewer.md" with { type: "text" };
11
- import taskMd from "../../../prompts/task.md" with { type: "text" };
9
+ import agentFrontmatterTemplate from "../../../prompts/agents/frontmatter.md" with { type: "text" };
10
+ import planMd from "../../../prompts/agents/plan.md" with { type: "text" };
11
+ import reviewerMd from "../../../prompts/agents/reviewer.md" with { type: "text" };
12
+ import taskMd from "../../../prompts/agents/task.md" with { type: "text" };
13
+ import { renderPromptTemplate } from "../../prompt-templates";
12
14
  import type { AgentDefinition, AgentSource } from "./types";
13
15
 
14
- const EMBEDDED_AGENTS: { name: string; content: string }[] = [
15
- { name: "explore.md", content: exploreMd },
16
- { name: "plan.md", content: planMd },
17
- { name: "reviewer.md", content: reviewerMd },
16
+ interface AgentFrontmatter {
17
+ name: string;
18
+ description: string;
19
+ spawns?: string;
20
+ model?: string;
21
+ }
22
+
23
+ interface EmbeddedAgentDef {
24
+ fileName: string;
25
+ frontmatter?: AgentFrontmatter;
26
+ template: string;
27
+ }
28
+
29
+ function buildAgentContent(def: EmbeddedAgentDef): string {
30
+ const body = renderPromptTemplate(def.template);
31
+ if (!def.frontmatter) return body;
32
+ return renderPromptTemplate(agentFrontmatterTemplate, { ...def.frontmatter, body });
33
+ }
34
+
35
+ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
36
+ { fileName: "explore.md", template: exploreMd },
37
+ { fileName: "plan.md", template: planMd },
38
+ { fileName: "reviewer.md", template: reviewerMd },
18
39
  {
19
- name: "task.md",
20
- content: `---
21
- name: task
22
- description: General-purpose subagent with full capabilities for delegated multi-step tasks
23
- spawns: explore
24
- model: default
25
- ---
26
- ${taskMd}`,
40
+ fileName: "task.md",
41
+ frontmatter: {
42
+ name: "task",
43
+ description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
44
+ spawns: "explore",
45
+ model: "default",
46
+ },
47
+ template: taskMd,
27
48
  },
28
49
  {
29
- name: "quick_task.md",
30
- content: `---
31
- name: quick_task
32
- description: Quick task for fast execution
33
- model: pi/smol
34
- ---
35
- ${taskMd}`,
50
+ fileName: "quick_task.md",
51
+ frontmatter: {
52
+ name: "quick_task",
53
+ description: "Quick task for fast execution",
54
+ model: "pi/smol",
55
+ },
56
+ template: taskMd,
36
57
  },
37
58
  {
38
- name: "deep_task.md",
39
- content: `---
40
- name: deep_task
41
- description: Deep task for comprehensive reasoning
42
- model: pi/slow
43
- ---
44
- ${taskMd}`,
59
+ fileName: "deep_task.md",
60
+ frontmatter: {
61
+ name: "deep_task",
62
+ description: "Deep task for comprehensive reasoning",
63
+ model: "pi/slow",
64
+ },
65
+ template: taskMd,
45
66
  },
46
67
  ];
47
68
 
69
+ const EMBEDDED_AGENTS: { name: string; content: string }[] = EMBEDDED_AGENT_DEFS.map((def) => ({
70
+ name: def.fileName,
71
+ content: buildAgentContent(def),
72
+ }));
73
+
48
74
  /**
49
75
  * Parse YAML frontmatter from markdown content.
50
76
  */
@@ -9,16 +9,17 @@ import { type SlashCommand, slashCommandCapability } from "../../../capability/s
9
9
  import { loadSync } from "../../../discovery";
10
10
 
11
11
  // Embed command markdown files at build time
12
- import architectPlanMd from "../../../prompts/architect-plan.md" with { type: "text" };
13
- import implementMd from "../../../prompts/implement.md" with { type: "text" };
14
- import implementWithCriticMd from "../../../prompts/implement-with-critic.md" with { type: "text" };
15
- import initMd from "../../../prompts/init.md" with { type: "text" };
12
+ import architectPlanMd from "../../../prompts/agents/architect-plan.md" with { type: "text" };
13
+ import implementMd from "../../../prompts/agents/implement.md" with { type: "text" };
14
+ import implementWithCriticMd from "../../../prompts/agents/implement-with-critic.md" with { type: "text" };
15
+ import initMd from "../../../prompts/agents/init.md" with { type: "text" };
16
+ import { renderPromptTemplate } from "../../prompt-templates";
16
17
 
17
18
  const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
18
- { name: "architect-plan.md", content: architectPlanMd },
19
- { name: "implement-with-critic.md", content: implementWithCriticMd },
20
- { name: "implement.md", content: implementMd },
21
- { name: "init.md", content: initMd },
19
+ { name: "architect-plan.md", content: renderPromptTemplate(architectPlanMd) },
20
+ { name: "implement-with-critic.md", content: renderPromptTemplate(implementWithCriticMd) },
21
+ { name: "implement.md", content: renderPromptTemplate(implementMd) },
22
+ { name: "init.md", content: renderPromptTemplate(initMd) },
22
23
  ];
23
24
 
24
25
  export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
@@ -17,6 +17,7 @@ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
17
17
  import type { Usage } from "@oh-my-pi/pi-ai";
18
18
  import type { Theme } from "../../../modes/interactive/theme/theme";
19
19
  import taskDescriptionTemplate from "../../../prompts/tools/task.md" with { type: "text" };
20
+ import { renderPromptTemplate } from "../../prompt-templates";
20
21
  import { formatDuration } from "../render-utils";
21
22
  import { cleanupTempDir, createTempArtifactsDir, getArtifactsDir } from "./artifacts";
22
23
  import { discoverAgents, getAgent } from "./discovery";
@@ -95,21 +96,12 @@ export { taskSchema } from "./types";
95
96
  async function buildDescription(cwd: string): Promise<string> {
96
97
  const { agents } = await discoverAgents(cwd);
97
98
 
98
- // Build agents list
99
- const agentLines: string[] = [];
100
- for (const agent of agents.slice(0, MAX_AGENTS_IN_DESCRIPTION)) {
101
- const tools = agent.tools?.join(", ") || "All tools";
102
- agentLines.push(`- ${agent.name}: ${agent.description} (Tools: ${tools})`);
103
- }
104
- if (agents.length > MAX_AGENTS_IN_DESCRIPTION) {
105
- agentLines.push(` ...and ${agents.length - MAX_AGENTS_IN_DESCRIPTION} more agents`);
106
- }
107
-
108
- // Fill template placeholders
109
- return taskDescriptionTemplate
110
- .replace("{{AGENTS_LIST}}", agentLines.join("\n"))
111
- .replace("{{MAX_PARALLEL_TASKS}}", String(MAX_PARALLEL_TASKS))
112
- .replace("{{MAX_CONCURRENCY}}", String(MAX_CONCURRENCY));
99
+ return renderPromptTemplate(taskDescriptionTemplate, {
100
+ agents: agents.slice(0, MAX_AGENTS_IN_DESCRIPTION),
101
+ moreAgents: agents.length > MAX_AGENTS_IN_DESCRIPTION ? agents.length - MAX_AGENTS_IN_DESCRIPTION : 0,
102
+ MAX_PARALLEL_TASKS,
103
+ MAX_CONCURRENCY,
104
+ });
113
105
  }
114
106
 
115
107
  /**
@@ -10,6 +10,7 @@ import { type Theme, theme } from "../../modes/interactive/theme/theme";
10
10
  import webFetchDescription from "../../prompts/tools/web-fetch.md" with { type: "text" };
11
11
  import { ensureTool } from "../../utils/tools-manager";
12
12
  import type { RenderResultOptions } from "../custom-tools/types";
13
+ import { renderPromptTemplate } from "../prompt-templates";
13
14
  import type { ToolSession } from "./index";
14
15
  import { specialHandlers } from "./web-scrapers/index";
15
16
  import type { RenderResult } from "./web-scrapers/types";
@@ -836,7 +837,7 @@ export function createWebFetchTool(_session: ToolSession): AgentTool<typeof webF
836
837
  return {
837
838
  name: "web_fetch",
838
839
  label: "Web Fetch",
839
- description: webFetchDescription,
840
+ description: renderPromptTemplate(webFetchDescription),
840
841
  parameters: webFetchSchema,
841
842
  execute: async (
842
843
  _toolCallId: string,