@oh-my-pi/pi-coding-agent 1.340.0 → 2.0.1337

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 (153) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +5 -3
  5. package/src/cli/args.ts +13 -6
  6. package/src/cli/file-processor.ts +3 -3
  7. package/src/cli/list-models.ts +2 -2
  8. package/src/cli/plugin-cli.ts +1 -1
  9. package/src/cli/session-picker.ts +2 -2
  10. package/src/cli.ts +1 -1
  11. package/src/config.ts +3 -3
  12. package/src/core/agent-session.ts +189 -29
  13. package/src/core/bash-executor.ts +50 -10
  14. package/src/core/compaction/branch-summarization.ts +5 -5
  15. package/src/core/compaction/compaction.ts +3 -3
  16. package/src/core/compaction/index.ts +3 -3
  17. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  18. package/src/core/custom-commands/index.ts +15 -0
  19. package/src/core/custom-commands/loader.ts +232 -0
  20. package/src/core/custom-commands/types.ts +112 -0
  21. package/src/core/custom-tools/index.ts +3 -3
  22. package/src/core/custom-tools/loader.ts +10 -8
  23. package/src/core/custom-tools/types.ts +11 -6
  24. package/src/core/custom-tools/wrapper.ts +2 -1
  25. package/src/core/exec.ts +22 -12
  26. package/src/core/export-html/index.ts +5 -5
  27. package/src/core/file-mentions.ts +54 -0
  28. package/src/core/hooks/index.ts +5 -5
  29. package/src/core/hooks/loader.ts +21 -16
  30. package/src/core/hooks/runner.ts +6 -6
  31. package/src/core/hooks/tool-wrapper.ts +2 -2
  32. package/src/core/hooks/types.ts +12 -15
  33. package/src/core/index.ts +6 -6
  34. package/src/core/logger.ts +112 -0
  35. package/src/core/mcp/client.ts +3 -3
  36. package/src/core/mcp/config.ts +1 -1
  37. package/src/core/mcp/index.ts +12 -12
  38. package/src/core/mcp/loader.ts +2 -2
  39. package/src/core/mcp/manager.ts +6 -6
  40. package/src/core/mcp/tool-bridge.ts +3 -3
  41. package/src/core/mcp/transports/http.ts +1 -1
  42. package/src/core/mcp/transports/index.ts +2 -2
  43. package/src/core/mcp/transports/stdio.ts +1 -1
  44. package/src/core/messages.ts +22 -0
  45. package/src/core/model-registry.ts +2 -2
  46. package/src/core/model-resolver.ts +103 -2
  47. package/src/core/plugins/doctor.ts +1 -1
  48. package/src/core/plugins/index.ts +6 -6
  49. package/src/core/plugins/installer.ts +4 -4
  50. package/src/core/plugins/loader.ts +4 -9
  51. package/src/core/plugins/manager.ts +5 -5
  52. package/src/core/plugins/paths.ts +3 -3
  53. package/src/core/sdk.ts +127 -52
  54. package/src/core/session-manager.ts +123 -20
  55. package/src/core/settings-manager.ts +106 -22
  56. package/src/core/skills.ts +5 -5
  57. package/src/core/slash-commands.ts +60 -45
  58. package/src/core/system-prompt.ts +6 -6
  59. package/src/core/title-generator.ts +94 -0
  60. package/src/core/tools/bash.ts +33 -157
  61. package/src/core/tools/context.ts +2 -2
  62. package/src/core/tools/edit-diff.ts +5 -5
  63. package/src/core/tools/edit.ts +60 -9
  64. package/src/core/tools/exa/company.ts +3 -3
  65. package/src/core/tools/exa/index.ts +16 -17
  66. package/src/core/tools/exa/linkedin.ts +3 -3
  67. package/src/core/tools/exa/mcp-client.ts +9 -9
  68. package/src/core/tools/exa/render.ts +5 -5
  69. package/src/core/tools/exa/researcher.ts +3 -3
  70. package/src/core/tools/exa/search.ts +6 -5
  71. package/src/core/tools/exa/types.ts +5 -6
  72. package/src/core/tools/exa/websets.ts +3 -3
  73. package/src/core/tools/find.ts +3 -3
  74. package/src/core/tools/grep.ts +6 -5
  75. package/src/core/tools/index.ts +114 -40
  76. package/src/core/tools/ls.ts +4 -4
  77. package/src/core/tools/lsp/client.ts +204 -108
  78. package/src/core/tools/lsp/config.ts +709 -35
  79. package/src/core/tools/lsp/edits.ts +2 -2
  80. package/src/core/tools/lsp/index.ts +432 -30
  81. package/src/core/tools/lsp/render.ts +2 -2
  82. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  83. package/src/core/tools/lsp/types.ts +5 -0
  84. package/src/core/tools/lsp/utils.ts +1 -1
  85. package/src/core/tools/notebook.ts +1 -1
  86. package/src/core/tools/output.ts +175 -0
  87. package/src/core/tools/read.ts +7 -7
  88. package/src/core/tools/renderers.ts +92 -13
  89. package/src/core/tools/review.ts +268 -0
  90. package/src/core/tools/task/agents.ts +1 -1
  91. package/src/core/tools/task/bundled-agents/explore.md +1 -1
  92. package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
  93. package/src/core/tools/task/discovery.ts +2 -2
  94. package/src/core/tools/task/executor.ts +145 -28
  95. package/src/core/tools/task/index.ts +78 -30
  96. package/src/core/tools/task/model-resolver.ts +72 -13
  97. package/src/core/tools/task/parallel.ts +1 -1
  98. package/src/core/tools/task/render.ts +219 -30
  99. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  100. package/src/core/tools/task/types.ts +36 -2
  101. package/src/core/tools/web-fetch.ts +5 -3
  102. package/src/core/tools/web-search/auth.ts +1 -1
  103. package/src/core/tools/web-search/index.ts +17 -15
  104. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  105. package/src/core/tools/web-search/providers/exa.ts +3 -5
  106. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  107. package/src/core/tools/web-search/render.ts +3 -3
  108. package/src/core/tools/write.ts +70 -7
  109. package/src/index.ts +33 -17
  110. package/src/main.ts +60 -34
  111. package/src/migrations.ts +3 -3
  112. package/src/modes/index.ts +5 -5
  113. package/src/modes/interactive/components/armin.ts +1 -1
  114. package/src/modes/interactive/components/assistant-message.ts +1 -1
  115. package/src/modes/interactive/components/bash-execution.ts +4 -4
  116. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  117. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  118. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  119. package/src/modes/interactive/components/diff.ts +1 -1
  120. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  121. package/src/modes/interactive/components/footer.ts +5 -5
  122. package/src/modes/interactive/components/hook-editor.ts +2 -2
  123. package/src/modes/interactive/components/hook-input.ts +2 -2
  124. package/src/modes/interactive/components/hook-message.ts +3 -3
  125. package/src/modes/interactive/components/hook-selector.ts +2 -2
  126. package/src/modes/interactive/components/model-selector.ts +341 -41
  127. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  128. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  129. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  130. package/src/modes/interactive/components/session-selector.ts +24 -11
  131. package/src/modes/interactive/components/settings-defs.ts +51 -3
  132. package/src/modes/interactive/components/settings-selector.ts +13 -16
  133. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  134. package/src/modes/interactive/components/theme-selector.ts +2 -2
  135. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  136. package/src/modes/interactive/components/tool-execution.ts +44 -8
  137. package/src/modes/interactive/components/tree-selector.ts +5 -5
  138. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  139. package/src/modes/interactive/components/user-message.ts +1 -1
  140. package/src/modes/interactive/components/welcome.ts +42 -5
  141. package/src/modes/interactive/interactive-mode.ts +169 -48
  142. package/src/modes/interactive/theme/theme.ts +8 -7
  143. package/src/modes/print-mode.ts +4 -3
  144. package/src/modes/rpc/rpc-client.ts +4 -4
  145. package/src/modes/rpc/rpc-mode.ts +21 -11
  146. package/src/modes/rpc/rpc-types.ts +3 -3
  147. package/src/utils/changelog.ts +2 -2
  148. package/src/utils/clipboard.ts +1 -1
  149. package/src/utils/shell-snapshot.ts +218 -0
  150. package/src/utils/shell.ts +93 -13
  151. package/src/utils/tools-manager.ts +1 -1
  152. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  153. package/src/core/tools/exa/logger.ts +0 -56
