@bubblebrain-ai/bubble 0.0.1
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/README.md +70 -0
- package/dist/agent/evidence-tracker.d.ts +15 -0
- package/dist/agent/evidence-tracker.js +93 -0
- package/dist/agent/execution-governor.d.ts +30 -0
- package/dist/agent/execution-governor.js +169 -0
- package/dist/agent/subtask-policy.d.ts +14 -0
- package/dist/agent/subtask-policy.js +60 -0
- package/dist/agent/task-classifier.d.ts +3 -0
- package/dist/agent/task-classifier.js +36 -0
- package/dist/agent/tool-arbiter.d.ts +7 -0
- package/dist/agent/tool-arbiter.js +33 -0
- package/dist/agent/tool-intent.d.ts +20 -0
- package/dist/agent/tool-intent.js +176 -0
- package/dist/agent.d.ts +95 -0
- package/dist/agent.js +672 -0
- package/dist/approval/controller.d.ts +48 -0
- package/dist/approval/controller.js +78 -0
- package/dist/approval/danger.d.ts +13 -0
- package/dist/approval/danger.js +55 -0
- package/dist/approval/diff-hunks.d.ts +12 -0
- package/dist/approval/diff-hunks.js +32 -0
- package/dist/approval/session-cache.d.ts +35 -0
- package/dist/approval/session-cache.js +68 -0
- package/dist/approval/tool-helper.d.ts +14 -0
- package/dist/approval/tool-helper.js +32 -0
- package/dist/approval/types.d.ts +56 -0
- package/dist/approval/types.js +8 -0
- package/dist/bubble-home.d.ts +8 -0
- package/dist/bubble-home.js +19 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +82 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +144 -0
- package/dist/context/budget.d.ts +21 -0
- package/dist/context/budget.js +72 -0
- package/dist/context/compact-llm.d.ts +16 -0
- package/dist/context/compact-llm.js +132 -0
- package/dist/context/compact.d.ts +15 -0
- package/dist/context/compact.js +251 -0
- package/dist/context/overflow.d.ts +9 -0
- package/dist/context/overflow.js +46 -0
- package/dist/context/projector.d.ts +26 -0
- package/dist/context/projector.js +150 -0
- package/dist/context/prune.d.ts +9 -0
- package/dist/context/prune.js +111 -0
- package/dist/lsp/config.d.ts +18 -0
- package/dist/lsp/config.js +58 -0
- package/dist/lsp/diagnostics.d.ts +24 -0
- package/dist/lsp/diagnostics.js +103 -0
- package/dist/lsp/index.d.ts +3 -0
- package/dist/lsp/index.js +3 -0
- package/dist/lsp/service.d.ts +85 -0
- package/dist/lsp/service.js +695 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +352 -0
- package/dist/mcp/client.d.ts +68 -0
- package/dist/mcp/client.js +163 -0
- package/dist/mcp/config.d.ts +26 -0
- package/dist/mcp/config.js +127 -0
- package/dist/mcp/manager.d.ts +55 -0
- package/dist/mcp/manager.js +296 -0
- package/dist/mcp/name.d.ts +26 -0
- package/dist/mcp/name.js +40 -0
- package/dist/mcp/transports.d.ts +53 -0
- package/dist/mcp/transports.js +248 -0
- package/dist/mcp/types.d.ts +111 -0
- package/dist/mcp/types.js +14 -0
- package/dist/memory/db.d.ts +62 -0
- package/dist/memory/db.js +313 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/paths.d.ts +18 -0
- package/dist/memory/paths.js +38 -0
- package/dist/memory/phase1.d.ts +23 -0
- package/dist/memory/phase1.js +172 -0
- package/dist/memory/phase2.d.ts +19 -0
- package/dist/memory/phase2.js +100 -0
- package/dist/memory/prompts.d.ts +19 -0
- package/dist/memory/prompts.js +99 -0
- package/dist/memory/reset.d.ts +1 -0
- package/dist/memory/reset.js +13 -0
- package/dist/memory/start.d.ts +24 -0
- package/dist/memory/start.js +50 -0
- package/dist/memory/storage.d.ts +10 -0
- package/dist/memory/storage.js +82 -0
- package/dist/memory/store.d.ts +43 -0
- package/dist/memory/store.js +193 -0
- package/dist/memory/usage.d.ts +1 -0
- package/dist/memory/usage.js +38 -0
- package/dist/model-catalog.d.ts +20 -0
- package/dist/model-catalog.js +99 -0
- package/dist/model-config.d.ts +32 -0
- package/dist/model-config.js +59 -0
- package/dist/model-pricing.d.ts +23 -0
- package/dist/model-pricing.js +46 -0
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/openai-codex.d.ts +9 -0
- package/dist/oauth/openai-codex.js +173 -0
- package/dist/oauth/storage.d.ts +18 -0
- package/dist/oauth/storage.js +60 -0
- package/dist/oauth/types.d.ts +15 -0
- package/dist/oauth/types.js +1 -0
- package/dist/orchestrator/default-hooks.d.ts +2 -0
- package/dist/orchestrator/default-hooks.js +96 -0
- package/dist/orchestrator/hooks.d.ts +78 -0
- package/dist/orchestrator/hooks.js +52 -0
- package/dist/orchestrator/workflow.d.ts +10 -0
- package/dist/orchestrator/workflow.js +22 -0
- package/dist/permission/mode.d.ts +23 -0
- package/dist/permission/mode.js +20 -0
- package/dist/permissions/rule.d.ts +39 -0
- package/dist/permissions/rule.js +234 -0
- package/dist/permissions/settings.d.ts +71 -0
- package/dist/permissions/settings.js +202 -0
- package/dist/permissions/types.d.ts +61 -0
- package/dist/permissions/types.js +14 -0
- package/dist/prompt/compose.d.ts +12 -0
- package/dist/prompt/compose.js +67 -0
- package/dist/prompt/environment.d.ts +12 -0
- package/dist/prompt/environment.js +38 -0
- package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
- package/dist/prompt/provider-prompts/anthropic.js +5 -0
- package/dist/prompt/provider-prompts/codex.d.ts +1 -0
- package/dist/prompt/provider-prompts/codex.js +5 -0
- package/dist/prompt/provider-prompts/default.d.ts +1 -0
- package/dist/prompt/provider-prompts/default.js +6 -0
- package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
- package/dist/prompt/provider-prompts/gemini.js +5 -0
- package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
- package/dist/prompt/provider-prompts/gpt.js +5 -0
- package/dist/prompt/reminders.d.ts +30 -0
- package/dist/prompt/reminders.js +164 -0
- package/dist/prompt/runtime.d.ts +12 -0
- package/dist/prompt/runtime.js +31 -0
- package/dist/prompt/skills.d.ts +2 -0
- package/dist/prompt/skills.js +4 -0
- package/dist/provider-openai-codex.d.ts +14 -0
- package/dist/provider-openai-codex.js +409 -0
- package/dist/provider-registry.d.ts +56 -0
- package/dist/provider-registry.js +244 -0
- package/dist/provider-transform.d.ts +10 -0
- package/dist/provider-transform.js +69 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.js +269 -0
- package/dist/question/controller.d.ts +22 -0
- package/dist/question/controller.js +97 -0
- package/dist/question/index.d.ts +2 -0
- package/dist/question/index.js +2 -0
- package/dist/question/types.d.ts +42 -0
- package/dist/question/types.js +6 -0
- package/dist/session-log.d.ts +16 -0
- package/dist/session-log.js +267 -0
- package/dist/session-types.d.ts +55 -0
- package/dist/session-types.js +1 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +135 -0
- package/dist/skills/discovery.d.ts +12 -0
- package/dist/skills/discovery.js +148 -0
- package/dist/skills/format.d.ts +2 -0
- package/dist/skills/format.js +47 -0
- package/dist/skills/frontmatter.d.ts +5 -0
- package/dist/skills/frontmatter.js +60 -0
- package/dist/skills/invocation.d.ts +8 -0
- package/dist/skills/invocation.js +51 -0
- package/dist/skills/registry.d.ts +17 -0
- package/dist/skills/registry.js +42 -0
- package/dist/skills/types.d.ts +32 -0
- package/dist/skills/types.js +1 -0
- package/dist/slash-commands/commands.d.ts +7 -0
- package/dist/slash-commands/commands.js +779 -0
- package/dist/slash-commands/index.d.ts +4 -0
- package/dist/slash-commands/index.js +8 -0
- package/dist/slash-commands/registry.d.ts +31 -0
- package/dist/slash-commands/registry.js +70 -0
- package/dist/slash-commands/types.d.ts +44 -0
- package/dist/slash-commands/types.js +1 -0
- package/dist/slash-commands/unified.d.ts +38 -0
- package/dist/slash-commands/unified.js +38 -0
- package/dist/system-prompt.d.ts +34 -0
- package/dist/system-prompt.js +7 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.js +135 -0
- package/dist/tools/edit.d.ts +16 -0
- package/dist/tools/edit.js +95 -0
- package/dist/tools/exa-mcp.d.ts +3 -0
- package/dist/tools/exa-mcp.js +74 -0
- package/dist/tools/exit-plan-mode.d.ts +17 -0
- package/dist/tools/exit-plan-mode.js +68 -0
- package/dist/tools/glob.d.ts +5 -0
- package/dist/tools/glob.js +129 -0
- package/dist/tools/grep.d.ts +5 -0
- package/dist/tools/grep.js +111 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/lsp.d.ts +4 -0
- package/dist/tools/lsp.js +92 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +90 -0
- package/dist/tools/question.d.ts +3 -0
- package/dist/tools/question.js +174 -0
- package/dist/tools/read.d.ts +7 -0
- package/dist/tools/read.js +83 -0
- package/dist/tools/sensitive-paths.d.ts +3 -0
- package/dist/tools/sensitive-paths.js +24 -0
- package/dist/tools/skill.d.ts +5 -0
- package/dist/tools/skill.js +51 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +57 -0
- package/dist/tools/todo.d.ts +12 -0
- package/dist/tools/todo.js +151 -0
- package/dist/tools/tool-search.d.ts +23 -0
- package/dist/tools/tool-search.js +124 -0
- package/dist/tools/web-fetch.d.ts +6 -0
- package/dist/tools/web-fetch.js +75 -0
- package/dist/tools/web-search.d.ts +5 -0
- package/dist/tools/web-search.js +49 -0
- package/dist/tools/write.d.ts +11 -0
- package/dist/tools/write.js +77 -0
- package/dist/tui/display-history.d.ts +35 -0
- package/dist/tui/display-history.js +243 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/image-paste.d.ts +54 -0
- package/dist/tui/image-paste.js +288 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/opencode-spinner.d.ts +21 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +41 -0
- package/dist/tui/prompt-keybindings.js +28 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/run.d.ts +39 -0
- package/dist/tui/run.js +5696 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.js +4 -0
- package/dist/variant/thinking-level.d.ts +5 -0
- package/dist/variant/thinking-level.js +25 -0
- package/dist/variant/variant-resolver.d.ts +4 -0
- package/dist/variant/variant-resolver.js +12 -0
- package/package.json +47 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Main entry point - assembles all layers and runs the agent.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Agent } from "./agent.js";
|
|
7
|
+
import { parseArgs, printHelp } from "./cli.js";
|
|
8
|
+
import { UserConfig } from "./config.js";
|
|
9
|
+
import { createProviderInstance, createUnavailableProvider } from "./provider.js";
|
|
10
|
+
import { getDefaultThinkingLevel } from "./provider-transform.js";
|
|
11
|
+
import { ProviderRegistry, displayModel, encodeModel, decodeModel } from "./provider-registry.js";
|
|
12
|
+
import { SessionManager } from "./session.js";
|
|
13
|
+
import { buildSystemPrompt } from "./system-prompt.js";
|
|
14
|
+
import { SkillRegistry } from "./skills/registry.js";
|
|
15
|
+
import { createAllTools } from "./tools/index.js";
|
|
16
|
+
import { PermissionAwareApprovalController } from "./approval/controller.js";
|
|
17
|
+
import { BashAllowlist } from "./approval/session-cache.js";
|
|
18
|
+
import { SettingsManager } from "./permissions/settings.js";
|
|
19
|
+
import { getLspService } from "./lsp/index.js";
|
|
20
|
+
import { loadMcpConfig } from "./mcp/config.js";
|
|
21
|
+
import { McpManager } from "./mcp/manager.js";
|
|
22
|
+
import { QuestionController } from "./question/index.js";
|
|
23
|
+
import { buildMemoryPrompt, formatMemoryStartupResult, recordMemoryCitations, runMemoryPhase2, runMemoryStartupPipeline, startMemoryStartupTask, } from "./memory/index.js";
|
|
24
|
+
async function main() {
|
|
25
|
+
const args = parseArgs(process.argv.slice(2));
|
|
26
|
+
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
27
|
+
printHelp();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
const userConfig = new UserConfig();
|
|
31
|
+
const registry = new ProviderRegistry(userConfig);
|
|
32
|
+
const skillRegistry = new SkillRegistry({
|
|
33
|
+
cwd: args.cwd,
|
|
34
|
+
skillPaths: userConfig.getSkillPaths(),
|
|
35
|
+
});
|
|
36
|
+
const printMode = args.print || !!args.prompt;
|
|
37
|
+
// Resolve configured providers only; do not auto-inject OpenRouter as a startup default.
|
|
38
|
+
const providers = registry.getConfigured();
|
|
39
|
+
if (providers.length === 0) {
|
|
40
|
+
if (printMode) {
|
|
41
|
+
console.error(chalk.red("Error: No provider configured. Start interactive mode and use /login or /provider --add <id>."));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
console.log(chalk.dim("No provider configured yet. Start with /login for ChatGPT or /provider --add <id> for an API key."));
|
|
45
|
+
}
|
|
46
|
+
const defaultProvider = registry.getDefault();
|
|
47
|
+
const unavailableProviderMessage = "No provider configured. Use /login for ChatGPT or /provider --add <id> before sending a prompt.";
|
|
48
|
+
const provider = defaultProvider
|
|
49
|
+
? createProviderInstance({
|
|
50
|
+
providerId: defaultProvider.id,
|
|
51
|
+
apiKey: defaultProvider.apiKey,
|
|
52
|
+
baseURL: defaultProvider.baseURL,
|
|
53
|
+
thinkingLevel: args.thinkingLevel,
|
|
54
|
+
})
|
|
55
|
+
: createUnavailableProvider(unavailableProviderMessage);
|
|
56
|
+
const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({ providerId, apiKey, baseURL, thinkingLevel: args.thinkingLevel });
|
|
57
|
+
let agentRef;
|
|
58
|
+
const todoStore = {
|
|
59
|
+
getTodos: () => agentRef?.getTodos() ?? [],
|
|
60
|
+
setTodos: (todos) => agentRef?.setTodos(todos),
|
|
61
|
+
};
|
|
62
|
+
const planHandlerRef = {};
|
|
63
|
+
const planController = {
|
|
64
|
+
getMode: () => agentRef?.mode ?? "default",
|
|
65
|
+
requestApproval: (plan) => planHandlerRef.current
|
|
66
|
+
? planHandlerRef.current(plan)
|
|
67
|
+
: Promise.resolve({
|
|
68
|
+
action: "reject",
|
|
69
|
+
reason: "No interactive UI available to approve the plan.",
|
|
70
|
+
}),
|
|
71
|
+
setMode: (mode) => {
|
|
72
|
+
agentRef?.setMode(mode);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const approvalHandlerRef = {};
|
|
76
|
+
const questionController = new QuestionController();
|
|
77
|
+
const bashAllowlist = new BashAllowlist();
|
|
78
|
+
const settingsManager = new SettingsManager(args.cwd);
|
|
79
|
+
for (const d of settingsManager.getMerged().diagnostics) {
|
|
80
|
+
console.error(chalk.yellow(`[settings:${d.scope}] ${d.path}: ${d.message}`));
|
|
81
|
+
}
|
|
82
|
+
const approvalController = new PermissionAwareApprovalController({
|
|
83
|
+
getMode: () => agentRef?.mode ?? "default",
|
|
84
|
+
handlerRef: approvalHandlerRef,
|
|
85
|
+
bashAllowlist,
|
|
86
|
+
cwd: args.cwd,
|
|
87
|
+
getRuleSet: () => settingsManager.getMerged().ruleSet,
|
|
88
|
+
});
|
|
89
|
+
const toolSearchController = {
|
|
90
|
+
listDeferred: () => agentRef?.listDeferredTools() ?? [],
|
|
91
|
+
unlock: (names) => agentRef?.unlockDeferredTools(names),
|
|
92
|
+
};
|
|
93
|
+
const lspService = getLspService(args.cwd, settingsManager.getMerged().lsp);
|
|
94
|
+
const tools = createAllTools(args.cwd, skillRegistry, {
|
|
95
|
+
todoStore,
|
|
96
|
+
planController,
|
|
97
|
+
approvalController,
|
|
98
|
+
questionController: printMode ? undefined : questionController,
|
|
99
|
+
toolSearchController,
|
|
100
|
+
lspService,
|
|
101
|
+
});
|
|
102
|
+
// Bring up MCP servers (if any). Failures are captured per-server and never
|
|
103
|
+
// block the rest of startup; /mcp surfaces status at runtime.
|
|
104
|
+
const mcpLoaded = loadMcpConfig({ cwd: args.cwd });
|
|
105
|
+
for (const d of mcpLoaded.diagnostics) {
|
|
106
|
+
console.error(chalk.yellow(`[mcp:${d.scope}] ${d.path}: ${d.message}`));
|
|
107
|
+
}
|
|
108
|
+
const mcpManager = new McpManager({ servers: mcpLoaded.servers });
|
|
109
|
+
if (mcpLoaded.servers.length > 0) {
|
|
110
|
+
await mcpManager.start();
|
|
111
|
+
// Only surface failures at startup. Successful connections would push the
|
|
112
|
+
// welcome screen above the visible area on small terminals. /mcp shows the
|
|
113
|
+
// full status.
|
|
114
|
+
for (const state of mcpManager.getStates()) {
|
|
115
|
+
if (state.status.kind === "failed") {
|
|
116
|
+
console.error(chalk.yellow(`[mcp] ${state.name}: failed — ${state.status.error}`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
tools.push(...mcpManager.getToolEntries());
|
|
120
|
+
}
|
|
121
|
+
// Expose MCP prompts as slash commands. Queried live at each lookup so
|
|
122
|
+
// /mcp reconnect picks up new prompts without restarting the process.
|
|
123
|
+
{
|
|
124
|
+
const { registry: slashRegistry } = await import("./slash-commands/index.js");
|
|
125
|
+
slashRegistry.addDynamicSource(() => mcpManager.getPromptCommands());
|
|
126
|
+
}
|
|
127
|
+
// Signal-based shutdown for Ctrl-C / kill. For /quit the command handler
|
|
128
|
+
// shuts MCP down directly — process.once("exit", ...)
|
|
129
|
+
// runs synchronously and can't await async work, so relying on it is a trap.
|
|
130
|
+
const shutdownMcp = async () => {
|
|
131
|
+
try {
|
|
132
|
+
await mcpManager.shutdown();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// ignore — we're exiting anyway
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
process.once("SIGINT", () => { void shutdownMcp().then(() => process.exit(130)); });
|
|
139
|
+
process.once("SIGTERM", () => { void shutdownMcp().then(() => process.exit(143)); });
|
|
140
|
+
// Session management:
|
|
141
|
+
// - default: always start a fresh session
|
|
142
|
+
// - --resume: explicitly restore the latest or a named session
|
|
143
|
+
let sessionManager = args.resume
|
|
144
|
+
? SessionManager.resume(args.cwd, args.sessionName)
|
|
145
|
+
: undefined;
|
|
146
|
+
let resumedExistingSession = !!sessionManager;
|
|
147
|
+
if (!sessionManager) {
|
|
148
|
+
sessionManager = args.sessionName && !args.resume
|
|
149
|
+
? SessionManager.create(args.cwd, args.sessionName)
|
|
150
|
+
: SessionManager.createFresh(args.cwd);
|
|
151
|
+
resumedExistingSession = false;
|
|
152
|
+
}
|
|
153
|
+
// Model resolution:
|
|
154
|
+
// 1. Session metadata 2. User-configured default model 3. CLI flag
|
|
155
|
+
// No implicit built-in model fallback.
|
|
156
|
+
const fallbackProviderId = defaultProvider?.id || "";
|
|
157
|
+
const sessionModel = sessionManager?.getMetadata().model;
|
|
158
|
+
const configuredModel = sessionModel ?? userConfig.getDefaultModel() ?? args.model;
|
|
159
|
+
const sessionThinkingLevel = sessionManager?.getMetadata().thinkingLevel;
|
|
160
|
+
const configuredThinkingLevel = userConfig.getDefaultThinkingLevel();
|
|
161
|
+
const normalizedConfiguredModel = configuredModel
|
|
162
|
+
? (configuredModel.includes(":")
|
|
163
|
+
? configuredModel
|
|
164
|
+
: (fallbackProviderId ? encodeModel(fallbackProviderId, configuredModel) : ""))
|
|
165
|
+
: "";
|
|
166
|
+
const { providerId: effectiveProviderId, modelId: effectiveModelId } = normalizedConfiguredModel
|
|
167
|
+
? decodeModel(normalizedConfiguredModel)
|
|
168
|
+
: { providerId: undefined, modelId: "" };
|
|
169
|
+
let activeProviderId = effectiveProviderId || fallbackProviderId;
|
|
170
|
+
if (registry.supportsOAuth(activeProviderId) && registry.getAuthStorage().has(activeProviderId)) {
|
|
171
|
+
await registry.prepareProvider(activeProviderId);
|
|
172
|
+
}
|
|
173
|
+
const activeProvider = registry.getConfigured().find((p) => p.id === activeProviderId) || defaultProvider;
|
|
174
|
+
const activeModel = activeProvider && effectiveModelId
|
|
175
|
+
? encodeModel(activeProviderId, effectiveModelId)
|
|
176
|
+
: "";
|
|
177
|
+
if (!activeModel && !activeProvider) {
|
|
178
|
+
activeProviderId = "";
|
|
179
|
+
}
|
|
180
|
+
const initialThinkingLevel = activeModel
|
|
181
|
+
? (sessionThinkingLevel
|
|
182
|
+
?? args.thinkingLevel
|
|
183
|
+
?? configuredThinkingLevel
|
|
184
|
+
?? getDefaultThinkingLevel(activeProviderId, effectiveModelId))
|
|
185
|
+
: (sessionThinkingLevel ?? args.thinkingLevel ?? configuredThinkingLevel ?? "off");
|
|
186
|
+
const restoredTodos = sessionManager?.getTodos() ?? [];
|
|
187
|
+
const initialMode = args.mode ?? "default";
|
|
188
|
+
const systemPrompt = buildSystemPrompt({
|
|
189
|
+
agentName: "Bubble",
|
|
190
|
+
configuredProvider: activeProviderId || "none",
|
|
191
|
+
configuredModel: activeModel ? displayModel(activeModel) : "none",
|
|
192
|
+
configuredModelId: activeModel || "none",
|
|
193
|
+
thinkingLevel: initialThinkingLevel,
|
|
194
|
+
mode: initialMode,
|
|
195
|
+
workingDir: args.cwd,
|
|
196
|
+
tools: tools.map((tool) => tool.name),
|
|
197
|
+
skills: skillRegistry.summaries(),
|
|
198
|
+
memoryPrompt: buildMemoryPrompt(args.cwd),
|
|
199
|
+
});
|
|
200
|
+
const agent = new Agent({
|
|
201
|
+
provider: activeProvider
|
|
202
|
+
? createProvider(activeProviderId, activeProvider.apiKey, activeProvider.baseURL)
|
|
203
|
+
: provider,
|
|
204
|
+
providerId: activeProviderId || "",
|
|
205
|
+
model: activeModel,
|
|
206
|
+
sessionID: sessionManager?.getSessionFile(),
|
|
207
|
+
tools,
|
|
208
|
+
systemPrompt,
|
|
209
|
+
temperature: 0.2,
|
|
210
|
+
thinkingLevel: initialThinkingLevel,
|
|
211
|
+
mode: initialMode,
|
|
212
|
+
todos: restoredTodos,
|
|
213
|
+
onMessageAppend: (message) => {
|
|
214
|
+
if (!sessionManager)
|
|
215
|
+
return;
|
|
216
|
+
if (message.role === "system")
|
|
217
|
+
return;
|
|
218
|
+
// <system-reminder> injections are runtime/ephemeral; don't persist them —
|
|
219
|
+
// they will be re-injected as needed on resume based on the current mode.
|
|
220
|
+
if (message.role === "user" && message.isMeta)
|
|
221
|
+
return;
|
|
222
|
+
sessionManager.appendMessage(message);
|
|
223
|
+
if (message.role === "assistant") {
|
|
224
|
+
recordMemoryCitations(args.cwd, message.content);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
onToolResult: (toolName, result) => {
|
|
228
|
+
if (!sessionManager)
|
|
229
|
+
return;
|
|
230
|
+
if (toolName !== "skill" || result.isError)
|
|
231
|
+
return;
|
|
232
|
+
const match = result.content.match(/^Skill:\s+([^\n]+)$/m);
|
|
233
|
+
if (match?.[1]) {
|
|
234
|
+
sessionManager.appendMarker("skill_activated", match[1].trim());
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
onTodosUpdate: (todos) => {
|
|
238
|
+
sessionManager?.appendTodosSnapshot(todos);
|
|
239
|
+
},
|
|
240
|
+
onModeUpdate: (mode) => {
|
|
241
|
+
sessionManager?.appendMarker("mode_switch", mode);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
agentRef = agent;
|
|
245
|
+
if (sessionManager) {
|
|
246
|
+
sessionManager.setMetadata({
|
|
247
|
+
...(agent.model ? { model: agent.model } : {}),
|
|
248
|
+
cwd: args.cwd,
|
|
249
|
+
thinkingLevel: agent.thinking,
|
|
250
|
+
reasoningEffort: agent.thinking,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const flushMemory = async () => {
|
|
254
|
+
// Codex-style memory runs at startup over historical rollouts. Exit should
|
|
255
|
+
// not perform an ad-hoc extraction of the just-finished session.
|
|
256
|
+
};
|
|
257
|
+
const runMemoryCompaction = async () => formatMemoryStartupResult(await runMemoryStartupPipeline({
|
|
258
|
+
cwd: args.cwd,
|
|
259
|
+
complete: (messages, completeOptions) => agent.complete(messages, completeOptions),
|
|
260
|
+
model: agent.apiModel,
|
|
261
|
+
}));
|
|
262
|
+
const runMemorySummary = async () => {
|
|
263
|
+
const result = await runMemoryPhase2({
|
|
264
|
+
cwd: args.cwd,
|
|
265
|
+
complete: (messages, completeOptions) => agent.complete(messages, completeOptions),
|
|
266
|
+
model: agent.apiModel,
|
|
267
|
+
});
|
|
268
|
+
return `Memory Phase 2 ${result.status}: selected ${result.selected}${result.reason ? ` (${result.reason})` : ""}.`;
|
|
269
|
+
};
|
|
270
|
+
const runMemoryRefresh = runMemoryCompaction;
|
|
271
|
+
startMemoryStartupTask({
|
|
272
|
+
cwd: args.cwd,
|
|
273
|
+
complete: (messages, completeOptions) => agent.complete(messages, completeOptions),
|
|
274
|
+
model: agent.apiModel,
|
|
275
|
+
});
|
|
276
|
+
if (activeModel && args.model && normalizedConfiguredModel === agent.model) {
|
|
277
|
+
userConfig.pushRecentModel(agent.model);
|
|
278
|
+
}
|
|
279
|
+
// Restore session if requested
|
|
280
|
+
if (resumedExistingSession && sessionManager) {
|
|
281
|
+
const history = sessionManager.getMessages();
|
|
282
|
+
if (history.length > 0) {
|
|
283
|
+
agent.messages = [{ role: "system", content: systemPrompt }, ...history];
|
|
284
|
+
// Reassigning agent.messages drops any <system-reminder> we injected during
|
|
285
|
+
// construction. Re-inject if the agent is starting in plan mode.
|
|
286
|
+
if (agent.mode === "plan") {
|
|
287
|
+
const { PLAN_MODE_ENTER_REMINDER } = await import("./prompt/reminders.js");
|
|
288
|
+
agent.injectSystemReminder(PLAN_MODE_ENTER_REMINDER);
|
|
289
|
+
}
|
|
290
|
+
console.log(chalk.dim(`Resumed session: ${sessionManager.getSessionFile()}`));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Print mode: single prompt, then exit
|
|
294
|
+
if (args.print || args.prompt) {
|
|
295
|
+
const prompt = args.prompt || (await readPipedStdin()) || "";
|
|
296
|
+
if (!prompt) {
|
|
297
|
+
console.error(chalk.red("Error: No prompt provided."));
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
for await (const event of agent.run(prompt, args.cwd)) {
|
|
301
|
+
if (event.type === "text_delta") {
|
|
302
|
+
process.stdout.write(event.content);
|
|
303
|
+
}
|
|
304
|
+
else if (event.type === "tool_start") {
|
|
305
|
+
console.log(chalk.cyan(`\n[Tool: ${event.name}]`));
|
|
306
|
+
}
|
|
307
|
+
else if (event.type === "tool_end") {
|
|
308
|
+
const color = event.result.isError ? chalk.red : chalk.dim;
|
|
309
|
+
console.log(color(`[Result: ${event.result.content.slice(0, 200)}${event.result.content.length > 200 ? "..." : ""}]`));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
console.log();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
// Interactive mode: OpenTUI uses Bun native FFI, matching opencode's TUI stack.
|
|
316
|
+
const { runTui } = await import("./tui/run.js");
|
|
317
|
+
await runTui(agent, args, {
|
|
318
|
+
sessionManager,
|
|
319
|
+
createProvider,
|
|
320
|
+
registry,
|
|
321
|
+
skillRegistry,
|
|
322
|
+
planHandlerRef,
|
|
323
|
+
approvalHandlerRef,
|
|
324
|
+
questionController,
|
|
325
|
+
bashAllowlist,
|
|
326
|
+
settingsManager,
|
|
327
|
+
lspService,
|
|
328
|
+
mcpManager,
|
|
329
|
+
bypassEnabled: args.bypassEnabled,
|
|
330
|
+
theme: userConfig.getTheme(),
|
|
331
|
+
flushMemory,
|
|
332
|
+
runMemoryCompaction,
|
|
333
|
+
runMemorySummary,
|
|
334
|
+
runMemoryRefresh,
|
|
335
|
+
});
|
|
336
|
+
await flushMemory();
|
|
337
|
+
}
|
|
338
|
+
async function readPipedStdin() {
|
|
339
|
+
if (process.stdin.isTTY)
|
|
340
|
+
return undefined;
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
let data = "";
|
|
343
|
+
process.stdin.setEncoding("utf8");
|
|
344
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
345
|
+
process.stdin.on("end", () => resolve(data.trim() || undefined));
|
|
346
|
+
process.stdin.resume();
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
main().catch((err) => {
|
|
350
|
+
console.error(chalk.red(`Fatal error: ${err.message}`));
|
|
351
|
+
process.exit(1);
|
|
352
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClient — protocol layer over a transport.
|
|
3
|
+
*
|
|
4
|
+
* Handles JSON-RPC id correlation, the `initialize` handshake, and typed
|
|
5
|
+
* wrappers for `tools/list` / `tools/call`. Enough surface for the tool
|
|
6
|
+
* registry to plug MCP tools in; more methods can be added the same way.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpPromptInfo, McpToolInfo, McpTransport } from "./types.js";
|
|
9
|
+
export interface ClientInfo {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ToolCallContent {
|
|
14
|
+
type: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
data?: string;
|
|
17
|
+
mimeType?: string;
|
|
18
|
+
[extra: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
export interface ToolCallResult {
|
|
21
|
+
content: ToolCallContent[];
|
|
22
|
+
isError?: boolean;
|
|
23
|
+
structuredContent?: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** One block from a prompt message. MCP content types mirror tools. */
|
|
26
|
+
export interface PromptContentBlock {
|
|
27
|
+
type: string;
|
|
28
|
+
text?: string;
|
|
29
|
+
data?: string;
|
|
30
|
+
mimeType?: string;
|
|
31
|
+
[extra: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
export interface PromptMessage {
|
|
34
|
+
role: "user" | "assistant";
|
|
35
|
+
/** Per MCP spec `content` is a single block; some servers return an array. */
|
|
36
|
+
content: PromptContentBlock | PromptContentBlock[];
|
|
37
|
+
}
|
|
38
|
+
export interface PromptResult {
|
|
39
|
+
description?: string;
|
|
40
|
+
messages: PromptMessage[];
|
|
41
|
+
}
|
|
42
|
+
export declare class MCPClient {
|
|
43
|
+
private readonly transport;
|
|
44
|
+
private readonly clientInfo;
|
|
45
|
+
private readonly defaultTimeoutMs;
|
|
46
|
+
private nextId;
|
|
47
|
+
private pending;
|
|
48
|
+
private closed;
|
|
49
|
+
private _serverInfo?;
|
|
50
|
+
private _capabilities?;
|
|
51
|
+
private _instructions?;
|
|
52
|
+
constructor(transport: McpTransport, clientInfo: ClientInfo, defaultTimeoutMs?: number);
|
|
53
|
+
start(): Promise<void>;
|
|
54
|
+
private initialize;
|
|
55
|
+
get serverInfo(): {
|
|
56
|
+
name: string;
|
|
57
|
+
version: string;
|
|
58
|
+
} | undefined;
|
|
59
|
+
get capabilities(): Record<string, unknown> | undefined;
|
|
60
|
+
get instructions(): string | undefined;
|
|
61
|
+
listTools(): Promise<McpToolInfo[]>;
|
|
62
|
+
listPrompts(): Promise<McpPromptInfo[]>;
|
|
63
|
+
getPrompt(name: string, args: Record<string, string>, timeoutMs?: number): Promise<PromptResult>;
|
|
64
|
+
callTool(name: string, args: Record<string, unknown>, timeoutMs?: number): Promise<ToolCallResult>;
|
|
65
|
+
private request;
|
|
66
|
+
private onMessage;
|
|
67
|
+
close(): Promise<void>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClient — protocol layer over a transport.
|
|
3
|
+
*
|
|
4
|
+
* Handles JSON-RPC id correlation, the `initialize` handshake, and typed
|
|
5
|
+
* wrappers for `tools/list` / `tools/call`. Enough surface for the tool
|
|
6
|
+
* registry to plug MCP tools in; more methods can be added the same way.
|
|
7
|
+
*/
|
|
8
|
+
const PROTOCOL_VERSION = "2025-06-18";
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
10
|
+
export class MCPClient {
|
|
11
|
+
transport;
|
|
12
|
+
clientInfo;
|
|
13
|
+
defaultTimeoutMs;
|
|
14
|
+
nextId = 1;
|
|
15
|
+
pending = new Map();
|
|
16
|
+
closed = false;
|
|
17
|
+
_serverInfo;
|
|
18
|
+
_capabilities;
|
|
19
|
+
_instructions;
|
|
20
|
+
constructor(transport, clientInfo, defaultTimeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
21
|
+
this.transport = transport;
|
|
22
|
+
this.clientInfo = clientInfo;
|
|
23
|
+
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
24
|
+
transport.onMessage((msg) => this.onMessage(msg));
|
|
25
|
+
transport.onError((err) => {
|
|
26
|
+
// Fail all outstanding calls; the manager will surface this.
|
|
27
|
+
for (const [, pending] of this.pending) {
|
|
28
|
+
clearTimeout(pending.timer);
|
|
29
|
+
pending.reject(err);
|
|
30
|
+
}
|
|
31
|
+
this.pending.clear();
|
|
32
|
+
});
|
|
33
|
+
transport.onClose(() => {
|
|
34
|
+
this.closed = true;
|
|
35
|
+
for (const [, pending] of this.pending) {
|
|
36
|
+
clearTimeout(pending.timer);
|
|
37
|
+
pending.reject(new Error("MCP transport closed"));
|
|
38
|
+
}
|
|
39
|
+
this.pending.clear();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async start() {
|
|
43
|
+
await this.transport.start();
|
|
44
|
+
await this.initialize();
|
|
45
|
+
}
|
|
46
|
+
async initialize() {
|
|
47
|
+
const result = (await this.request("initialize", {
|
|
48
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
49
|
+
capabilities: {},
|
|
50
|
+
clientInfo: this.clientInfo,
|
|
51
|
+
}));
|
|
52
|
+
this._serverInfo = result.serverInfo;
|
|
53
|
+
this._capabilities = result.capabilities;
|
|
54
|
+
this._instructions = result.instructions;
|
|
55
|
+
// Per spec, client must send `notifications/initialized` after the response.
|
|
56
|
+
await this.transport.send({
|
|
57
|
+
jsonrpc: "2.0",
|
|
58
|
+
method: "notifications/initialized",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
get serverInfo() {
|
|
62
|
+
return this._serverInfo;
|
|
63
|
+
}
|
|
64
|
+
get capabilities() {
|
|
65
|
+
return this._capabilities;
|
|
66
|
+
}
|
|
67
|
+
get instructions() {
|
|
68
|
+
return this._instructions;
|
|
69
|
+
}
|
|
70
|
+
async listTools() {
|
|
71
|
+
const tools = [];
|
|
72
|
+
let cursor;
|
|
73
|
+
do {
|
|
74
|
+
const result = (await this.request("tools/list", cursor ? { cursor } : {}));
|
|
75
|
+
if (Array.isArray(result.tools)) {
|
|
76
|
+
for (const t of result.tools) {
|
|
77
|
+
tools.push({
|
|
78
|
+
name: t.name,
|
|
79
|
+
description: t.description,
|
|
80
|
+
inputSchema: t.inputSchema,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
cursor = result.nextCursor;
|
|
85
|
+
} while (cursor);
|
|
86
|
+
return tools;
|
|
87
|
+
}
|
|
88
|
+
async listPrompts() {
|
|
89
|
+
const prompts = [];
|
|
90
|
+
let cursor;
|
|
91
|
+
do {
|
|
92
|
+
const result = (await this.request("prompts/list", cursor ? { cursor } : {}));
|
|
93
|
+
if (Array.isArray(result.prompts)) {
|
|
94
|
+
for (const p of result.prompts) {
|
|
95
|
+
prompts.push({
|
|
96
|
+
name: p.name,
|
|
97
|
+
description: p.description,
|
|
98
|
+
arguments: p.arguments,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
cursor = result.nextCursor;
|
|
103
|
+
} while (cursor);
|
|
104
|
+
return prompts;
|
|
105
|
+
}
|
|
106
|
+
async getPrompt(name, args, timeoutMs) {
|
|
107
|
+
const result = (await this.request("prompts/get", { name, arguments: args }, timeoutMs));
|
|
108
|
+
return {
|
|
109
|
+
description: result.description,
|
|
110
|
+
messages: Array.isArray(result.messages) ? result.messages : [],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async callTool(name, args, timeoutMs) {
|
|
114
|
+
const result = (await this.request("tools/call", { name, arguments: args }, timeoutMs));
|
|
115
|
+
return {
|
|
116
|
+
content: Array.isArray(result.content) ? result.content : [],
|
|
117
|
+
isError: result.isError,
|
|
118
|
+
structuredContent: result.structuredContent,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
request(method, params, timeoutMs) {
|
|
122
|
+
if (this.closed)
|
|
123
|
+
return Promise.reject(new Error("MCP client is closed"));
|
|
124
|
+
const id = this.nextId++;
|
|
125
|
+
const req = { jsonrpc: "2.0", id, method, params };
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const ms = timeoutMs ?? this.defaultTimeoutMs;
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
this.pending.delete(id);
|
|
130
|
+
reject(new Error(`MCP request "${method}" timed out after ${ms}ms`));
|
|
131
|
+
}, ms);
|
|
132
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
133
|
+
this.transport.send(req).catch((err) => {
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
this.pending.delete(id);
|
|
136
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
onMessage(msg) {
|
|
141
|
+
// Response: has "id" plus "result" or "error".
|
|
142
|
+
if ("id" in msg && (("result" in msg) || ("error" in msg))) {
|
|
143
|
+
const id = typeof msg.id === "number" ? msg.id : Number(msg.id);
|
|
144
|
+
const pending = this.pending.get(id);
|
|
145
|
+
if (!pending)
|
|
146
|
+
return;
|
|
147
|
+
this.pending.delete(id);
|
|
148
|
+
clearTimeout(pending.timer);
|
|
149
|
+
if ("error" in msg && msg.error) {
|
|
150
|
+
pending.reject(new Error(`${msg.error.message} (code ${msg.error.code})`));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
pending.resolve(msg.result);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Server→client request or notification. v1 ignores these (no sampling, no roots).
|
|
158
|
+
}
|
|
159
|
+
async close() {
|
|
160
|
+
this.closed = true;
|
|
161
|
+
await this.transport.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load and validate mcpServers from user / project / local settings files.
|
|
3
|
+
*
|
|
4
|
+
* Precedence: later scope wins (user → project → local). Each server is
|
|
5
|
+
* identified by its key in mcpServers; if the same name appears in multiple
|
|
6
|
+
* scopes, the higher-precedence scope overwrites, and we record a diagnostic.
|
|
7
|
+
*
|
|
8
|
+
* Env expansion: ${VAR} in command, args, env values, url, or header values
|
|
9
|
+
* is replaced with the matching process.env entry. Missing vars become empty
|
|
10
|
+
* strings and yield a diagnostic (non-fatal).
|
|
11
|
+
*/
|
|
12
|
+
import type { ScopedMcpServerConfig } from "./types.js";
|
|
13
|
+
export interface McpConfigDiagnostic {
|
|
14
|
+
scope: "user" | "project" | "local";
|
|
15
|
+
path: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export interface LoadedMcpConfig {
|
|
19
|
+
servers: ScopedMcpServerConfig[];
|
|
20
|
+
diagnostics: McpConfigDiagnostic[];
|
|
21
|
+
}
|
|
22
|
+
export interface LoadMcpConfigOptions {
|
|
23
|
+
cwd: string;
|
|
24
|
+
bubbleHome?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function loadMcpConfig(options: LoadMcpConfigOptions): LoadedMcpConfig;
|