@oh-my-pi/pi-coding-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
package/src/core/sdk.ts
ADDED
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK for programmatic usage of AgentSession.
|
|
3
|
+
*
|
|
4
|
+
* Provides a factory function and discovery helpers that allow full control
|
|
5
|
+
* over agent configuration, or sensible defaults that match CLI behavior.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Minimal - everything auto-discovered
|
|
10
|
+
* const session = await createAgentSession();
|
|
11
|
+
*
|
|
12
|
+
* // With custom hooks
|
|
13
|
+
* const session = await createAgentSession({
|
|
14
|
+
* hooks: [
|
|
15
|
+
* ...await discoverHooks(),
|
|
16
|
+
* { factory: myHookFactory },
|
|
17
|
+
* ],
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Full control
|
|
21
|
+
* const session = await createAgentSession({
|
|
22
|
+
* model: myModel,
|
|
23
|
+
* getApiKey: async () => process.env.MY_KEY,
|
|
24
|
+
* tools: [readTool, bashTool],
|
|
25
|
+
* hooks: [],
|
|
26
|
+
* skills: [],
|
|
27
|
+
* sessionFile: false,
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
34
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
35
|
+
import chalk from "chalk";
|
|
36
|
+
// Import discovery to register all providers on startup
|
|
37
|
+
import "../discovery";
|
|
38
|
+
import { loadSync as loadCapability } from "../capability/index";
|
|
39
|
+
import { type Rule, ruleCapability } from "../capability/rule";
|
|
40
|
+
import { getAgentDir, getConfigDirPaths } from "../config";
|
|
41
|
+
import { AgentSession } from "./agent-session";
|
|
42
|
+
import { AuthStorage } from "./auth-storage";
|
|
43
|
+
import {
|
|
44
|
+
type CustomCommandsLoadResult,
|
|
45
|
+
loadCustomCommands as loadCustomCommandsInternal,
|
|
46
|
+
} from "./custom-commands/index";
|
|
47
|
+
import {
|
|
48
|
+
type CustomToolsLoadResult,
|
|
49
|
+
discoverAndLoadCustomTools,
|
|
50
|
+
type LoadedCustomTool,
|
|
51
|
+
wrapCustomTools,
|
|
52
|
+
} from "./custom-tools/index";
|
|
53
|
+
import type { CustomTool } from "./custom-tools/types";
|
|
54
|
+
import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index";
|
|
55
|
+
import type { HookFactory } from "./hooks/types";
|
|
56
|
+
import { logger } from "./logger";
|
|
57
|
+
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
|
|
58
|
+
import { convertToLlm } from "./messages";
|
|
59
|
+
import { ModelRegistry } from "./model-registry";
|
|
60
|
+
import { SessionManager } from "./session-manager";
|
|
61
|
+
import { type CommandsSettings, type Settings, SettingsManager, type SkillsSettings } from "./settings-manager";
|
|
62
|
+
import { loadSkills as loadSkillsInternal, type Skill } from "./skills";
|
|
63
|
+
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands";
|
|
64
|
+
import {
|
|
65
|
+
buildSystemPrompt as buildSystemPromptInternal,
|
|
66
|
+
loadProjectContextFiles as loadContextFilesInternal,
|
|
67
|
+
} from "./system-prompt";
|
|
68
|
+
import { time } from "./timings";
|
|
69
|
+
import { createToolContextStore } from "./tools/context";
|
|
70
|
+
import {
|
|
71
|
+
allTools,
|
|
72
|
+
applyBashInterception,
|
|
73
|
+
bashTool,
|
|
74
|
+
codingTools,
|
|
75
|
+
createBashTool,
|
|
76
|
+
createCodingTools,
|
|
77
|
+
createEditTool,
|
|
78
|
+
createFindTool,
|
|
79
|
+
createGrepTool,
|
|
80
|
+
createLsTool,
|
|
81
|
+
createReadOnlyTools,
|
|
82
|
+
createReadTool,
|
|
83
|
+
createRulebookTool,
|
|
84
|
+
createWriteTool,
|
|
85
|
+
editTool,
|
|
86
|
+
filterRulebookRules,
|
|
87
|
+
findTool,
|
|
88
|
+
grepTool,
|
|
89
|
+
lsTool,
|
|
90
|
+
readOnlyTools,
|
|
91
|
+
readTool,
|
|
92
|
+
type Tool,
|
|
93
|
+
warmupLspServers,
|
|
94
|
+
writeTool,
|
|
95
|
+
} from "./tools/index";
|
|
96
|
+
import { createTtsrManager } from "./ttsr";
|
|
97
|
+
|
|
98
|
+
// Types
|
|
99
|
+
|
|
100
|
+
export interface CreateAgentSessionOptions {
|
|
101
|
+
/** Working directory for project-local discovery. Default: process.cwd() */
|
|
102
|
+
cwd?: string;
|
|
103
|
+
/** Global config directory. Default: ~/.omp/agent */
|
|
104
|
+
agentDir?: string;
|
|
105
|
+
|
|
106
|
+
/** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
|
|
107
|
+
authStorage?: AuthStorage;
|
|
108
|
+
/** Model registry. Default: discoverModels(authStorage, agentDir) */
|
|
109
|
+
modelRegistry?: ModelRegistry;
|
|
110
|
+
|
|
111
|
+
/** Model to use. Default: from settings, else first available */
|
|
112
|
+
model?: Model<any>;
|
|
113
|
+
/** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */
|
|
114
|
+
thinkingLevel?: ThinkingLevel;
|
|
115
|
+
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
116
|
+
scopedModels?: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
|
|
117
|
+
|
|
118
|
+
/** System prompt. String replaces default, function receives default and returns final. */
|
|
119
|
+
systemPrompt?: string | ((defaultPrompt: string) => string);
|
|
120
|
+
|
|
121
|
+
/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */
|
|
122
|
+
tools?: Tool[];
|
|
123
|
+
/** Custom tools (replaces discovery). */
|
|
124
|
+
customTools?: Array<{ path?: string; tool: CustomTool }>;
|
|
125
|
+
/** Additional custom tool paths to load (merged with discovery). */
|
|
126
|
+
additionalCustomToolPaths?: string[];
|
|
127
|
+
|
|
128
|
+
/** Hooks (replaces discovery). */
|
|
129
|
+
hooks?: Array<{ path?: string; factory: HookFactory }>;
|
|
130
|
+
/** Additional hook paths to load (merged with discovery). */
|
|
131
|
+
additionalHookPaths?: string[];
|
|
132
|
+
|
|
133
|
+
/** Skills. Default: discovered from multiple locations */
|
|
134
|
+
skills?: Skill[];
|
|
135
|
+
/** Context files (AGENTS.md content). Default: discovered walking up from cwd */
|
|
136
|
+
contextFiles?: Array<{ path: string; content: string }>;
|
|
137
|
+
/** Slash commands. Default: discovered from cwd/.omp/commands/ + agentDir/commands/ */
|
|
138
|
+
slashCommands?: FileSlashCommand[];
|
|
139
|
+
|
|
140
|
+
/** Enable MCP server discovery from .mcp.json files. Default: true */
|
|
141
|
+
enableMCP?: boolean;
|
|
142
|
+
|
|
143
|
+
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
144
|
+
explicitTools?: string[];
|
|
145
|
+
|
|
146
|
+
/** Session manager. Default: SessionManager.create(cwd) */
|
|
147
|
+
sessionManager?: SessionManager;
|
|
148
|
+
|
|
149
|
+
/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */
|
|
150
|
+
settingsManager?: SettingsManager;
|
|
151
|
+
|
|
152
|
+
/** Whether UI is available (enables interactive tools like ask). Default: false */
|
|
153
|
+
hasUI?: boolean;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Result from createAgentSession */
|
|
157
|
+
export interface CreateAgentSessionResult {
|
|
158
|
+
/** The created session */
|
|
159
|
+
session: AgentSession;
|
|
160
|
+
/** Custom tools result (for UI context setup in interactive mode) */
|
|
161
|
+
customToolsResult: CustomToolsLoadResult;
|
|
162
|
+
/** MCP manager for server lifecycle management (undefined if MCP disabled) */
|
|
163
|
+
mcpManager?: MCPManager;
|
|
164
|
+
/** Warning if session was restored with a different model than saved */
|
|
165
|
+
modelFallbackMessage?: string;
|
|
166
|
+
/** LSP servers that were warmed up at startup */
|
|
167
|
+
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Re-exports
|
|
171
|
+
|
|
172
|
+
export type { CustomCommand, CustomCommandFactory } from "./custom-commands/types";
|
|
173
|
+
export type { CustomTool } from "./custom-tools/types";
|
|
174
|
+
export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types";
|
|
175
|
+
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index";
|
|
176
|
+
export type { Settings, SkillsSettings } from "./settings-manager";
|
|
177
|
+
export type { Skill } from "./skills";
|
|
178
|
+
export type { FileSlashCommand } from "./slash-commands";
|
|
179
|
+
export type { Tool } from "./tools/index";
|
|
180
|
+
|
|
181
|
+
export {
|
|
182
|
+
// Pre-built tools (use process.cwd())
|
|
183
|
+
readTool,
|
|
184
|
+
bashTool,
|
|
185
|
+
editTool,
|
|
186
|
+
writeTool,
|
|
187
|
+
grepTool,
|
|
188
|
+
findTool,
|
|
189
|
+
lsTool,
|
|
190
|
+
codingTools,
|
|
191
|
+
readOnlyTools,
|
|
192
|
+
allTools as allBuiltInTools,
|
|
193
|
+
// Tool factories (for custom cwd)
|
|
194
|
+
createCodingTools,
|
|
195
|
+
createReadOnlyTools,
|
|
196
|
+
createReadTool,
|
|
197
|
+
createBashTool,
|
|
198
|
+
createEditTool,
|
|
199
|
+
createWriteTool,
|
|
200
|
+
createGrepTool,
|
|
201
|
+
createFindTool,
|
|
202
|
+
createLsTool,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Helper Functions
|
|
206
|
+
|
|
207
|
+
function getDefaultAgentDir(): string {
|
|
208
|
+
return getAgentDir();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Discovery Functions
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create an AuthStorage instance with fallback support.
|
|
215
|
+
* Reads from primary path first, then falls back to legacy paths (.pi, .claude).
|
|
216
|
+
*/
|
|
217
|
+
export function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): AuthStorage {
|
|
218
|
+
const primaryPath = join(agentDir, "auth.json");
|
|
219
|
+
// Get all auth.json paths (user-level only), excluding the primary
|
|
220
|
+
const allPaths = getConfigDirPaths("auth.json", { project: false });
|
|
221
|
+
const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
|
|
222
|
+
|
|
223
|
+
logger.debug("discoverAuthStorage", { agentDir, primaryPath, allPaths, fallbackPaths });
|
|
224
|
+
|
|
225
|
+
return new AuthStorage(primaryPath, fallbackPaths);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Create a ModelRegistry with fallback support.
|
|
230
|
+
* Reads from primary path first, then falls back to legacy paths (.pi, .claude).
|
|
231
|
+
*/
|
|
232
|
+
export function discoverModels(authStorage: AuthStorage, agentDir: string = getDefaultAgentDir()): ModelRegistry {
|
|
233
|
+
const primaryPath = join(agentDir, "models.json");
|
|
234
|
+
// Get all models.json paths (user-level only), excluding the primary
|
|
235
|
+
const allPaths = getConfigDirPaths("models.json", { project: false });
|
|
236
|
+
const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
|
|
237
|
+
|
|
238
|
+
logger.debug("discoverModels", { primaryPath, fallbackPaths });
|
|
239
|
+
|
|
240
|
+
return new ModelRegistry(authStorage, primaryPath, fallbackPaths);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Discover hooks from cwd and agentDir.
|
|
245
|
+
*/
|
|
246
|
+
export async function discoverHooks(
|
|
247
|
+
cwd?: string,
|
|
248
|
+
_agentDir?: string,
|
|
249
|
+
): Promise<Array<{ path: string; factory: HookFactory }>> {
|
|
250
|
+
const resolvedCwd = cwd ?? process.cwd();
|
|
251
|
+
|
|
252
|
+
const { hooks, errors } = await discoverAndLoadHooks([], resolvedCwd);
|
|
253
|
+
|
|
254
|
+
// Log errors but don't fail
|
|
255
|
+
for (const { path, error } of errors) {
|
|
256
|
+
console.error(`Failed to load hook "${path}": ${error}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return hooks.map((h) => ({
|
|
260
|
+
path: h.path,
|
|
261
|
+
factory: createFactoryFromLoadedHook(h),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Discover custom tools from cwd and agentDir.
|
|
267
|
+
*/
|
|
268
|
+
export async function discoverCustomTools(
|
|
269
|
+
cwd?: string,
|
|
270
|
+
_agentDir?: string,
|
|
271
|
+
): Promise<Array<{ path: string; tool: CustomTool }>> {
|
|
272
|
+
const resolvedCwd = cwd ?? process.cwd();
|
|
273
|
+
|
|
274
|
+
const { tools, errors } = await discoverAndLoadCustomTools([], resolvedCwd, Object.keys(allTools));
|
|
275
|
+
|
|
276
|
+
// Log errors but don't fail
|
|
277
|
+
for (const { path, error } of errors) {
|
|
278
|
+
console.error(`Failed to load custom tool "${path}": ${error}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return tools.map((t) => ({
|
|
282
|
+
path: t.path,
|
|
283
|
+
tool: t.tool,
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Discover skills from cwd and agentDir.
|
|
289
|
+
*/
|
|
290
|
+
export function discoverSkills(cwd?: string, _agentDir?: string, settings?: SkillsSettings): Skill[] {
|
|
291
|
+
const { skills } = loadSkillsInternal({
|
|
292
|
+
...settings,
|
|
293
|
+
cwd: cwd ?? process.cwd(),
|
|
294
|
+
});
|
|
295
|
+
return skills;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Discover context files (AGENTS.md) walking up from cwd.
|
|
300
|
+
* Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
|
|
301
|
+
*/
|
|
302
|
+
export function discoverContextFiles(
|
|
303
|
+
cwd?: string,
|
|
304
|
+
_agentDir?: string,
|
|
305
|
+
): Array<{ path: string; content: string; depth?: number }> {
|
|
306
|
+
return loadContextFilesInternal({
|
|
307
|
+
cwd: cwd ?? process.cwd(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Discover slash commands from cwd and agentDir.
|
|
313
|
+
*/
|
|
314
|
+
export function discoverSlashCommands(
|
|
315
|
+
cwd?: string,
|
|
316
|
+
_agentDir?: string,
|
|
317
|
+
_settings?: CommandsSettings,
|
|
318
|
+
): FileSlashCommand[] {
|
|
319
|
+
return loadSlashCommandsInternal({
|
|
320
|
+
cwd: cwd ?? process.cwd(),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Discover custom commands (TypeScript slash commands) from cwd and agentDir.
|
|
326
|
+
*/
|
|
327
|
+
export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
|
|
328
|
+
const resolvedCwd = cwd ?? process.cwd();
|
|
329
|
+
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
|
330
|
+
|
|
331
|
+
return loadCustomCommandsInternal({
|
|
332
|
+
cwd: resolvedCwd,
|
|
333
|
+
agentDir: resolvedAgentDir,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Discover MCP servers from .mcp.json files.
|
|
339
|
+
* Returns the manager and loaded tools.
|
|
340
|
+
*/
|
|
341
|
+
export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
|
|
342
|
+
const resolvedCwd = cwd ?? process.cwd();
|
|
343
|
+
return discoverAndLoadMCPTools(resolvedCwd);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// API Key Helpers
|
|
347
|
+
|
|
348
|
+
// System Prompt
|
|
349
|
+
|
|
350
|
+
export interface BuildSystemPromptOptions {
|
|
351
|
+
tools?: Tool[];
|
|
352
|
+
skills?: Skill[];
|
|
353
|
+
contextFiles?: Array<{ path: string; content: string }>;
|
|
354
|
+
cwd?: string;
|
|
355
|
+
appendPrompt?: string;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Build the default system prompt.
|
|
360
|
+
*/
|
|
361
|
+
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
|
362
|
+
return buildSystemPromptInternal({
|
|
363
|
+
cwd: options.cwd,
|
|
364
|
+
skills: options.skills,
|
|
365
|
+
contextFiles: options.contextFiles,
|
|
366
|
+
appendSystemPrompt: options.appendPrompt,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Settings
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Load settings from agentDir/settings.json merged with cwd/.omp/settings.json.
|
|
374
|
+
*/
|
|
375
|
+
export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|
376
|
+
const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
|
|
377
|
+
return {
|
|
378
|
+
modelRoles: manager.getModelRoles(),
|
|
379
|
+
defaultThinkingLevel: manager.getDefaultThinkingLevel(),
|
|
380
|
+
queueMode: manager.getQueueMode(),
|
|
381
|
+
theme: manager.getTheme(),
|
|
382
|
+
compaction: manager.getCompactionSettings(),
|
|
383
|
+
retry: manager.getRetrySettings(),
|
|
384
|
+
hideThinkingBlock: manager.getHideThinkingBlock(),
|
|
385
|
+
shellPath: manager.getShellPath(),
|
|
386
|
+
collapseChangelog: manager.getCollapseChangelog(),
|
|
387
|
+
hooks: manager.getHookPaths(),
|
|
388
|
+
customTools: manager.getCustomToolPaths(),
|
|
389
|
+
skills: manager.getSkillsSettings(),
|
|
390
|
+
terminal: { showImages: manager.getShowImages() },
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Internal Helpers
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Create a HookFactory from a LoadedHook.
|
|
398
|
+
* This allows mixing discovered hooks with inline hooks.
|
|
399
|
+
*/
|
|
400
|
+
function createFactoryFromLoadedHook(loaded: LoadedHook): HookFactory {
|
|
401
|
+
return (api) => {
|
|
402
|
+
for (const [eventType, handlers] of loaded.handlers) {
|
|
403
|
+
for (const handler of handlers) {
|
|
404
|
+
api.on(eventType as any, handler as any);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Convert hook definitions to LoadedHooks for the HookRunner.
|
|
412
|
+
*/
|
|
413
|
+
function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; factory: HookFactory }>): LoadedHook[] {
|
|
414
|
+
return definitions.map((def) => {
|
|
415
|
+
const handlers = new Map<string, Array<(...args: unknown[]) => Promise<unknown>>>();
|
|
416
|
+
const messageRenderers = new Map<string, any>();
|
|
417
|
+
const commands = new Map<string, any>();
|
|
418
|
+
let sendMessageHandler: (message: any, triggerTurn?: boolean) => void = () => {};
|
|
419
|
+
let appendEntryHandler: (customType: string, data?: any) => void = () => {};
|
|
420
|
+
let newSessionHandler: (options?: any) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
|
421
|
+
let branchHandler: (entryId: string) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
|
422
|
+
let navigateTreeHandler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }> = async () => ({
|
|
423
|
+
cancelled: false,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const api = {
|
|
427
|
+
on: (event: string, handler: (...args: unknown[]) => Promise<unknown>) => {
|
|
428
|
+
const list = handlers.get(event) ?? [];
|
|
429
|
+
list.push(handler);
|
|
430
|
+
handlers.set(event, list);
|
|
431
|
+
},
|
|
432
|
+
sendMessage: (message: any, triggerTurn?: boolean) => {
|
|
433
|
+
sendMessageHandler(message, triggerTurn);
|
|
434
|
+
},
|
|
435
|
+
appendEntry: (customType: string, data?: any) => {
|
|
436
|
+
appendEntryHandler(customType, data);
|
|
437
|
+
},
|
|
438
|
+
registerMessageRenderer: (customType: string, renderer: any) => {
|
|
439
|
+
messageRenderers.set(customType, renderer);
|
|
440
|
+
},
|
|
441
|
+
registerCommand: (name: string, options: any) => {
|
|
442
|
+
commands.set(name, { name, ...options });
|
|
443
|
+
},
|
|
444
|
+
newSession: (options?: any) => newSessionHandler(options),
|
|
445
|
+
branch: (entryId: string) => branchHandler(entryId),
|
|
446
|
+
navigateTree: (targetId: string, options?: any) => navigateTreeHandler(targetId, options),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
def.factory(api as any);
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
path: def.path ?? "<inline>",
|
|
453
|
+
resolvedPath: def.path ?? "<inline>",
|
|
454
|
+
handlers,
|
|
455
|
+
messageRenderers,
|
|
456
|
+
commands,
|
|
457
|
+
setSendMessageHandler: (handler: (message: any, triggerTurn?: boolean) => void) => {
|
|
458
|
+
sendMessageHandler = handler;
|
|
459
|
+
},
|
|
460
|
+
setAppendEntryHandler: (handler: (customType: string, data?: any) => void) => {
|
|
461
|
+
appendEntryHandler = handler;
|
|
462
|
+
},
|
|
463
|
+
setNewSessionHandler: (handler: (options?: any) => Promise<{ cancelled: boolean }>) => {
|
|
464
|
+
newSessionHandler = handler;
|
|
465
|
+
},
|
|
466
|
+
setBranchHandler: (handler: (entryId: string) => Promise<{ cancelled: boolean }>) => {
|
|
467
|
+
branchHandler = handler;
|
|
468
|
+
},
|
|
469
|
+
setNavigateTreeHandler: (handler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }>) => {
|
|
470
|
+
navigateTreeHandler = handler;
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Factory
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Create an AgentSession with the specified options.
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* // Minimal - uses defaults
|
|
484
|
+
* const { session } = await createAgentSession();
|
|
485
|
+
*
|
|
486
|
+
* // With explicit model
|
|
487
|
+
* import { getModel } from '@oh-my-pi/pi-ai';
|
|
488
|
+
* const { session } = await createAgentSession({
|
|
489
|
+
* model: getModel('anthropic', 'claude-opus-4-5'),
|
|
490
|
+
* thinkingLevel: 'high',
|
|
491
|
+
* });
|
|
492
|
+
*
|
|
493
|
+
* // Continue previous session
|
|
494
|
+
* const { session, modelFallbackMessage } = await createAgentSession({
|
|
495
|
+
* continueSession: true,
|
|
496
|
+
* });
|
|
497
|
+
*
|
|
498
|
+
* // Full control
|
|
499
|
+
* const { session } = await createAgentSession({
|
|
500
|
+
* model: myModel,
|
|
501
|
+
* getApiKey: async () => process.env.MY_KEY,
|
|
502
|
+
* systemPrompt: 'You are helpful.',
|
|
503
|
+
* tools: [readTool, bashTool],
|
|
504
|
+
* hooks: [],
|
|
505
|
+
* skills: [],
|
|
506
|
+
* sessionManager: SessionManager.inMemory(),
|
|
507
|
+
* });
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
511
|
+
const cwd = options.cwd ?? process.cwd();
|
|
512
|
+
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
|
513
|
+
|
|
514
|
+
// Use provided or create AuthStorage and ModelRegistry
|
|
515
|
+
const authStorage = options.authStorage ?? discoverAuthStorage(agentDir);
|
|
516
|
+
const modelRegistry = options.modelRegistry ?? discoverModels(authStorage, agentDir);
|
|
517
|
+
time("discoverModels");
|
|
518
|
+
|
|
519
|
+
const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
|
|
520
|
+
time("settingsManager");
|
|
521
|
+
|
|
522
|
+
// Initialize discovery system with settings for provider persistence
|
|
523
|
+
const { initializeWithSettings } = await import("../discovery");
|
|
524
|
+
initializeWithSettings(settingsManager);
|
|
525
|
+
time("initializeWithSettings");
|
|
526
|
+
|
|
527
|
+
const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
|
|
528
|
+
time("sessionManager");
|
|
529
|
+
|
|
530
|
+
// Check if session has existing data to restore
|
|
531
|
+
const existingSession = sessionManager.buildSessionContext();
|
|
532
|
+
time("loadSession");
|
|
533
|
+
const hasExistingSession = existingSession.messages.length > 0;
|
|
534
|
+
|
|
535
|
+
let model = options.model;
|
|
536
|
+
let modelFallbackMessage: string | undefined;
|
|
537
|
+
|
|
538
|
+
// If session has data, try to restore model from it
|
|
539
|
+
const defaultModelStr = existingSession.models.default;
|
|
540
|
+
if (!model && hasExistingSession && defaultModelStr) {
|
|
541
|
+
const slashIdx = defaultModelStr.indexOf("/");
|
|
542
|
+
if (slashIdx > 0) {
|
|
543
|
+
const provider = defaultModelStr.slice(0, slashIdx);
|
|
544
|
+
const modelId = defaultModelStr.slice(slashIdx + 1);
|
|
545
|
+
const restoredModel = modelRegistry.find(provider, modelId);
|
|
546
|
+
if (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {
|
|
547
|
+
model = restoredModel;
|
|
548
|
+
}
|
|
549
|
+
if (!model) {
|
|
550
|
+
modelFallbackMessage = `Could not restore model ${defaultModelStr}`;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// If still no model, try settings default
|
|
556
|
+
if (!model) {
|
|
557
|
+
const settingsDefaultModel = settingsManager.getModelRole("default");
|
|
558
|
+
if (settingsDefaultModel) {
|
|
559
|
+
const slashIdx = settingsDefaultModel.indexOf("/");
|
|
560
|
+
if (slashIdx > 0) {
|
|
561
|
+
const provider = settingsDefaultModel.slice(0, slashIdx);
|
|
562
|
+
const modelId = settingsDefaultModel.slice(slashIdx + 1);
|
|
563
|
+
const settingsModel = modelRegistry.find(provider, modelId);
|
|
564
|
+
if (settingsModel && (await modelRegistry.getApiKey(settingsModel))) {
|
|
565
|
+
model = settingsModel;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Fall back to first available model with a valid API key
|
|
572
|
+
if (!model) {
|
|
573
|
+
for (const m of modelRegistry.getAll()) {
|
|
574
|
+
if (await modelRegistry.getApiKey(m)) {
|
|
575
|
+
model = m;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
time("findAvailableModel");
|
|
580
|
+
if (model) {
|
|
581
|
+
if (modelFallbackMessage) {
|
|
582
|
+
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
// No models available - set message so user knows to /login or configure keys
|
|
586
|
+
modelFallbackMessage = "No models available. Use /login or set an API key environment variable.";
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
let thinkingLevel = options.thinkingLevel;
|
|
591
|
+
|
|
592
|
+
// If session has data, restore thinking level from it
|
|
593
|
+
if (thinkingLevel === undefined && hasExistingSession) {
|
|
594
|
+
thinkingLevel = existingSession.thinkingLevel as ThinkingLevel;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Fall back to settings default
|
|
598
|
+
if (thinkingLevel === undefined) {
|
|
599
|
+
thinkingLevel = settingsManager.getDefaultThinkingLevel() ?? "off";
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Clamp to model capabilities
|
|
603
|
+
if (!model || !model.reasoning) {
|
|
604
|
+
thinkingLevel = "off";
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const skills = options.skills ?? discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings());
|
|
608
|
+
time("discoverSkills");
|
|
609
|
+
|
|
610
|
+
// Discover rules
|
|
611
|
+
const ttsrManager = createTtsrManager(settingsManager.getTtsrSettings());
|
|
612
|
+
const rulesResult = loadCapability<Rule>(ruleCapability.id, { cwd });
|
|
613
|
+
for (const rule of rulesResult.items) {
|
|
614
|
+
if (rule.ttsrTrigger) {
|
|
615
|
+
ttsrManager.addRule(rule);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
time("discoverTtsrRules");
|
|
619
|
+
|
|
620
|
+
// Filter rules for the rulebook (non-TTSR, non-alwaysApply, with descriptions)
|
|
621
|
+
const rulebookRules = filterRulebookRules(rulesResult.items);
|
|
622
|
+
time("filterRulebookRules");
|
|
623
|
+
|
|
624
|
+
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
|
625
|
+
time("discoverContextFiles");
|
|
626
|
+
|
|
627
|
+
// Hook runner - always created (needed for custom command context even without hooks)
|
|
628
|
+
let loadedHooks: LoadedHook[] = [];
|
|
629
|
+
if (options.hooks !== undefined) {
|
|
630
|
+
if (options.hooks.length > 0) {
|
|
631
|
+
loadedHooks = createLoadedHooksFromDefinitions(options.hooks);
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
// Discover hooks, merging with additional paths
|
|
635
|
+
const configuredPaths = [...settingsManager.getHookPaths(), ...(options.additionalHookPaths ?? [])];
|
|
636
|
+
const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd);
|
|
637
|
+
time("discoverAndLoadHooks");
|
|
638
|
+
for (const { path, error } of errors) {
|
|
639
|
+
console.error(`Failed to load hook "${path}": ${error}`);
|
|
640
|
+
}
|
|
641
|
+
loadedHooks = hooks;
|
|
642
|
+
}
|
|
643
|
+
const hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
|
|
644
|
+
|
|
645
|
+
const sessionContext = {
|
|
646
|
+
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
647
|
+
};
|
|
648
|
+
const builtInTools =
|
|
649
|
+
options.tools ??
|
|
650
|
+
createCodingTools(cwd, options.hasUI ?? false, sessionContext, {
|
|
651
|
+
lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
|
|
652
|
+
lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
|
|
653
|
+
lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
|
|
654
|
+
editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
|
|
655
|
+
});
|
|
656
|
+
time("createCodingTools");
|
|
657
|
+
|
|
658
|
+
let customToolsResult: CustomToolsLoadResult;
|
|
659
|
+
if (options.customTools !== undefined) {
|
|
660
|
+
// Use provided custom tools
|
|
661
|
+
const loadedTools: LoadedCustomTool[] = options.customTools.map((ct) => ({
|
|
662
|
+
path: ct.path ?? "<inline>",
|
|
663
|
+
resolvedPath: ct.path ?? "<inline>",
|
|
664
|
+
tool: ct.tool,
|
|
665
|
+
}));
|
|
666
|
+
customToolsResult = {
|
|
667
|
+
tools: loadedTools,
|
|
668
|
+
errors: [],
|
|
669
|
+
setUIContext: () => {},
|
|
670
|
+
};
|
|
671
|
+
} else {
|
|
672
|
+
// Discover custom tools, merging with additional paths
|
|
673
|
+
const configuredPaths = [...settingsManager.getCustomToolPaths(), ...(options.additionalCustomToolPaths ?? [])];
|
|
674
|
+
customToolsResult = await discoverAndLoadCustomTools(configuredPaths, cwd, Object.keys(allTools));
|
|
675
|
+
time("discoverAndLoadCustomTools");
|
|
676
|
+
for (const { path, error } of customToolsResult.errors) {
|
|
677
|
+
console.error(`Failed to load custom tool "${path}": ${error}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Discover MCP tools from .mcp.json files
|
|
682
|
+
let mcpManager: MCPManager | undefined;
|
|
683
|
+
const enableMCP = options.enableMCP ?? true;
|
|
684
|
+
if (enableMCP) {
|
|
685
|
+
const mcpResult = await discoverAndLoadMCPTools(cwd, {
|
|
686
|
+
onConnecting: (serverNames) => {
|
|
687
|
+
if (options.hasUI && serverNames.length > 0) {
|
|
688
|
+
process.stderr.write(chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}...\n`));
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
enableProjectConfig: settingsManager.getMCPProjectConfigEnabled(),
|
|
692
|
+
// Always filter Exa - we have native integration
|
|
693
|
+
filterExa: true,
|
|
694
|
+
});
|
|
695
|
+
time("discoverAndLoadMCPTools");
|
|
696
|
+
mcpManager = mcpResult.manager;
|
|
697
|
+
|
|
698
|
+
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
699
|
+
if (mcpResult.exaApiKeys.length > 0 && !process.env.EXA_API_KEY) {
|
|
700
|
+
process.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Log MCP errors
|
|
704
|
+
for (const { path, error } of mcpResult.errors) {
|
|
705
|
+
console.error(`MCP "${path}": ${error}`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Merge MCP tools into custom tools result
|
|
709
|
+
if (mcpResult.tools.length > 0) {
|
|
710
|
+
customToolsResult = {
|
|
711
|
+
...customToolsResult,
|
|
712
|
+
tools: [...customToolsResult.tools, ...mcpResult.tools],
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Add specialized Exa web search tools if EXA_API_KEY is available
|
|
718
|
+
const exaSettings = settingsManager.getExaSettings();
|
|
719
|
+
if (exaSettings.enabled && exaSettings.enableSearch) {
|
|
720
|
+
const { getWebSearchTools } = await import("./tools/web-search/index.js");
|
|
721
|
+
const exaWebSearchTools = await getWebSearchTools({
|
|
722
|
+
enableLinkedin: exaSettings.enableLinkedin,
|
|
723
|
+
enableCompany: exaSettings.enableCompany,
|
|
724
|
+
});
|
|
725
|
+
// Filter out the base web_search (already in built-in tools), add specialized Exa tools
|
|
726
|
+
const specializedTools = exaWebSearchTools.filter((t) => t.name !== "web_search");
|
|
727
|
+
if (specializedTools.length > 0) {
|
|
728
|
+
const loadedExaTools: LoadedCustomTool[] = specializedTools.map((tool) => ({
|
|
729
|
+
path: "<exa>",
|
|
730
|
+
resolvedPath: "<exa>",
|
|
731
|
+
tool,
|
|
732
|
+
source: { provider: "builtin", providerName: "builtin", level: "user" },
|
|
733
|
+
}));
|
|
734
|
+
customToolsResult = {
|
|
735
|
+
...customToolsResult,
|
|
736
|
+
tools: [...customToolsResult.tools, ...loadedExaTools],
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
time("getWebSearchTools");
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
let agent: Agent;
|
|
743
|
+
let session: AgentSession;
|
|
744
|
+
const getSessionContext = () => ({
|
|
745
|
+
sessionManager,
|
|
746
|
+
modelRegistry,
|
|
747
|
+
model: agent.state.model,
|
|
748
|
+
isIdle: () => !session.isStreaming,
|
|
749
|
+
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
750
|
+
abort: () => {
|
|
751
|
+
session.abort();
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
const toolContextStore = createToolContextStore(getSessionContext);
|
|
755
|
+
const wrappedCustomTools = wrapCustomTools(customToolsResult.tools, getSessionContext);
|
|
756
|
+
const baseSetUIContext = customToolsResult.setUIContext;
|
|
757
|
+
customToolsResult = {
|
|
758
|
+
...customToolsResult,
|
|
759
|
+
setUIContext: (uiContext, hasUI) => {
|
|
760
|
+
toolContextStore.setUIContext(uiContext, hasUI);
|
|
761
|
+
baseSetUIContext(uiContext, hasUI);
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
let allToolsArray: Tool[] = [...builtInTools, ...wrappedCustomTools];
|
|
766
|
+
|
|
767
|
+
// Add rulebook tool if there are rules with descriptions (always enabled, regardless of --tools)
|
|
768
|
+
if (rulebookRules.length > 0) {
|
|
769
|
+
allToolsArray.push(createRulebookTool(rulebookRules));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Filter out hidden tools unless explicitly requested
|
|
773
|
+
if (options.explicitTools) {
|
|
774
|
+
const explicitSet = new Set(options.explicitTools);
|
|
775
|
+
allToolsArray = allToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
|
|
776
|
+
} else {
|
|
777
|
+
allToolsArray = allToolsArray.filter((tool) => !tool.hidden);
|
|
778
|
+
}
|
|
779
|
+
time("combineTools");
|
|
780
|
+
|
|
781
|
+
// Apply bash interception to redirect common shell patterns to proper tools (if enabled)
|
|
782
|
+
if (settingsManager.getBashInterceptorEnabled()) {
|
|
783
|
+
allToolsArray = applyBashInterception(allToolsArray);
|
|
784
|
+
}
|
|
785
|
+
time("applyBashInterception");
|
|
786
|
+
|
|
787
|
+
if (hookRunner) {
|
|
788
|
+
allToolsArray = wrapToolsWithHooks(allToolsArray, hookRunner) as Tool[];
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
let systemPrompt: string;
|
|
792
|
+
const defaultPrompt = buildSystemPromptInternal({
|
|
793
|
+
cwd,
|
|
794
|
+
skills,
|
|
795
|
+
contextFiles,
|
|
796
|
+
rulebookRules,
|
|
797
|
+
});
|
|
798
|
+
time("buildSystemPrompt");
|
|
799
|
+
|
|
800
|
+
if (options.systemPrompt === undefined) {
|
|
801
|
+
systemPrompt = defaultPrompt;
|
|
802
|
+
} else if (typeof options.systemPrompt === "string") {
|
|
803
|
+
systemPrompt = buildSystemPromptInternal({
|
|
804
|
+
cwd,
|
|
805
|
+
skills,
|
|
806
|
+
contextFiles,
|
|
807
|
+
rulebookRules,
|
|
808
|
+
customPrompt: options.systemPrompt,
|
|
809
|
+
});
|
|
810
|
+
} else {
|
|
811
|
+
systemPrompt = options.systemPrompt(defaultPrompt);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const commandsSettings = settingsManager.getCommandsSettings();
|
|
815
|
+
const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd, agentDir, commandsSettings);
|
|
816
|
+
time("discoverSlashCommands");
|
|
817
|
+
|
|
818
|
+
// Discover custom commands (TypeScript slash commands)
|
|
819
|
+
const customCommandsResult = await loadCustomCommandsInternal({ cwd, agentDir });
|
|
820
|
+
time("discoverCustomCommands");
|
|
821
|
+
for (const { path, error } of customCommandsResult.errors) {
|
|
822
|
+
console.error(`Failed to load custom command "${path}": ${error}`);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
agent = new Agent({
|
|
826
|
+
initialState: {
|
|
827
|
+
systemPrompt,
|
|
828
|
+
model,
|
|
829
|
+
thinkingLevel,
|
|
830
|
+
tools: allToolsArray,
|
|
831
|
+
},
|
|
832
|
+
convertToLlm,
|
|
833
|
+
transformContext: hookRunner
|
|
834
|
+
? async (messages) => {
|
|
835
|
+
return hookRunner.emitContext(messages);
|
|
836
|
+
}
|
|
837
|
+
: undefined,
|
|
838
|
+
queueMode: settingsManager.getQueueMode(),
|
|
839
|
+
interruptMode: settingsManager.getInterruptMode(),
|
|
840
|
+
getToolContext: toolContextStore.getContext,
|
|
841
|
+
getApiKey: async () => {
|
|
842
|
+
const currentModel = agent.state.model;
|
|
843
|
+
if (!currentModel) {
|
|
844
|
+
throw new Error("No model selected");
|
|
845
|
+
}
|
|
846
|
+
const key = await modelRegistry.getApiKey(currentModel);
|
|
847
|
+
if (!key) {
|
|
848
|
+
throw new Error(`No API key found for provider "${currentModel.provider}"`);
|
|
849
|
+
}
|
|
850
|
+
return key;
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
time("createAgent");
|
|
854
|
+
|
|
855
|
+
// Restore messages if session has existing data
|
|
856
|
+
if (hasExistingSession) {
|
|
857
|
+
agent.replaceMessages(existingSession.messages);
|
|
858
|
+
} else {
|
|
859
|
+
// Save initial model and thinking level for new sessions so they can be restored on resume
|
|
860
|
+
if (model) {
|
|
861
|
+
sessionManager.appendModelChange(`${model.provider}/${model.id}`);
|
|
862
|
+
}
|
|
863
|
+
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
session = new AgentSession({
|
|
867
|
+
agent,
|
|
868
|
+
sessionManager,
|
|
869
|
+
settingsManager,
|
|
870
|
+
scopedModels: options.scopedModels,
|
|
871
|
+
fileCommands: slashCommands,
|
|
872
|
+
hookRunner,
|
|
873
|
+
customTools: customToolsResult.tools,
|
|
874
|
+
customCommands: customCommandsResult.commands,
|
|
875
|
+
skillsSettings: settingsManager.getSkillsSettings(),
|
|
876
|
+
modelRegistry,
|
|
877
|
+
ttsrManager,
|
|
878
|
+
});
|
|
879
|
+
time("createAgentSession");
|
|
880
|
+
|
|
881
|
+
// Warm up LSP servers (connects to detected servers)
|
|
882
|
+
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
883
|
+
if (settingsManager.getLspDiagnosticsOnWrite()) {
|
|
884
|
+
try {
|
|
885
|
+
const result = await warmupLspServers(cwd);
|
|
886
|
+
lspServers = result.servers;
|
|
887
|
+
time("warmupLspServers");
|
|
888
|
+
} catch {
|
|
889
|
+
// Ignore warmup errors
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
session,
|
|
895
|
+
customToolsResult,
|
|
896
|
+
mcpManager,
|
|
897
|
+
modelFallbackMessage,
|
|
898
|
+
lspServers,
|
|
899
|
+
};
|
|
900
|
+
}
|