@@ -6,8 +6,8 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
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
- import { isValidThinkingLevel } from "../cli/args.js";
10
- import type { ModelRegistry } from "./model-registry.js";
9
+ import { isValidThinkingLevel } from "../cli/args";
10
+ import type { ModelRegistry } from "./model-registry";
11
11
 
12
12
  /** Default model IDs for each known provider */
13
13
  export const defaultModelPerProvider: Record<KnownProvider, string> = {
@@ -30,6 +30,29 @@ export interface ScopedModel {
30
30
  thinkingLevel: ThinkingLevel;
31
31
  }
32
32
 
33
+ /** Priority chain for auto-discovering smol/fast models */
34
+ export const SMOL_MODEL_PRIORITY = ["claude-haiku-4-5", "haiku", "flash", "mini"];
35
+
36
+ /** Priority chain for auto-discovering slow/comprehensive models (reasoning, codex) */
37
+ export const SLOW_MODEL_PRIORITY = ["gpt-5.2-codex", "gpt-5.2", "codex", "gpt", "opus", "pro"];
38
+
39
+ /**
40
+ * Parse a model string in "provider/modelId" format.
41
+ * Returns undefined if the format is invalid.
42
+ */
43
+ export function parseModelString(modelStr: string): { provider: string; id: string } | undefined {
44
+ const slashIdx = modelStr.indexOf("/");
45
+ if (slashIdx <= 0) return undefined;
46
+ return { provider: modelStr.slice(0, slashIdx), id: modelStr.slice(slashIdx + 1) };
47
+ }
48
+
49
+ /**
50
+ * Format a model as "provider/modelId" string.
51
+ */
52
+ export function formatModelString(model: Model<Api>): string {
53
+ return `${model.provider}/${model.id}`;
54
+ }
55
+
33
56
  /**
34
57
  * Helper to check if a model ID looks like an alias (no date suffix)
35
58
  * Dates are typically in format: -20241022 or -20250929
@@ -391,3 +414,81 @@ export async function restoreModelFromSession(
391
414
  // No models available
392
415
  return { model: undefined, fallbackMessage: undefined };
393
416
  }
417
+
418
+ /**
419
+ * Find a smol/fast model using the priority chain.
420
+ * Tries exact matches first, then fuzzy matches.
421
+ *
422
+ * @param modelRegistry The model registry to search
423
+ * @param savedModel Optional saved model string from settings (provider/modelId)
424
+ * @returns The best available smol model, or undefined if none found
425
+ */
426
+ export async function findSmolModel(
427
+ modelRegistry: ModelRegistry,
428
+ savedModel?: string,
429
+ ): Promise<Model<Api> | undefined> {
430
+ const availableModels = await modelRegistry.getAvailable();
431
+ if (availableModels.length === 0) return undefined;
432
+
433
+ // 1. Try saved model from settings
434
+ if (savedModel) {
435
+ const parsed = parseModelString(savedModel);
436
+ if (parsed) {
437
+ const match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
438
+ if (match) return match;
439
+ }
440
+ }
441
+
442
+ // 2. Try priority chain
443
+ for (const pattern of SMOL_MODEL_PRIORITY) {
444
+ // Try exact match first
445
+ const exactMatch = availableModels.find((m) => m.id.toLowerCase() === pattern.toLowerCase());
446
+ if (exactMatch) return exactMatch;
447
+
448
+ // Try fuzzy match (substring)
449
+ const fuzzyMatch = availableModels.find((m) => m.id.toLowerCase().includes(pattern.toLowerCase()));
450
+ if (fuzzyMatch) return fuzzyMatch;
451
+ }
452
+
453
+ // 3. Fallback to first available (same as default)
454
+ return availableModels[0];
455
+ }
456
+
457
+ /**
458
+ * Find a slow/comprehensive model using the priority chain.
459
+ * Prioritizes reasoning and codex models for thorough analysis.
460
+ *
461
+ * @param modelRegistry The model registry to search
462
+ * @param savedModel Optional saved model string from settings (provider/modelId)
463
+ * @returns The best available slow model, or undefined if none found
464
+ */
465
+ export async function findSlowModel(
466
+ modelRegistry: ModelRegistry,
467
+ savedModel?: string,
468
+ ): Promise<Model<Api> | undefined> {
469
+ const availableModels = await modelRegistry.getAvailable();
470
+ if (availableModels.length === 0) return undefined;
471
+
472
+ // 1. Try saved model from settings
473
+ if (savedModel) {
474
+ const parsed = parseModelString(savedModel);
475
+ if (parsed) {
476
+ const match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
477
+ if (match) return match;
478
+ }
479
+ }
480
+
481
+ // 2. Try priority chain
482
+ for (const pattern of SLOW_MODEL_PRIORITY) {
483
+ // Try exact match first
484
+ const exactMatch = availableModels.find((m) => m.id.toLowerCase() === pattern.toLowerCase());
485
+ if (exactMatch) return exactMatch;
486
+
487
+ // Try fuzzy match (substring)
488
+ const fuzzyMatch = availableModels.find((m) => m.id.toLowerCase().includes(pattern.toLowerCase()));
489
+ if (fuzzyMatch) return fuzzyMatch;
490
+ }
491
+
492
+ // 3. Fallback to first available (same as default)
493
+ return availableModels[0];
494
+ }
@@ -1,4 +1,4 @@
1
- import type { DoctorCheck } from "./types.js";
1
+ import type { DoctorCheck } from "./types";
2
2
 
3
3
  export async function runDoctorChecks(): Promise<DoctorCheck[]> {
4
4
  const checks: DoctorCheck[] = [];
@@ -1,5 +1,5 @@
1
1
  // Plugin system exports
2
- export { formatDoctorResults, runDoctorChecks } from "./doctor.js";
2
+ export { formatDoctorResults, runDoctorChecks } from "./doctor";
3
3
  export {
4
4
  getAllPluginCommandPaths,
5
5
  getAllPluginHookPaths,
@@ -9,16 +9,16 @@ export {
9
9
  resolvePluginCommandPaths,
10
10
  resolvePluginHookPaths,
11
11
  resolvePluginToolPaths,
12
- } from "./loader.js";
13
- export { PluginManager, parseSettingValue, validateSetting } from "./manager.js";
14
- export { extractPackageName, formatPluginSpec, parsePluginSpec } from "./parser.js";
12
+ } from "./loader";
13
+ export { PluginManager, parseSettingValue, validateSetting } from "./manager";
14
+ export { extractPackageName, formatPluginSpec, parsePluginSpec } from "./parser";
15
15
  export {
16
16
  getPluginsDir,
17
17
  getPluginsLockfile,
18
18
  getPluginsNodeModules,
19
19
  getPluginsPackageJson,
20
20
  getProjectPluginOverrides,
21
- } from "./paths.js";
21
+ } from "./paths";
22
22
  export type {
23
23
  BooleanSetting,
24
24
  DoctorCheck,
@@ -35,4 +35,4 @@ export type {
35
35
  PluginSettingType,
36
36
  ProjectPluginOverrides,
37
37
  StringSetting,
38
- } from "./types.js";
38
+ } from "./types";
@@ -1,7 +1,7 @@
1
- import { mkdir } from "fs/promises";
2
- import { join, resolve } from "path";
3
- import { getAgentDir } from "../../config.js";
4
- import type { InstalledPlugin } from "./types.js";
1
+ import { mkdir } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { getAgentDir } from "../../config";
4
+ import type { InstalledPlugin } from "./types";
5
5
 
6
6
  const PLUGINS_DIR = join(getAgentDir(), "plugins");
7
7
 
@@ -5,15 +5,10 @@
5
5
  * based on manifest entries and enabled features.
6
6
  */
7
7
 
8
- import { existsSync, readFileSync } from "fs";
9
- import { join } from "path";
10
- import {
11
- getPluginsLockfile,
12
- getPluginsNodeModules,
13
- getPluginsPackageJson,
14
- getProjectPluginOverrides,
15
- } from "./paths.js";
16
- import type { InstalledPlugin, PluginManifest, PluginRuntimeConfig, ProjectPluginOverrides } from "./types.js";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson, getProjectPluginOverrides } from "./paths";
11
+ import type { InstalledPlugin, PluginManifest, PluginRuntimeConfig, ProjectPluginOverrides } from "./types";
17
12
 
