@oh-my-pi/pi-coding-agent 3.20.1 → 3.24.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 (123) hide show
  1. package/CHANGELOG.md +107 -8
  2. package/docs/custom-tools.md +3 -3
  3. package/docs/extensions.md +226 -220
  4. package/docs/hooks.md +2 -2
  5. package/docs/sdk.md +50 -53
  6. package/examples/custom-tools/README.md +2 -17
  7. package/examples/extensions/README.md +76 -74
  8. package/examples/extensions/todo.ts +2 -5
  9. package/examples/hooks/custom-compaction.ts +2 -4
  10. package/examples/hooks/handoff.ts +1 -1
  11. package/examples/hooks/qna.ts +1 -1
  12. package/examples/sdk/02-custom-model.ts +1 -1
  13. package/examples/sdk/README.md +7 -11
  14. package/package.json +6 -6
  15. package/src/cli/args.ts +9 -6
  16. package/src/cli/file-processor.ts +1 -1
  17. package/src/cli/list-models.ts +1 -1
  18. package/src/core/agent-session.ts +16 -5
  19. package/src/core/auth-storage.ts +1 -1
  20. package/src/core/compaction/branch-summarization.ts +2 -2
  21. package/src/core/compaction/compaction.ts +2 -2
  22. package/src/core/compaction/utils.ts +1 -1
  23. package/src/core/custom-tools/types.ts +1 -1
  24. package/src/core/custom-tools/wrapper.ts +0 -1
  25. package/src/core/extensions/index.ts +1 -6
  26. package/src/core/extensions/runner.ts +1 -1
  27. package/src/core/extensions/types.ts +1 -1
  28. package/src/core/extensions/wrapper.ts +1 -8
  29. package/src/core/file-mentions.ts +5 -8
  30. package/src/core/hooks/runner.ts +2 -2
  31. package/src/core/hooks/types.ts +1 -1
  32. package/src/core/messages.ts +1 -1
  33. package/src/core/model-registry.ts +1 -1
  34. package/src/core/model-resolver.ts +1 -1
  35. package/src/core/sdk.ts +64 -105
  36. package/src/core/session-manager.ts +18 -22
  37. package/src/core/settings-manager.ts +66 -1
  38. package/src/core/slash-commands.ts +12 -5
  39. package/src/core/system-prompt.ts +49 -36
  40. package/src/core/title-generator.ts +2 -2
  41. package/src/core/tools/ask.ts +98 -4
  42. package/src/core/tools/bash-interceptor.ts +11 -4
  43. package/src/core/tools/bash.ts +121 -5
  44. package/src/core/tools/context.ts +7 -0
  45. package/src/core/tools/edit-diff.ts +73 -24
  46. package/src/core/tools/edit.ts +221 -34
  47. package/src/core/tools/exa/render.ts +4 -16
  48. package/src/core/tools/find.ts +149 -5
  49. package/src/core/tools/gemini-image.ts +279 -56
  50. package/src/core/tools/git.ts +17 -3
  51. package/src/core/tools/grep.ts +185 -5
  52. package/src/core/tools/index.test.ts +180 -0
  53. package/src/core/tools/index.ts +96 -242
  54. package/src/core/tools/ls.ts +133 -5
  55. package/src/core/tools/lsp/index.ts +32 -29
  56. package/src/core/tools/lsp/render.ts +21 -22
  57. package/src/core/tools/notebook.ts +112 -4
  58. package/src/core/tools/output.ts +175 -15
  59. package/src/core/tools/read.ts +127 -25
  60. package/src/core/tools/render-utils.ts +241 -0
  61. package/src/core/tools/renderers.ts +40 -828
  62. package/src/core/tools/review.ts +26 -25
  63. package/src/core/tools/rulebook.ts +11 -3
  64. package/src/core/tools/task/agents.ts +28 -7
  65. package/src/core/tools/task/discovery.ts +0 -6
  66. package/src/core/tools/task/executor.ts +264 -254
  67. package/src/core/tools/task/index.ts +48 -208
  68. package/src/core/tools/task/render.ts +26 -11
  69. package/src/core/tools/task/types.ts +7 -12
  70. package/src/core/tools/task/worker-protocol.ts +17 -0
  71. package/src/core/tools/task/worker.ts +238 -0
  72. package/src/core/tools/truncate.ts +27 -1
  73. package/src/core/tools/web-fetch.ts +25 -49
  74. package/src/core/tools/web-search/index.ts +132 -46
  75. package/src/core/tools/web-search/providers/anthropic.ts +7 -2
  76. package/src/core/tools/web-search/providers/exa.ts +2 -1
  77. package/src/core/tools/web-search/providers/perplexity.ts +6 -1
  78. package/src/core/tools/web-search/render.ts +6 -4
  79. package/src/core/tools/web-search/types.ts +13 -0
  80. package/src/core/tools/write.ts +96 -14
  81. package/src/core/voice.ts +1 -1
  82. package/src/discovery/helpers.test.ts +1 -1
  83. package/src/index.ts +5 -16
  84. package/src/main.ts +5 -5
  85. package/src/modes/interactive/components/assistant-message.ts +1 -1
  86. package/src/modes/interactive/components/custom-message.ts +1 -1
  87. package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
  88. package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
  89. package/src/modes/interactive/components/footer.ts +1 -1
  90. package/src/modes/interactive/components/hook-message.ts +1 -1
  91. package/src/modes/interactive/components/model-selector.ts +1 -1
  92. package/src/modes/interactive/components/oauth-selector.ts +1 -1
  93. package/src/modes/interactive/components/settings-defs.ts +49 -0
  94. package/src/modes/interactive/components/status-line.ts +1 -1
  95. package/src/modes/interactive/components/tool-execution.ts +93 -538
  96. package/src/modes/interactive/interactive-mode.ts +19 -7
  97. package/src/modes/interactive/theme/theme.ts +4 -4
  98. package/src/modes/print-mode.ts +1 -1
  99. package/src/modes/rpc/rpc-client.ts +1 -1
  100. package/src/modes/rpc/rpc-types.ts +1 -1
  101. package/src/prompts/system-prompt.md +4 -0
  102. package/src/prompts/task.md +0 -7
  103. package/src/prompts/tools/gemini-image.md +5 -1
  104. package/src/prompts/tools/output.md +6 -2
  105. package/src/prompts/tools/task.md +68 -0
  106. package/src/prompts/tools/web-fetch.md +1 -0
  107. package/src/prompts/tools/web-search.md +2 -0
  108. package/src/utils/image-convert.ts +8 -2
  109. package/src/utils/image-magick.ts +247 -0
  110. package/src/utils/image-resize.ts +53 -13
  111. package/examples/custom-tools/question/index.ts +0 -84
  112. package/examples/custom-tools/subagent/README.md +0 -172
  113. package/examples/custom-tools/subagent/agents/planner.md +0 -37
  114. package/examples/custom-tools/subagent/agents/scout.md +0 -50
  115. package/examples/custom-tools/subagent/agents/worker.md +0 -24
  116. package/examples/custom-tools/subagent/agents.ts +0 -156
  117. package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
  118. package/examples/custom-tools/subagent/commands/implement.md +0 -10
  119. package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
  120. package/examples/custom-tools/subagent/index.ts +0 -1002
  121. package/examples/sdk/05-tools.ts +0 -94
  122. package/examples/sdk/12-full-control.ts +0 -95
  123. package/src/prompts/browser.md +0 -71