18
13
  // =============================================================================
19
14
  // Runtime Config Loading
@@ -1,13 +1,13 @@
1
- import { existsSync, lstatSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
2
- import { join, resolve } from "path";
3
- import { extractPackageName, parsePluginSpec } from "./parser.js";
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { extractPackageName, parsePluginSpec } from "./parser";
4
4
  import {
5
5
  getPluginsDir,
6
6
  getPluginsLockfile,
7
7
  getPluginsNodeModules,
8
8
  getPluginsPackageJson,
9
9
  getProjectPluginOverrides,
10
- } from "./paths.js";
10
+ } from "./paths";
11
11
  import type {
12
12
  DoctorCheck,
13
13
  DoctorOptions,
@@ -17,7 +17,7 @@ import type {
17
17
  PluginRuntimeConfig,
18
18
  PluginSettingSchema,
19
19
  ProjectPluginOverrides,
20
- } from "./types.js";
20
+ } from "./types";
21
21
 
22
22
  // =============================================================================
23
23
  // Validation
@@ -1,6 +1,6 @@
1
- import { homedir } from "os";
2
- import { join } from "path";
3
- import { CONFIG_DIR_NAME } from "../../config.js";
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { CONFIG_DIR_NAME } from "../../config";
4
4
 
5
5
  // =============================================================================