@@ -13,9 +13,9 @@
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
15
 
16
+ import type { AssistantMessage, ImageContent, Message, Model, TextContent, Usage } from "@mariozechner/pi-ai";
17
+ import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
16
18
  import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
17
- import type { AssistantMessage, ImageContent, Message, Model, TextContent, Usage } from "@oh-my-pi/pi-ai";
18
- import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
19
19
  import type { Rule } from "../capability/rule";
20
20
  import { getAuthPath } from "../config";
21
21
  import { theme } from "../modes/interactive/theme/theme";
@@ -51,6 +51,7 @@ import { parseModelString } from "./model-resolver";
51
51
  import { expandPromptTemplate, type PromptTemplate, parseCommandArgs } from "./prompt-templates";
52
52
  import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionManager } from "./session-manager";
53
53
  import type { SettingsManager, SkillsSettings } from "./settings-manager";
54
+ import { expandSlashCommand, type FileSlashCommand } from "./slash-commands";
54
55
  import type { TtsrManager } from "./ttsr";
55
56
 
56
57
  /** Session-specific events that extend the core AgentEvent */
@@ -77,6 +78,8 @@ export interface AgentSessionConfig {
77
78
  scopedModels?: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
78
79
  /** Prompt templates for expansion */
79
80
  promptTemplates?: PromptTemplate[];
81
+ /** File-based slash commands for expansion */
82
+ slashCommands?: FileSlashCommand[];
80
83
  /** Extension runner (created in main.ts with wrapped tools) */
81
84
  extensionRunner?: ExtensionRunner;
82
85
  /** Custom commands (TypeScript slash commands) */
@@ -87,7 +90,7 @@ export interface AgentSessionConfig {
87
90
  /** Tool registry for LSP and settings */
88
91
  toolRegistry?: Map<string, AgentTool>;
89
92
  /** System prompt builder that can consider tool availability */
90
- rebuildSystemPrompt?: (toolNames: string[]) => string;
93
+ rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => string;
91
94
  /** TTSR manager for time-traveling stream rules */
92
95
  ttsrManager?: TtsrManager;
93
96
  }
@@ -175,6 +178,7 @@ export class AgentSession {
175
178
 
176
179
  private _scopedModels: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
177
180
  private _promptTemplates: PromptTemplate[];
181
+ private _slashCommands: FileSlashCommand[];
178
182
 
179
183
  // Event subscription state
180
184
  private _unsubscribeAgent?: () => void;
@@ -218,7 +222,7 @@ export class AgentSession {
218
222
 
219
223
  // Tool registry and prompt builder for extensions
220
224
  private _toolRegistry: Map<string, AgentTool>;
221
- private _rebuildSystemPrompt: ((toolNames: string[]) => string) | undefined;
225
+ private _rebuildSystemPrompt: ((toolNames: string[], tools: Map<string, AgentTool>) => string) | undefined;
222
226
  private _baseSystemPrompt: string;
223
227
 
224
228
  // TTSR manager for time-traveling stream rules
@@ -232,6 +236,7 @@ export class AgentSession {
232
236
  this.settingsManager = config.settingsManager;
233
237
  this._scopedModels = config.scopedModels ?? [];
234
238
  this._promptTemplates = config.promptTemplates ?? [];
239
+ this._slashCommands = config.slashCommands ?? [];
235
240
  this._extensionRunner = config.extensionRunner;
236
241
  this._customCommands = config.customCommands ?? [];
237
242
  this._skillsSettings = config.skillsSettings;
@@ -598,7 +603,7 @@ export class AgentSession {
598
603
 
599
604
  // Rebuild base system prompt with new tool set
600
605
  if (this._rebuildSystemPrompt) {
601
- this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
606
+ this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames, this._toolRegistry);
602
607
  this.agent.setSystemPrompt(this._baseSystemPrompt);
603
608
  }
604
609
  }
@@ -684,6 +689,12 @@ export class AgentSession {
684
689
  }
685
690
  text = customResult;
686
691
  }
692
+
693
+ // Try file-based slash commands (markdown files from commands/ directories)
694
+ // Only if text still starts with "/" (wasn't transformed by custom command)
695
+ if (text.startsWith("/")) {
696
+ text = expandSlashCommand(text, this._slashCommands);
697
+ }
687
698
  }
688
699
 
689
700
  // Expand file-based prompt templates if requested
@@ -15,7 +15,7 @@ import {
15
15
  loginOpenAICodex,
16
16
  type OAuthCredentials,
17
17
  type OAuthProvider,
18
- } from "@oh-my-pi/pi-ai";
18
+ } from "@mariozechner/pi-ai";
19
19
  import { logger } from "./logger";