6
6
  // Plugin Directory Paths
package/src/core/sdk.ts CHANGED
@@ -29,34 +29,38 @@
29
29
  * ```
30
30
  */
31
31
 
32
+ import { join } from "node:path";
32
33
  import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
33
34
  import type { Model } from "@oh-my-pi/pi-ai";
34
- import { join } from "path";
35
- import { getAgentDir } from "../config.js";
36
- import { AgentSession } from "./agent-session.js";
37
- import { AuthStorage } from "./auth-storage.js";
35
+ import { getAgentDir } from "../config";
36
+ import { AgentSession } from "./agent-session";
37
+ import { AuthStorage } from "./auth-storage";
38
+ import {
39
+ type CustomCommandsLoadResult,
40
+ loadCustomCommands as loadCustomCommandsInternal,
41
+ } from "./custom-commands/index";
38
42
  import {
39
43
  type CustomToolsLoadResult,
40
44
  discoverAndLoadCustomTools,
41
45
  type LoadedCustomTool,
42
46
  wrapCustomTools,
43
- } from "./custom-tools/index.js";
44
- import type { CustomTool } from "./custom-tools/types.js";
45
- import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index.js";
46
- import type { HookFactory } from "./hooks/types.js";
47
- import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index.js";
48
- import { convertToLlm } from "./messages.js";
49
- import { ModelRegistry } from "./model-registry.js";
50
- import { SessionManager } from "./session-manager.js";
51
- import { type Settings, SettingsManager, type SkillsSettings } from "./settings-manager.js";
52
- import { loadSkills as loadSkillsInternal, type Skill } from "./skills.js";
53
- import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands.js";
47
+ } from "./custom-tools/index";
48
+ import type { CustomTool } from "./custom-tools/types";
49
+ import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index";
50
+ import type { HookFactory } from "./hooks/types";
51
+ import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
52
+ import { convertToLlm } from "./messages";
53
+ import { ModelRegistry } from "./model-registry";
54
+ import { SessionManager } from "./session-manager";
55
+ import { type CommandsSettings, type Settings, SettingsManager, type SkillsSettings } from "./settings-manager";
56
+ import { loadSkills as loadSkillsInternal, type Skill } from "./skills";
57
+ import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands";
54
58
  import {
55
59
  buildSystemPrompt as buildSystemPromptInternal,
56
60
  loadProjectContextFiles as loadContextFilesInternal,
57
- } from "./system-prompt.js";
58
- import { time } from "./timings.js";
59
- import { createToolContextStore } from "./tools/context.js";
61
+ } from "./system-prompt";
62
+ import { time } from "./timings";
63
+ import { createToolContextStore } from "./tools/context";
60
64
  import {
61
65
  allTools,
62
66
  applyBashInterception,
@@ -78,8 +82,9 @@ import {
78
82
  readOnlyTools,
79
83
  readTool,
80
84
  type Tool,
85
+ warmupLspServers,
81
86
  writeTool,
82
- } from "./tools/index.js";
87
+ } from "./tools/index";
83
88
 
84
89
  // Types
85
90
 
@@ -126,6 +131,9 @@ export interface CreateAgentSessionOptions {
126
131
  /** Enable MCP server discovery from .mcp.json files. Default: true */
127
132
  enableMCP?: boolean;
128
133
 
134
+ /** Tool names explicitly requested (enables disabled-by-default tools) */
135
+ explicitTools?: string[];
136
+
129
137
  /** Session manager. Default: SessionManager.create(cwd) */
130
138
  sessionManager?: SessionManager;
131
139
 
@@ -146,17 +154,20 @@ export interface CreateAgentSessionResult {
146
154
  mcpManager?: MCPManager;
147
155
  /** Warning if session was restored with a different model than saved */
148
156
  modelFallbackMessage?: string;
157
+ /** LSP servers that were warmed up at startup */
158
+ lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
149
159
  }
150
160
 
151
161
  // Re-exports
152
162
 
153
- export type { CustomTool } from "./custom-tools/types.js";
154
- export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types.js";
155
- export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index.js";
156
- export type { Settings, SkillsSettings } from "./settings-manager.js";
157
- export type { Skill } from "./skills.js";
158
- export type { FileSlashCommand } from "./slash-commands.js";
159
- export type { Tool } from "./tools/index.js";
163
+ export type { CustomCommand, CustomCommandFactory } from "./custom-commands/types";
164
+ export type { CustomTool } from "./custom-tools/types";
165
+ export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types";
166
+ export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index";
167
+ export type { Settings, SkillsSettings } from "./settings-manager";
168
+ export type { Skill } from "./skills";
169
+ export type { FileSlashCommand } from "./slash-commands";
170
+ export type { Tool } from "./tools/index";
160
171
 
161
172
  export {
162
173
  // Pre-built tools (use process.cwd())
@@ -275,10 +286,29 @@ export function discoverContextFiles(cwd?: string, agentDir?: string): Array<{ p
275
286
  /**
276
287
  * Discover slash commands from cwd and agentDir.
277
288
  */
278
- export function discoverSlashCommands(cwd?: string, agentDir?: string): FileSlashCommand[] {
289
+ export function discoverSlashCommands(
290
+ cwd?: string,
291
+ agentDir?: string,
292
+ settings?: CommandsSettings,
293
+ ): FileSlashCommand[] {
279
294
  return loadSlashCommandsInternal({
280
295
  cwd: cwd ?? process.cwd(),
281
296
  agentDir: agentDir ?? getDefaultAgentDir(),
297
+ enableClaudeUser: settings?.enableClaudeUser,
298
+ enableClaudeProject: settings?.enableClaudeProject,
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Discover custom commands (TypeScript slash commands) from cwd and agentDir.
304
+ */
305
+ export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
306
+ const resolvedCwd = cwd ?? process.cwd();
307
+ const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
308
+
309
+ return loadCustomCommandsInternal({
310
+ cwd: resolvedCwd,
311
+ agentDir: resolvedAgentDir,
282
312
  });
283
313
  }
284
314
 
@@ -323,8 +353,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
323
353
  export function loadSettings(cwd?: string, agentDir?: string): Settings {
324
354
  const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
325
355
  return {
326
- defaultProvider: manager.getDefaultProvider(),
327
- defaultModel: manager.getDefaultModel(),
356
+ modelRoles: manager.getModelRoles(),
328
357
  defaultThinkingLevel: manager.getDefaultThinkingLevel(),
329
358
  queueMode: manager.getQueueMode(),
330
359
  theme: manager.getTheme(),
@@ -479,24 +508,34 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
479
508
  let modelFallbackMessage: string | undefined;
480
509
 
481
510
  // If session has data, try to restore model from it
482
- if (!model && hasExistingSession && existingSession.model) {
483
- const restoredModel = modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);
484
- if (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {
485
- model = restoredModel;
486
- }
487
- if (!model) {
488
- modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;
511
+ const defaultModelStr = existingSession.models.default;
512
+ if (!model && hasExistingSession && defaultModelStr) {
513
+ const slashIdx = defaultModelStr.indexOf("/");
514
+ if (slashIdx > 0) {
515
+ const provider = defaultModelStr.slice(0, slashIdx);
516
+ const modelId = defaultModelStr.slice(slashIdx + 1);
517
+ const restoredModel = modelRegistry.find(provider, modelId);
518
+ if (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {
519
+ model = restoredModel;
520
+ }
521
+ if (!model) {
522
+ modelFallbackMessage = `Could not restore model ${defaultModelStr}`;
523
+ }
489
524
  }
490
525
  }
491
526
 
492
527
  // If still no model, try settings default
493
528
  if (!model) {
494
- const defaultProvider = settingsManager.getDefaultProvider();
495
- const defaultModelId = settingsManager.getDefaultModel();
496
- if (defaultProvider && defaultModelId) {
497
- const settingsModel = modelRegistry.find(defaultProvider, defaultModelId);
498
- if (settingsModel && (await modelRegistry.getApiKey(settingsModel))) {
499
- model = settingsModel;
529
+ const settingsDefaultModel = settingsManager.getModelRole("default");
530
+ if (settingsDefaultModel) {
531
+ const slashIdx = settingsDefaultModel.indexOf("/");
532
+ if (slashIdx > 0) {
533
+ const provider = settingsDefaultModel.slice(0, slashIdx);
534
+ const modelId = settingsDefaultModel.slice(slashIdx + 1);
535
+ const settingsModel = modelRegistry.find(provider, modelId);
536
+ if (settingsModel && (await modelRegistry.getApiKey(settingsModel))) {
537
+ model = settingsModel;
538
+ }
500
539
  }
501
540
  }
502
541
  }
@@ -543,12 +582,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
543
582
  const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
544
583
  time("discoverContextFiles");
545
584
 
546
- // Hook runner - created early for hooks
547
- let hookRunner: HookRunner | undefined;
585
+ // Hook runner - always created (needed for custom command context even without hooks)
586
+ let loadedHooks: LoadedHook[] = [];
548
587
  if (options.hooks !== undefined) {
549
588
  if (options.hooks.length > 0) {
550
- const loadedHooks = createLoadedHooksFromDefinitions(options.hooks);
551
- hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
589
+ loadedHooks = createLoadedHooksFromDefinitions(options.hooks);
552
590
  }
553
591
  } else {
554
592
  // Discover hooks, merging with additional paths
@@ -558,15 +596,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
558
596
  for (const { path, error } of errors) {
559
597
  console.error(`Failed to load hook "${path}": ${error}`);
560
598
  }
561
- if (hooks.length > 0) {
562
- hookRunner = new HookRunner(hooks, cwd, sessionManager, modelRegistry);
563
- }
599
+ loadedHooks = hooks;
564
600
  }
601
+ const hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
565
602
 
566
603
  const sessionContext = {
567
604
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
568
605
  };
569
- const builtInTools = options.tools ?? createCodingTools(cwd, options.hasUI ?? false, sessionContext);
606
+ const builtInTools =
607
+ options.tools ??
608
+ createCodingTools(cwd, options.hasUI ?? false, sessionContext, {
609
+ lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
610
+ lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
611
+ lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
612
+ editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
613
+ });
570
614
  time("createCodingTools");
571
615
 
572
616
  let customToolsResult: CustomToolsLoadResult;
@@ -676,6 +720,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
676
720
  };
677
721
 
678
722
  let allToolsArray: Tool[] = [...builtInTools, ...wrappedCustomTools];
723
+
724
+ // Filter out hidden tools unless explicitly requested
725
+ if (options.explicitTools) {
726
+ const explicitSet = new Set(options.explicitTools);
727
+ allToolsArray = allToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
728
+ } else {
729
+ allToolsArray = allToolsArray.filter((tool) => !tool.hidden);
730
+ }
679
731
  time("combineTools");
680
732
 
681
733
  // Apply bash interception to redirect common shell patterns to proper tools (if enabled)
@@ -711,9 +763,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
711
763
  systemPrompt = options.systemPrompt(defaultPrompt);
712
764
  }
713
765
 
714
- const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd, agentDir);
766
+ const commandsSettings = settingsManager.getCommandsSettings();
767
+ const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd, agentDir, commandsSettings);
715
768
  time("discoverSlashCommands");
716
769
 
770
+ // Discover custom commands (TypeScript slash commands)
771
+ const customCommandsResult = await loadCustomCommandsInternal({ cwd, agentDir });
772
+ time("discoverCustomCommands");
773
+ for (const { path, error } of customCommandsResult.errors) {
774
+ console.error(`Failed to load custom command "${path}": ${error}`);
775
+ }
776
+
717
777
  agent = new Agent({
718
778
  initialState: {
719
779
  systemPrompt,
@@ -728,6 +788,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
728
788
  }
729
789
  : undefined,
730
790
  queueMode: settingsManager.getQueueMode(),
791
+ interruptMode: settingsManager.getInterruptMode(),
731
792
  getToolContext: toolContextStore.getContext,
732
793
  getApiKey: async () => {
733
794
  const currentModel = agent.state.model;
@@ -749,7 +810,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
749
810
  } else {
750
811
  // Save initial model and thinking level for new sessions so they can be restored on resume
751
812
  if (model) {
752
- sessionManager.appendModelChange(model.provider, model.id);
813
+ sessionManager.appendModelChange(`${model.provider}/${model.id}`);
753
814
  }
754
815
  sessionManager.appendThinkingLevelChange(thinkingLevel);
755
816
  }
@@ -762,15 +823,29 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
762
823
  fileCommands: slashCommands,
763
824
  hookRunner,
764
825
  customTools: customToolsResult.tools,
826
+ customCommands: customCommandsResult.commands,
765
827
  skillsSettings: settingsManager.getSkillsSettings(),
766
828
  modelRegistry,
767
829
  });
768
830
  time("createAgentSession");
769
831
 
832
+ // Warm up LSP servers (connects to detected servers)
833
+ let lspServers: CreateAgentSessionResult["lspServers"];
834
+ if (settingsManager.getLspDiagnosticsOnWrite()) {
835
+ try {
836
+ const result = await warmupLspServers(cwd);
837
+ lspServers = result.servers;
838
+ time("warmupLspServers");
839
+ } catch {
840
+ // Ignore warmup errors
841
+ }
842
+ }
843
+
770
844
  return {
771
845
  session,
772
846
  customToolsResult,
773
847
  mcpManager,
774
848
  modelFallbackMessage,
849
+ lspServers,
775
850
  };
776
851
  }