20
20
 
21
21
  export type ApiKeyCredential = {
@@ -5,9 +5,9 @@
5
5
  * a summary of the branch being left so context isn't lost.
6
6
  */
7
7
 
8
+ import type { Model } from "@mariozechner/pi-ai";
9
+ import { completeSimple } from "@mariozechner/pi-ai";
8
10
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
9
- import type { Model } from "@oh-my-pi/pi-ai";
10
- import { completeSimple } from "@oh-my-pi/pi-ai";
11
11
  import branchSummaryPrompt from "../../prompts/branch-summary.md" with { type: "text" };
12
12
  import branchSummaryPreamble from "../../prompts/branch-summary-preamble.md" with { type: "text" };
13
13
  import {
@@ -5,9 +5,9 @@
5
5
  * and after compaction the session is reloaded.
6
6
  */
7
7
 
8
+ import type { AssistantMessage, Model, Usage } from "@mariozechner/pi-ai";
9
+ import { complete, completeSimple } from "@mariozechner/pi-ai";
8
10
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
9
- import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai";
10
- import { complete, completeSimple } from "@oh-my-pi/pi-ai";
11
11
  import compactionSummaryPrompt from "../../prompts/compaction-summary.md" with { type: "text" };
12
12
  import compactionTurnPrefixPrompt from "../../prompts/compaction-turn-prefix.md" with { type: "text" };
13
13
  import compactionUpdateSummaryPrompt from "../../prompts/compaction-update-summary.md" with { type: "text" };
@@ -2,8 +2,8 @@
2
2
  * Shared utilities for compaction and branch summarization.
3
3
  */
4
4
 
5
+ import type { Message } from "@mariozechner/pi-ai";
5
6
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
- import type { Message } from "@oh-my-pi/pi-ai";
7
7
  import summarizationSystemPrompt from "../../prompts/summarization-system.md" with { type: "text" };
8
8
 
9
9
  // ============================================================================
@@ -5,8 +5,8 @@
5
5
  * They can provide custom rendering for tool calls and results in the TUI.
6
6
  */
7
7
 
8
+ import type { Model } from "@mariozechner/pi-ai";
8
9
  import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
9
- import type { Model } from "@oh-my-pi/pi-ai";
10
10
  import type { Component } from "@oh-my-pi/pi-tui";
11
11
  import type { Static, TSchema } from "@sinclair/typebox";
12
12
  import type { Theme } from "../../modes/interactive/theme/theme";
@@ -16,7 +16,6 @@ export function wrapCustomTool(tool: CustomTool, getContext: () => CustomToolCon
16
16
  label: tool.label,
17
17
  description: tool.description,
18
18
  parameters: tool.parameters,
19
- hidden: tool.hidden,
20
19
  execute: (toolCallId, params, signal, onUpdate, context) =>
21
20
  tool.execute(toolCallId, params, onUpdate, context ?? getContext(), signal),
22
21
  renderCall: tool.renderCall ? (args, theme) => tool.renderCall?.(args, theme as Theme) : undefined,
@@ -92,9 +92,4 @@ export {
92
92
  isReadToolResult,
93
93
  isWriteToolResult,
94
94
  } from "./types";
95
- export {
96
- wrapRegisteredTool,
97
- wrapRegisteredTools,
98
- wrapToolsWithExtensions,
99
- wrapToolWithExtensions,
100
- } from "./wrapper";
95
+ export { wrapRegisteredTool, wrapRegisteredTools, wrapToolWithExtensions } from "./wrapper";
@@ -2,8 +2,8 @@
2
2
  * Extension runner - executes extensions and manages their lifecycle.
3
3
  */
4
4
 
5
+ import type { ImageContent, Model } from "@mariozechner/pi-ai";
5
6
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
- import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
7
7
  import type { KeyId } from "@oh-my-pi/pi-tui";
8
8
  import { theme } from "../../modes/interactive/theme/theme";
9
9
  import type { ModelRegistry } from "../model-registry";
@@ -8,8 +8,8 @@
8
8
  * - Interact with the user via UI primitives
9
9
  */
10
10
 
11
+ import type { ImageContent, Model, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
11
12
  import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
12
- import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
13
13
  import type { Component, KeyId, TUI } from "@oh-my-pi/pi-tui";
14
14
  import type { Static, TSchema } from "@sinclair/typebox";
15
15
  import type { Theme } from "../../modes/interactive/theme/theme";
@@ -2,8 +2,8 @@
2
2
  * Tool wrappers for extensions.
3
3
  */
4
4
 
5
+ import type { ImageContent, TextContent } from "@mariozechner/pi-ai";
5
6
  import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
6
- import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
7
7
  import type { Theme } from "../../modes/interactive/theme/theme";
8
8
  import type { ExtensionRunner } from "./runner";
9
9
  import type { ExtensionContext, RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
@@ -138,10 +138,3 @@ export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: Exten
138
138
  },
139
139
  };
140
140
  }
141
-
142
- /**
143
- * Wrap all tools with extension callbacks.
144
- */
145
- export function wrapToolsWithExtensions<T>(tools: AgentTool<any, T>[], runner: ExtensionRunner): AgentTool<any, T>[] {
146
- return tools.map((tool) => wrapToolWithExtensions(tool, runner));
147
- }
@@ -6,9 +6,9 @@
6
6
  * so the agent doesn't need to read them manually.
7
7
  */
8
8
 
9
+ import path from "node:path";
9
10
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
10
11
  import type { FileMentionMessage } from "./messages";
11
- import { createReadTool } from "./tools/read";
12
12
 
13
13
  /** Regex to match @filepath patterns in text */
14
14
  const FILE_MENTION_REGEX = /@((?:[^\s@]+\/)*[^\s@]+\.[a-zA-Z0-9]+)/g;
@@ -26,17 +26,14 @@ export function extractFileMentions(text: string): string[] {
26
26
  export async function generateFileMentionMessages(filePaths: string[], cwd: string): Promise<AgentMessage[]> {
27
27
  if (filePaths.length === 0) return [];
28
28
 
29
- const readTool = createReadTool(cwd);
30
29
  const files: FileMentionMessage["files"] = [];
31
30
 
32
31
  for (const filePath of filePaths) {
33
32
  try {
34
- const result = await readTool.execute("auto-read", { path: filePath });
35
- const textContent = result.content.find((c) => c.type === "text");
36
- if (textContent && textContent.type === "text") {
37
- const lineCount = textContent.text.split("\n").length;
38
- files.push({ path: filePath, content: textContent.text, lineCount });
39
- }
33
+ const absolutePath = path.resolve(cwd, filePath);
34
+ const content = await Bun.file(absolutePath).text();
35
+ const lineCount = content.split("\n").length;
36
+ files.push({ path: filePath, content, lineCount });
40
37
  } catch {
41
38
  // File doesn't exist or isn't readable - skip silently
42
39
  }
@@ -2,8 +2,8 @@
2
2
  * Hook runner - executes hooks and manages their lifecycle.
3
3
  */
4
4
 
5
+ import type { Model } from "@mariozechner/pi-ai";
5
6
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
- import type { Model } from "@oh-my-pi/pi-ai";
7
7
  import { theme } from "../../modes/interactive/theme/theme";
8
8
  import type { ModelRegistry } from "../model-registry";
9
9
  import type { SessionManager } from "../session-manager";
@@ -400,7 +400,7 @@ export class HookRunner {
400
400
  */
401
401
  async emitBeforeAgentStart(
402
402
  prompt: string,
403
- images?: import("@oh-my-pi/pi-ai").ImageContent[],
403
+ images?: import("@mariozechner/pi-ai").ImageContent[],
404
404
  ): Promise<BeforeAgentStartEventResult | undefined> {
405
405
  const ctx = this.createContext();
406
406
  let result: BeforeAgentStartEventResult | undefined;
@@ -5,8 +5,8 @@
5
5
  * and interact with the user via UI primitives.
6
6
  */
7
7
 
8
+ import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
8
9
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
9
- import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
10
10
  import type { Component, TUI } from "@oh-my-pi/pi-tui";
11
11
  import type { Theme } from "../../modes/interactive/theme/theme";
12
12
  import type { CompactionPreparation, CompactionResult } from "../compaction/index";
@@ -5,8 +5,8 @@
5
5
  * and provides a transformer to convert them to LLM-compatible messages.
6
6
  */
7
7
 
8
+ import type { ImageContent, Message, TextContent } from "@mariozechner/pi-ai";
8
9
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
9
- import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
10
10
 
11
11
  export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
12
12
 
@@ -11,7 +11,7 @@ import {
11
11
  type KnownProvider,
12
12
  type Model,
13
13
  normalizeDomain,
14
- } from "@oh-my-pi/pi-ai";
14
+ } from "@mariozechner/pi-ai";
15
15
  import { type Static, Type } from "@sinclair/typebox";
16
16
  import AjvModule from "ajv";
17
17
  import type { AuthStorage } from "./auth-storage";
@@ -2,8 +2,8 @@
2
2
  * Model resolution, scoping, and initial selection
3
3
  */
4
4
 
5
+ import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@mariozechner/pi-ai";
5
6
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
- import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
7
7
  import chalk from "chalk";
8
8
  import { minimatch } from "minimatch";
9
9
  import { isValidThinkingLevel } from "../cli/args";
package/src/core/sdk.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * const session = await createAgentSession({
19
19
  * model: myModel,
20
20
  * getApiKey: async () => process.env.MY_KEY,
21
- * tools: [readTool, bashTool],
21
+ * toolNames: ["read", "bash", "edit", "write"], // Filter tools
22
22
  * extensions: [],
23
23
  * skills: [],
24
24
  * sessionFile: false,
@@ -27,8 +27,8 @@
27
27
  */
28
28
 
29
29
  import { join } from "node:path";
30
+ import type { Model } from "@mariozechner/pi-ai";
30
31
  import { Agent, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
31
- import type { Model } from "@oh-my-pi/pi-ai";
32
32
  import type { Component } from "@oh-my-pi/pi-tui";
33
33
  import chalk from "chalk";
34
34
  // Import discovery to register all providers on startup
@@ -55,7 +55,7 @@ import {
55
55
  loadExtensionFromFactory,
56
56
  type ToolDefinition,
57
57
  wrapRegisteredTools,
58
- wrapToolsWithExtensions,
58
+ wrapToolWithExtensions,
59
59
  } from "./extensions/index";
60
60
  import { logger } from "./logger";
61
61
  import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
@@ -66,6 +66,7 @@ import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate
66
66
  import { SessionManager } from "./session-manager";
67
67
  import { type Settings, SettingsManager, type SkillsSettings } from "./settings-manager";
68
68
  import { loadSkills as loadSkillsInternal, type Skill } from "./skills";
69
+ import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands";
69
70
  import {
70
71
  buildSystemPrompt as buildSystemPromptInternal,
71
72
  loadProjectContextFiles as loadContextFilesInternal,
@@ -74,46 +75,34 @@ import { time } from "./timings";
74
75
  import { createToolContextStore } from "./tools/context";
75
76
  import { getGeminiImageTools } from "./tools/gemini-image";
76
77
  import {
77
- allTools,
78
- applyBashInterception,
79
- baseCodingToolNames,
80
- bashTool,
81
- codingTools,
82
- createAllTools,
78
+ BUILTIN_TOOLS,
83
79
  createBashTool,
84
- createCodingTools,
85
80
  createEditTool,
86
81
  createFindTool,
87
82
  createGitTool,
88
83
  createGrepTool,
89
84
  createLsTool,
90
- createReadOnlyTools,
91
85
  createReadTool,
92
- createRulebookTool,
86
+ createTools,
93
87
  createWriteTool,
94
- editTool,
95
88
  filterRulebookRules,
96
- findTool,
97
89
  getWebSearchTools,
98
- gitTool,
99
- grepTool,
100
- lsTool,
101
- readOnlyTools,
102
- readTool,
90
+ setPreferredImageProvider,
91
+ setPreferredWebSearchProvider,
103
92
  type Tool,
104
- type ToolName,
93
+ type ToolSession,
105
94
  warmupLspServers,
106
- writeTool,
107
95
  } from "./tools/index";
108
96
  import { createTtsrManager } from "./ttsr";
109
97
 
110
98
  // Types
111
-
112
99
  export interface CreateAgentSessionOptions {
113
100
  /** Working directory for project-local discovery. Default: process.cwd() */
114
101
  cwd?: string;
115
102
  /** Global config directory. Default: ~/.omp/agent */
116
103
  agentDir?: string;
104
+ /** Spawns to allow. Default: "*" */
105
+ spawns?: string;
117
106
 
118
107
  /** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
119
108
  authStorage?: AuthStorage;
@@ -130,8 +119,6 @@ export interface CreateAgentSessionOptions {
130
119
  /** System prompt. String replaces default, function receives default and returns final. */
131
120
  systemPrompt?: string | ((defaultPrompt: string) => string);
132
121
 
133
- /** Built-in tools to use. Default: all coding tools (read, bash, edit, write, grep, find, ls, lsp, notebook, output, task, web_fetch, web_search) */
134
- tools?: Tool[];
135
122
  /** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
136
123
  customTools?: (CustomTool | ToolDefinition)[];
137
124
  /** Inline extensions (merged with discovery). */
@@ -153,12 +140,14 @@ export interface CreateAgentSessionOptions {
153
140
  contextFiles?: Array<{ path: string; content: string }>;
154
141
  /** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
155
142
  promptTemplates?: PromptTemplate[];
143
+ /** File-based slash commands. Default: discovered from commands/ directories */
144
+ slashCommands?: FileSlashCommand[];
156
145
 
157
146
  /** Enable MCP server discovery from .mcp.json files. Default: true */
158
147
  enableMCP?: boolean;
159
148
 
160
149
  /** Tool names explicitly requested (enables disabled-by-default tools) */
161
- explicitTools?: string[];
150
+ toolNames?: string[];
162
151
 
163
152
  /** Session manager. Default: SessionManager.create(cwd) */
164
153
  sessionManager?: SessionManager;
@@ -199,24 +188,15 @@ export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResu
199
188
  export type { PromptTemplate } from "./prompt-templates";
200
189
  export type { Settings, SkillsSettings } from "./settings-manager";
201
190
  export type { Skill } from "./skills";
191
+ export type { FileSlashCommand } from "./slash-commands";
202
192
  export type { Tool } from "./tools/index";
203
193
 
204
194
  export {
205
- // Pre-built tools (use process.cwd())
206
- readTool,
207
- bashTool,
208
- editTool,
209
- writeTool,
210
- grepTool,
211
- findTool,
212
- gitTool,
213
- lsTool,
214
- codingTools,
215
- readOnlyTools,
216
- allTools as allBuiltInTools,
217
- // Tool factories (for custom cwd)
218
- createCodingTools,
219
- createReadOnlyTools,
195
+ // Tool factories
196
+ BUILTIN_TOOLS,
197
+ createTools,
198
+ type ToolSession,
199
+ // Individual tool factories (for custom usage)
220
200
  createReadTool,
221
201
  createBashTool,
222
202
  createEditTool,
@@ -315,6 +295,13 @@ export async function discoverPromptTemplates(cwd?: string, agentDir?: string):
315
295
  });
316
296
  }
317
297
 
298
+ /**
299
+ * Discover file-based slash commands from commands/ directories.
300
+ */
301
+ export function discoverSlashCommands(cwd?: string): FileSlashCommand[] {
302
+ return loadSlashCommandsInternal({ cwd: cwd ?? process.cwd() });
303
+ }
304
+
318
305
  /**
319
306
  * Discover custom commands (TypeScript slash commands) from cwd and agentDir.
320
307
  */
@@ -413,7 +400,6 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
413
400
  label: tool.label,
414
401
  description: tool.description,
415
402
  parameters: tool.parameters,
416
- hidden: tool.hidden,
417
403
  execute: (toolCallId, params, onUpdate, ctx, signal) =>
418
404
  tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
419
405
  onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
@@ -480,7 +466,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
480
466
  * const { session } = await createAgentSession();
481
467
  *
482
468
  * // With explicit model
483
- * import { getModel } from '@oh-my-pi/pi-ai';
469
+ * import { getModel } from '@mariozechner/pi-ai';
484
470
  * const { session } = await createAgentSession({
485
471
  * model: getModel('anthropic', 'claude-opus-4-5'),
486
472
  * thinkingLevel: 'high',
@@ -496,7 +482,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
496
482
  * model: myModel,
497
483
  * getApiKey: async () => process.env.MY_KEY,
498
484
  * systemPrompt: 'You are helpful.',
499
- * tools: [readTool, bashTool],
485
+ * tools: codingTools({ cwd: process.cwd() }),
500
486
  * skills: [],
501
487
  * sessionManager: SessionManager.inMemory(),
502
488
  * });
@@ -517,6 +503,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
517
503
  initializeWithSettings(settingsManager);
518
504
  time("initializeWithSettings");
519
505
 
506
+ // Initialize provider preferences from settings
507
+ setPreferredWebSearchProvider(settingsManager.getWebSearchProvider());
508
+ setPreferredImageProvider(settingsManager.getImageProvider());
509
+
520
510
  const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
521
511
  time("sessionManager");
522
512
 
@@ -613,22 +603,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
613
603
  const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
614
604
  time("discoverContextFiles");
615
605
 
616
- const sessionContext = {
606
+ const toolSession: ToolSession = {
607
+ cwd,
608
+ hasUI: options.hasUI ?? false,
609
+ rulebookRules,
610
+ eventBus,
617
611
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
612
+ getSessionSpawns: () => options.spawns ?? "*",
613
+ settings: settingsManager,
618
614
  };
619
- const allBuiltInToolsMap = await createAllTools(cwd, sessionContext, {
620
- lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
621
- lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
622
- lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
623
- editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
624
- readAutoResizeImages: settingsManager.getImageAutoResize(),
625
- });
626
- time("createAllTools");
627
615
 
628
- const initialActiveToolNames: ToolName[] = options.tools
629
- ? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allBuiltInToolsMap)
630
- : baseCodingToolNames;
631
- const initialActiveBuiltInTools = initialActiveToolNames.map((name) => allBuiltInToolsMap[name]);
616
+ const builtinTools = await createTools(toolSession, options.toolNames);
617
+ time("createAllTools");
632
618
 
633
619
  // Discover MCP tools from .mcp.json files
634
620
  let mcpManager: MCPManager | undefined;
@@ -815,61 +801,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
815
801
  hasQueuedMessages: () => session.queuedMessageCount > 0,
816
802
  }));
817
803
 
804
+ // All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
818
805
  const toolRegistry = new Map<string, AgentTool>();
819
- for (const [name, tool] of Object.entries(allBuiltInToolsMap)) {
820
- toolRegistry.set(name, tool as AgentTool);
806
+ for (const tool of builtinTools) {
807
+ toolRegistry.set(tool.name, tool as AgentTool);
821
808
  }
822
- for (const tool of wrappedExtensionTools as AgentTool[]) {
809
+ for (const tool of wrappedExtensionTools) {
823
810
  toolRegistry.set(tool.name, tool);
824
811
  }
825
-
826
- let activeToolsArray: Tool[] = [...initialActiveBuiltInTools, ...wrappedExtensionTools];
827
-
828
- if (rulebookRules.length > 0) {
829
- activeToolsArray.push(createRulebookTool(rulebookRules));
830
- }
831
-
832
- if (options.explicitTools) {
833
- const explicitSet = new Set(options.explicitTools);
834
- activeToolsArray = activeToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
835
- } else {
836
- activeToolsArray = activeToolsArray.filter((tool) => !tool.hidden);
837
- }
838
- time("combineTools");
839
-
840
- if (settingsManager.getBashInterceptorEnabled()) {
841
- activeToolsArray = applyBashInterception(activeToolsArray);
842
- }
843
- time("applyBashInterception");
844
-
845
- let wrappedToolRegistry: Map<string, AgentTool> | undefined;
846
812
  if (extensionRunner) {
847
- activeToolsArray = wrapToolsWithExtensions(activeToolsArray as AgentTool[], extensionRunner);
848
- const allRegistryTools = Array.from(toolRegistry.values());
849
- const wrappedAllTools = wrapToolsWithExtensions(allRegistryTools, extensionRunner);
850
- wrappedToolRegistry = new Map<string, AgentTool>();
851
- for (const tool of wrappedAllTools) {
852
- wrappedToolRegistry.set(tool.name, tool);
813
+ for (const tool of toolRegistry.values()) {
814
+ toolRegistry.set(tool.name, wrapToolWithExtensions(tool, extensionRunner));
853
815
  }
854
816
  }
817
+ time("combineTools");
855
818
 
856
- const rebuildSystemPrompt = (toolNames: string[]): string => {
857
- const validToolNames = toolNames.filter((n): n is ToolName => n in allBuiltInToolsMap);
858
- const extraToolDescriptions = toolNames
859
- .filter((name) => !(name in allBuiltInToolsMap))
860
- .map((name) => {
861
- const tool = toolRegistry.get(name);
862
- if (!tool) return null;
863
- return { name, description: tool.description || tool.label || "Custom tool" };
864
- })
865
- .filter((tool): tool is { name: string; description: string } => tool !== null);
819
+ const rebuildSystemPrompt = (toolNames: string[], tools: Map<string, AgentTool>): string => {
820
+ toolContextStore.setToolNames(toolNames);
866
821
  const defaultPrompt = buildSystemPromptInternal({
867
822
  cwd,
868
823
  skills,
869
824
  contextFiles,
870
- rulebookRules,
871
- selectedTools: validToolNames,
872
- extraToolDescriptions,
825
+ tools,
826
+ toolNames,
827
+ rules: rulebookRules,
873
828
  skillsSettings: settingsManager.getSkillsSettings(),
874
829
  });
875
830
 
@@ -881,9 +836,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
881
836
  cwd,
882
837
  skills,
883
838
  contextFiles,
884
- rulebookRules,
885
- selectedTools: validToolNames,
886
- extraToolDescriptions,
839
+ tools,
840
+ toolNames,
841
+ rules: rulebookRules,
887
842
  skillsSettings: settingsManager.getSkillsSettings(),
888
843
  customPrompt: options.systemPrompt,
889
844
  });
@@ -891,12 +846,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
891
846
  return options.systemPrompt(defaultPrompt);
892
847
  };
893
848
 
894
- const systemPrompt = rebuildSystemPrompt(initialActiveToolNames);
849
+ const systemPrompt = rebuildSystemPrompt(Array.from(toolRegistry.keys()), toolRegistry);
895
850
  time("buildSystemPrompt");
896
851
 
897
852
  const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
898
853
  time("discoverPromptTemplates");
899
854
 
855
+ const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd);
856
+ time("discoverSlashCommands");
857
+
900
858
  const baseSetUIContext = extensionsResult.setUIContext;
901
859
  extensionsResult.setUIContext = (uiContext, hasUI) => {
902
860
  baseSetUIContext(uiContext, hasUI);
@@ -908,7 +866,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
908
866
  systemPrompt,
909
867
  model,
910
868
  thinkingLevel,
911
- tools: activeToolsArray,
869
+ tools: Array.from(toolRegistry.values()),
912
870
  },
913
871
  convertToLlm,
914
872
  transformContext: extensionRunner
@@ -951,11 +909,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
951
909
  settingsManager,
952
910
  scopedModels: options.scopedModels,
953
911
  promptTemplates,
912
+ slashCommands,
954
913
  extensionRunner,
955
914
  customCommands: customCommandsResult.commands,
956
915
  skillsSettings: settingsManager.getSkillsSettings(),
957
916
  modelRegistry,
958
- toolRegistry: wrappedToolRegistry ?? toolRegistry,
917
+ toolRegistry,
959
918
  rebuildSystemPrompt,
960
919
  ttsrManager,
961
920
  });