@bubblebrain-ai/bubble 0.0.34 → 0.0.36
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 +27 -7
- package/dist/agent/abort-errors.d.ts +2 -2
- package/dist/agent/budget-ledger.d.ts +4 -45
- package/dist/agent/budget-ledger.js +5 -75
- package/dist/agent/categories.d.ts +66 -2
- package/dist/agent/categories.js +120 -19
- package/dist/agent/child-runner.d.ts +1 -3
- package/dist/agent/child-runner.js +6 -37
- package/dist/agent/discovery-barrier.js +1 -1
- package/dist/agent/profiles.d.ts +1 -2
- package/dist/agent/profiles.js +25 -14
- package/dist/agent/routing-catalog.d.ts +113 -0
- package/dist/agent/routing-catalog.js +240 -0
- package/dist/agent/subagent-control.d.ts +5 -12
- package/dist/agent/subagent-control.js +0 -2
- package/dist/agent/subagent-store.d.ts +1 -1
- package/dist/agent/subagent-store.js +1 -3
- package/dist/agent/workflow/control.d.ts +21 -0
- package/dist/agent/workflow/control.js +50 -8
- package/dist/agent/workflow/runtime.d.ts +15 -2
- package/dist/agent/workflow/runtime.js +112 -30
- package/dist/agent.d.ts +82 -48
- package/dist/agent.js +360 -232
- package/dist/approval/controller.js +28 -0
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +21 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -1
- package/dist/config.d.ts +5 -3
- package/dist/config.js +10 -5
- package/dist/context/compact-llm.d.ts +2 -2
- package/dist/external-runtime/grok-acp.d.ts +56 -0
- package/dist/external-runtime/grok-acp.js +386 -0
- package/dist/external-runtime/grok-binary.d.ts +25 -0
- package/dist/external-runtime/grok-binary.js +108 -0
- package/dist/external-runtime/grok-errors.d.ts +7 -0
- package/dist/external-runtime/grok-errors.js +15 -0
- package/dist/external-runtime/grok-input-policy.d.ts +58 -0
- package/dist/external-runtime/grok-input-policy.js +52 -0
- package/dist/external-runtime/grok-ndjson.d.ts +14 -0
- package/dist/external-runtime/grok-ndjson.js +193 -0
- package/dist/external-runtime/grok-network.d.ts +15 -0
- package/dist/external-runtime/grok-network.js +81 -0
- package/dist/external-runtime/grok-preflight.d.ts +28 -0
- package/dist/external-runtime/grok-preflight.js +194 -0
- package/dist/external-runtime/grok-process.d.ts +26 -0
- package/dist/external-runtime/grok-process.js +184 -0
- package/dist/external-runtime/grok-profile.d.ts +32 -0
- package/dist/external-runtime/grok-profile.js +419 -0
- package/dist/external-runtime/grok-provider.d.ts +31 -0
- package/dist/external-runtime/grok-provider.js +41 -0
- package/dist/external-runtime/grok-runtime.d.ts +91 -0
- package/dist/external-runtime/grok-runtime.js +882 -0
- package/dist/external-runtime/index.d.ts +13 -0
- package/dist/external-runtime/index.js +13 -0
- package/dist/external-runtime/session-policy.d.ts +11 -0
- package/dist/external-runtime/session-policy.js +30 -0
- package/dist/external-runtime/types.d.ts +65 -0
- package/dist/external-runtime/types.js +1 -0
- package/dist/feishu/agent-host/approval-card.js +21 -0
- package/dist/feishu/agent-host/run-driver.js +19 -3
- package/dist/feishu/serve.js +1 -0
- package/dist/goal/tools.d.ts +6 -3
- package/dist/goal/tools.js +7 -19
- package/dist/main.js +114 -28
- package/dist/memory/db.d.ts +2 -0
- package/dist/memory/db.js +10 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/phase1.js +14 -2
- package/dist/memory/phase2.js +21 -2
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/session-policy.d.ts +25 -0
- package/dist/memory/session-policy.js +55 -0
- package/dist/memory/storage.d.ts +1 -0
- package/dist/memory/storage.js +13 -0
- package/dist/model-catalog.d.ts +18 -0
- package/dist/model-catalog.js +92 -52
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +11 -0
- package/dist/oauth/grok.d.ts +25 -0
- package/dist/oauth/grok.js +218 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/oauth/storage.d.ts +9 -0
- package/dist/oauth/storage.js +25 -0
- package/dist/orchestrator/default-hooks.js +23 -7
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.d.ts +1 -1
- package/dist/prompt/delegation.js +41 -8
- package/dist/prompt/environment.js +0 -4
- package/dist/prompt/reminders.d.ts +0 -1
- package/dist/prompt/reminders.js +1 -11
- package/dist/prompt/routing.d.ts +14 -0
- package/dist/prompt/routing.js +71 -0
- package/dist/prompt/runtime.js +1 -1
- package/dist/prompt/task-reminders.d.ts +20 -0
- package/dist/prompt/task-reminders.js +73 -1
- package/dist/provider-ai-sdk.js +2 -2
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +59 -0
- package/dist/provider-openai-codex.d.ts +17 -1
- package/dist/provider-openai-codex.js +101 -29
- package/dist/provider-registry.d.ts +72 -0
- package/dist/provider-registry.js +336 -59
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +35 -17
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +14 -0
- package/dist/session-types.d.ts +19 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.js +4 -0
- package/dist/slash-commands/commands.js +224 -14
- package/dist/slash-commands/types.d.ts +14 -1
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.d.ts +1 -9
- package/dist/tools/agent-lifecycle.js +42 -272
- package/dist/tools/bash.js +1 -1
- package/dist/tools/child-tools.js +2 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +7 -5
- package/dist/tools/lsp.js +1 -0
- package/dist/tools/memory.d.ts +7 -2
- package/dist/tools/memory.js +28 -43
- package/dist/tools/server.js +4 -0
- package/dist/tools/tool-search.d.ts +1 -1
- package/dist/tools/tool-search.js +23 -3
- package/dist/tui/model-switch.d.ts +7 -0
- package/dist/tui/model-switch.js +15 -5
- package/dist/tui-ink/app.d.ts +3 -1
- package/dist/tui-ink/app.js +672 -243
- package/dist/tui-ink/approval/approval-dialog.js +37 -1
- package/dist/tui-ink/approval/diff-view.js +4 -4
- package/dist/tui-ink/approval/select.js +2 -2
- package/dist/tui-ink/composer-buffer.d.ts +55 -0
- package/dist/tui-ink/composer-buffer.js +317 -0
- package/dist/tui-ink/feedback-dialog.js +2 -2
- package/dist/tui-ink/feishu-setup-picker.js +3 -3
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +7 -2
- package/dist/tui-ink/input-box.d.ts +36 -7
- package/dist/tui-ink/input-box.js +229 -157
- package/dist/tui-ink/markdown.js +11 -9
- package/dist/tui-ink/message-list.d.ts +5 -1
- package/dist/tui-ink/message-list.js +15 -2
- package/dist/tui-ink/model-picker.d.ts +61 -4
- package/dist/tui-ink/model-picker.js +464 -128
- package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
- package/dist/tui-ink/paste-operation-tracker.js +25 -0
- package/dist/tui-ink/plan-confirm.js +1 -1
- package/dist/tui-ink/run.d.ts +3 -0
- package/dist/tui-ink/run.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +34 -1
- package/dist/tui-ink/subagent-view.js +82 -1
- package/dist/tui-ink/theme.d.ts +19 -2
- package/dist/tui-ink/theme.js +25 -8
- package/dist/tui-ink/welcome.d.ts +3 -2
- package/dist/tui-ink/welcome.js +10 -6
- package/dist/types.d.ts +8 -31
- package/dist/types.js +1 -1
- package/dist/variant/thinking-level.d.ts +2 -2
- package/dist/variant/thinking-level.js +2 -1
- package/dist/variant/variant-resolver.d.ts +22 -0
- package/dist/variant/variant-resolver.js +43 -0
- package/package.json +6 -2
- package/dist/tools/task.d.ts +0 -2
- package/dist/tools/task.js +0 -58
package/dist/session.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export declare class SessionManager {
|
|
|
51
51
|
setMetadata(metadata: SessionMetadata): void;
|
|
52
52
|
updateMetadata(patch: Partial<SessionMetadata>): void;
|
|
53
53
|
clearTitleMetadata(): void;
|
|
54
|
+
clearExternalRuntimeMetadata(): void;
|
|
54
55
|
appendMessage(message: Message): void;
|
|
55
56
|
appendCompaction(summary: string): void;
|
|
56
57
|
appendMarker(kind: SessionMarkerKind, value: string): void;
|
package/dist/session.js
CHANGED
|
@@ -134,6 +134,10 @@ export class SessionManager {
|
|
|
134
134
|
const { title: _title, titleSource: _titleSource, titleUpdatedAt: _titleUpdatedAt, titleUserMessageId: _titleUserMessageId, ...metadata } = this.log.getMetadata();
|
|
135
135
|
this.setMetadata(metadata);
|
|
136
136
|
}
|
|
137
|
+
clearExternalRuntimeMetadata() {
|
|
138
|
+
const { externalRuntime: _externalRuntime, ...metadata } = this.log.getMetadata();
|
|
139
|
+
this.setMetadata(metadata);
|
|
140
|
+
}
|
|
137
141
|
appendMessage(message) {
|
|
138
142
|
const entries = this.log.appendMessage(message);
|
|
139
143
|
this.persist(entries);
|
|
@@ -11,9 +11,13 @@ import { normalizeSingleLine } from "../text-display.js";
|
|
|
11
11
|
import { formatRelativeTime } from "../tui/recent-activity.js";
|
|
12
12
|
import { HOOK_EVENT_NAMES, isHookEventName } from "../hooks/index.js";
|
|
13
13
|
import { isThinkingLevel } from "../variant/thinking-level.js";
|
|
14
|
+
import { normalizeInheritedThinkingLevel } from "../variant/variant-resolver.js";
|
|
14
15
|
import { collectUsageStatsBundle, formatStatsText } from "../stats/usage.js";
|
|
15
16
|
import { buildMemoryPrompt, getMemoryStatus, isMemoryDisabled, resetMemory, searchMemory, } from "../memory/index.js";
|
|
16
17
|
import { feishuCommand } from "./feishu.js";
|
|
18
|
+
import { GROK_LOCAL_COMMAND_HELP } from "../external-runtime/grok-input-policy.js";
|
|
19
|
+
import { isGrokSubscriptionProviderId, } from "../external-runtime/grok-provider.js";
|
|
20
|
+
import { classifyExternalRuntimeBinding } from "../external-runtime/session-policy.js";
|
|
17
21
|
const VALID_SCOPES = ["user", "project", "local"];
|
|
18
22
|
const VALID_LISTS = ["allow", "deny"];
|
|
19
23
|
function isScope(value) {
|
|
@@ -141,13 +145,15 @@ function formatMcpContextStatus(ctx) {
|
|
|
141
145
|
lines.push("- MCP prompts are slash commands; they do not enter context until invoked.");
|
|
142
146
|
return lines.join("\n");
|
|
143
147
|
}
|
|
144
|
-
function switchToProviderModel(providerId, modelId, ctx, thinkingLevel) {
|
|
148
|
+
function switchToProviderModel(providerId, modelId, ctx, thinkingLevel, preparedProvider) {
|
|
145
149
|
const provider = ctx.registry.getConfigured().find((item) => item.id === providerId);
|
|
146
150
|
if (!provider?.apiKey) {
|
|
147
151
|
return false;
|
|
148
152
|
}
|
|
149
|
-
ctx.agent.thinking =
|
|
150
|
-
|
|
153
|
+
ctx.agent.thinking = thinkingLevel !== undefined
|
|
154
|
+
? normalizeThinkingLevel(thinkingLevel, getAvailableThinkingLevels(providerId, modelId))
|
|
155
|
+
: normalizeInheritedThinkingLevel(providerId, modelId, ctx.agent.thinking);
|
|
156
|
+
ctx.agent.setProvider(preparedProvider ?? ctx.createProvider(providerId, provider.apiKey, provider.baseURL));
|
|
151
157
|
ctx.agent.providerId = providerId;
|
|
152
158
|
ctx.agent.model = encodeModel(providerId, modelId);
|
|
153
159
|
syncSystemPrompt(ctx, ctx.agent.model);
|
|
@@ -182,6 +188,122 @@ function displaySelectedModel(model, thinkingLevel) {
|
|
|
182
188
|
const defaultLevel = providerId ? getDefaultThinkingLevel(providerId, modelId) : "off";
|
|
183
189
|
return thinkingLevel === "off" || thinkingLevel === defaultLevel ? label : `${label} (${thinkingLevel})`;
|
|
184
190
|
}
|
|
191
|
+
function getGrokSessionId(ctx) {
|
|
192
|
+
const binding = ctx.sessionManager?.getMetadata().externalRuntime;
|
|
193
|
+
return binding && isGrokSubscriptionProviderId(binding.id) ? binding.sessionId : undefined;
|
|
194
|
+
}
|
|
195
|
+
function isGrokSessionActive(ctx) {
|
|
196
|
+
return classifyExternalRuntimeBinding(ctx.sessionManager?.getMetadata().externalRuntime) === "grok";
|
|
197
|
+
}
|
|
198
|
+
function hasExternalRuntimeSession(ctx) {
|
|
199
|
+
return classifyExternalRuntimeBinding(ctx.sessionManager?.getMetadata().externalRuntime) !== "none";
|
|
200
|
+
}
|
|
201
|
+
async function transitionGrokSessionToNative(ctx) {
|
|
202
|
+
if (!hasExternalRuntimeSession(ctx))
|
|
203
|
+
return;
|
|
204
|
+
if (!ctx.transitionToNative) {
|
|
205
|
+
throw new Error("Switching from Grok subscription to a native Bubble session is unavailable in this mode.");
|
|
206
|
+
}
|
|
207
|
+
const next = await ctx.transitionToNative();
|
|
208
|
+
if (!next) {
|
|
209
|
+
throw new Error("Bubble could not start a fresh native session.");
|
|
210
|
+
}
|
|
211
|
+
// Handlers below may persist provider/model metadata after the transition.
|
|
212
|
+
// Keep those writes on the new native session rather than the old Grok log.
|
|
213
|
+
ctx.sessionManager = next;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Sign in to the Grok subscription as a native model provider (like ChatGPT
|
|
217
|
+
* OAuth): browser PKCE against auth.x.ai, tokens in ~/.bubble/auth.json, and
|
|
218
|
+
* grok models served through Bubble's own agent loop. An existing Grok CLI
|
|
219
|
+
* login is imported so users who already ran `grok login` skip the browser.
|
|
220
|
+
*/
|
|
221
|
+
async function loginGrokSubscription(ctx) {
|
|
222
|
+
const providerId = "grok";
|
|
223
|
+
const { loginGrok, importGrokCliCredentials } = await import("../oauth/grok.js");
|
|
224
|
+
const storage = ctx.registry.getAuthStorage();
|
|
225
|
+
const runBrowserLogin = async () => {
|
|
226
|
+
const tokens = await loginGrok({
|
|
227
|
+
onStatus: (msg) => ctx.addMessage("assistant", msg),
|
|
228
|
+
});
|
|
229
|
+
storage.set(providerId, {
|
|
230
|
+
type: "oauth",
|
|
231
|
+
accessToken: tokens.accessToken,
|
|
232
|
+
refreshToken: tokens.refreshToken,
|
|
233
|
+
expiresAt: tokens.expiresAt,
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
let importedExisting = false;
|
|
237
|
+
if (!storage.has(providerId)) {
|
|
238
|
+
const imported = importGrokCliCredentials();
|
|
239
|
+
if (imported) {
|
|
240
|
+
storage.set(providerId, {
|
|
241
|
+
type: "oauth",
|
|
242
|
+
accessToken: imported.accessToken,
|
|
243
|
+
refreshToken: imported.refreshToken,
|
|
244
|
+
expiresAt: imported.expiresAt,
|
|
245
|
+
});
|
|
246
|
+
importedExisting = true;
|
|
247
|
+
ctx.addMessage("assistant", "Imported the existing Grok CLI sign-in.");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const hadStoredLogin = storage.has(providerId);
|
|
251
|
+
if (!hadStoredLogin) {
|
|
252
|
+
await runBrowserLogin();
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await ctx.registry.prepareProvider(providerId);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
// Stored/imported credentials whose refresh token was revoked get exactly
|
|
259
|
+
// one browser repair attempt; a failure right after browser login is real.
|
|
260
|
+
if (!hadStoredLogin)
|
|
261
|
+
throw error;
|
|
262
|
+
ctx.addMessage("assistant", `Stored Grok credentials could not be refreshed (${error instanceof Error ? error.message : String(error)}). Signing in again…`);
|
|
263
|
+
storage.remove(providerId);
|
|
264
|
+
await runBrowserLogin();
|
|
265
|
+
await ctx.registry.prepareProvider(providerId);
|
|
266
|
+
}
|
|
267
|
+
const provider = ctx.registry.getConfigured().find((item) => item.id === providerId);
|
|
268
|
+
const defaultModel = ctx.registry.getDefaultModel(providerId, "oauth");
|
|
269
|
+
if (!provider?.apiKey || !defaultModel) {
|
|
270
|
+
return `Grok subscription login succeeded, but the provider could not be activated. Tokens saved to ${storage.getPath()}`;
|
|
271
|
+
}
|
|
272
|
+
// Complete every fallible preparation step before leaving a legacy
|
|
273
|
+
// Grok-runtime session; commit only after the native switch succeeds.
|
|
274
|
+
const preparedProvider = ctx.createProvider(providerId, provider.apiKey, provider.baseURL);
|
|
275
|
+
await transitionGrokSessionToNative(ctx);
|
|
276
|
+
const switched = switchToProviderModel(providerId, defaultModel, ctx, undefined, preparedProvider);
|
|
277
|
+
if (!switched) {
|
|
278
|
+
return `Grok subscription login succeeded, but the provider could not be activated. Tokens saved to ${storage.getPath()}`;
|
|
279
|
+
}
|
|
280
|
+
ctx.registry.setDefault(providerId);
|
|
281
|
+
const via = importedExisting ? " (reused your Grok CLI sign-in)" : "";
|
|
282
|
+
return `Grok subscription login successful${via}. Switched to ${displayModel(ctx.agent.model)}. Tokens saved to ${storage.getPath()}`;
|
|
283
|
+
}
|
|
284
|
+
async function logoutGrokSubscription(ctx) {
|
|
285
|
+
const storage = ctx.registry.getAuthStorage();
|
|
286
|
+
const hadNativeLogin = storage.has("grok");
|
|
287
|
+
storage.remove("grok");
|
|
288
|
+
// Legacy cleanup: sessions bound to the old Grok ACP runtime, and the
|
|
289
|
+
// isolated CLI profile's credentials.
|
|
290
|
+
if (isGrokSessionActive(ctx) && ctx.externalRuntime && ctx.startFreshSession) {
|
|
291
|
+
await ctx.externalRuntime.cancel(getGrokSessionId(ctx)).catch(() => undefined);
|
|
292
|
+
await ctx.externalRuntime.dispose().catch(() => undefined);
|
|
293
|
+
await ctx.externalRuntime.logout().catch(() => undefined);
|
|
294
|
+
const freshSession = await ctx.startFreshSession();
|
|
295
|
+
freshSession.clearExternalRuntimeMetadata();
|
|
296
|
+
freshSession.appendMarker("runtime_switch", "native");
|
|
297
|
+
ctx.sessionManager = freshSession;
|
|
298
|
+
ctx.onExternalRuntimeChange?.(freshSession);
|
|
299
|
+
return "Grok subscription logged out. Started a fresh native Bubble session.";
|
|
300
|
+
}
|
|
301
|
+
await ctx.externalRuntime?.logout().catch(() => undefined);
|
|
302
|
+
if (!hadNativeLogin) {
|
|
303
|
+
return "No Grok subscription login was stored on this device.";
|
|
304
|
+
}
|
|
305
|
+
return "Grok subscription logged out. Only this device's local login was removed.";
|
|
306
|
+
}
|
|
185
307
|
function parseMemoryScopeArgs(args) {
|
|
186
308
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
187
309
|
let scope = "project";
|
|
@@ -298,6 +420,16 @@ const builtinSlashCommandEntries = [
|
|
|
298
420
|
name: "help",
|
|
299
421
|
description: "Show available slash commands",
|
|
300
422
|
async handler(args, ctx) {
|
|
423
|
+
if (hasExternalRuntimeSession(ctx)) {
|
|
424
|
+
const grok = isGrokSessionActive(ctx);
|
|
425
|
+
return [
|
|
426
|
+
grok
|
|
427
|
+
? "Grok Subscription · workspace tools · Bubble approvals"
|
|
428
|
+
: "Unsupported external runtime session · recovery-only mode",
|
|
429
|
+
"Available commands:",
|
|
430
|
+
...GROK_LOCAL_COMMAND_HELP.map((command) => (` ${command.usage} - ${command.description}`)),
|
|
431
|
+
].join("\n");
|
|
432
|
+
}
|
|
301
433
|
const { registry } = await import("./index.js");
|
|
302
434
|
const lines = ["Available commands:"];
|
|
303
435
|
for (const cmd of registry.list()) {
|
|
@@ -475,11 +607,12 @@ const builtinSlashCommandEntries = [
|
|
|
475
607
|
name: "provider",
|
|
476
608
|
description: "Manage providers. /provider to switch, /provider --add [id] to add, /provider --remove <id>, /provider --set <id>",
|
|
477
609
|
async handler(args, ctx) {
|
|
478
|
-
|
|
610
|
+
const trimmed = args.trim();
|
|
611
|
+
if (!trimmed) {
|
|
479
612
|
ctx.openPicker("provider");
|
|
480
613
|
return;
|
|
481
614
|
}
|
|
482
|
-
const parts =
|
|
615
|
+
const parts = trimmed.split(/\s+/);
|
|
483
616
|
const flag = parts[0];
|
|
484
617
|
const value = parts[1];
|
|
485
618
|
if (flag === "--add") {
|
|
@@ -487,6 +620,9 @@ const builtinSlashCommandEntries = [
|
|
|
487
620
|
ctx.openPicker("provider-add");
|
|
488
621
|
return;
|
|
489
622
|
}
|
|
623
|
+
if (isGrokSubscriptionProviderId(value)) {
|
|
624
|
+
return "Grok Subscription is built in. Use /provider --set grok or /login grok to activate it.";
|
|
625
|
+
}
|
|
490
626
|
const builtin = BUILTIN_PROVIDERS.find((p) => p.id === value && isUserVisibleProvider(p.id));
|
|
491
627
|
if (!builtin) {
|
|
492
628
|
const ids = BUILTIN_PROVIDERS.filter((p) => isUserVisibleProvider(p.id)).map((p) => p.id).join(", ");
|
|
@@ -498,17 +634,27 @@ const builtinSlashCommandEntries = [
|
|
|
498
634
|
return;
|
|
499
635
|
}
|
|
500
636
|
if (flag === "--remove" && value) {
|
|
637
|
+
if (isGrokSubscriptionProviderId(value)) {
|
|
638
|
+
return "Grok Subscription is built in and cannot be removed. Use /logout grok to remove its local login.";
|
|
639
|
+
}
|
|
501
640
|
if (ctx.registry.getModelConfig().hasProvider(value)) {
|
|
502
641
|
return `Provider ${value} is defined in ~/.bubble/models.json. Please edit that file directly.`;
|
|
503
642
|
}
|
|
504
643
|
ctx.registry.removeProvider(value);
|
|
644
|
+
// Registry mutation without a model switch still changes the routing
|
|
645
|
+
// world (design §1.6): refresh the routing menu in the system prompt.
|
|
646
|
+
syncSystemPrompt(ctx, ctx.agent.model);
|
|
505
647
|
return `Provider ${value} removed.`;
|
|
506
648
|
}
|
|
507
649
|
if (flag === "--set" && value) {
|
|
650
|
+
if (isGrokSubscriptionProviderId(value)) {
|
|
651
|
+
return loginGrokSubscription(ctx);
|
|
652
|
+
}
|
|
508
653
|
const providers = ctx.registry.getConfigured();
|
|
509
654
|
const p = providers.find((x) => x.id === value);
|
|
510
655
|
if (!p)
|
|
511
656
|
return `Provider ${value} is not configured.`;
|
|
657
|
+
await transitionGrokSessionToNative(ctx);
|
|
512
658
|
ctx.registry.setDefault(value);
|
|
513
659
|
if (ctx.registry.getModelConfig().hasProvider(value)) {
|
|
514
660
|
return `Default provider set to ${p.name}. Note: config is managed via ~/.bubble/models.json.`;
|
|
@@ -517,13 +663,17 @@ const builtinSlashCommandEntries = [
|
|
|
517
663
|
}
|
|
518
664
|
if (flag === "--list") {
|
|
519
665
|
const providers = ctx.registry.getConfigured();
|
|
520
|
-
const
|
|
666
|
+
const grokActive = isGrokSessionActive(ctx);
|
|
667
|
+
const lines = ["Available providers:"];
|
|
521
668
|
for (const p of providers) {
|
|
522
|
-
const marker = p.id === ctx.registry.getDefault()?.id ? "* " : " ";
|
|
669
|
+
const marker = !grokActive && p.id === ctx.registry.getDefault()?.id ? "* " : " ";
|
|
523
670
|
const source = ctx.registry.getModelConfig().hasProvider(p.id) ? " [models.json]" : "";
|
|
524
671
|
const oauth = ctx.registry.getAuthStorage().has(p.id) ? " [oauth]" : "";
|
|
525
672
|
lines.push(`${marker}${p.name} (${p.id}) ${p.enabled ? "" : "[disabled]"}${oauth}${source}`);
|
|
526
673
|
}
|
|
674
|
+
if (!providers.some((provider) => provider.id === "grok")) {
|
|
675
|
+
lines.push(`${grokActive ? "* " : " "}Grok Subscription (grok) [not signed in — /login grok]`);
|
|
676
|
+
}
|
|
527
677
|
if (ctx.registry.getModelConfig().getLoadError()) {
|
|
528
678
|
lines.push(`Warning: failed to load models.json: ${ctx.registry.getModelConfig().getLoadError()}`);
|
|
529
679
|
}
|
|
@@ -534,15 +684,18 @@ const builtinSlashCommandEntries = [
|
|
|
534
684
|
},
|
|
535
685
|
{
|
|
536
686
|
name: "login",
|
|
537
|
-
description: "OAuth
|
|
687
|
+
description: "Login to OpenAI OAuth or Grok Subscription. Usage: /login [openai|grok]",
|
|
538
688
|
async handler(args, ctx) {
|
|
539
689
|
const providerId = args?.trim() || "openai";
|
|
540
690
|
if (!providerId) {
|
|
541
691
|
ctx.openPicker("login");
|
|
542
692
|
return;
|
|
543
693
|
}
|
|
694
|
+
if (isGrokSubscriptionProviderId(providerId)) {
|
|
695
|
+
return loginGrokSubscription(ctx);
|
|
696
|
+
}
|
|
544
697
|
if (!ctx.registry.supportsOAuth(providerId)) {
|
|
545
|
-
return `Unsupported
|
|
698
|
+
return `Unsupported login provider: ${providerId}. Supported providers: 'openai' and 'grok'.`;
|
|
546
699
|
}
|
|
547
700
|
const { loginOpenAICodex } = await import("../oauth/openai-codex.js");
|
|
548
701
|
const tokens = await loginOpenAICodex({
|
|
@@ -557,17 +710,25 @@ const builtinSlashCommandEntries = [
|
|
|
557
710
|
accountId: tokens.accountId,
|
|
558
711
|
});
|
|
559
712
|
await ctx.registry.prepareProvider(providerId);
|
|
560
|
-
ctx.registry.setDefault(providerId);
|
|
561
713
|
const provider = ctx.registry.getConfigured().find((item) => item.id === providerId);
|
|
562
714
|
const discoveredModels = provider ? await ctx.registry.listModels(provider) : [];
|
|
563
715
|
const defaultModel = discoveredModels[0]?.id || ctx.registry.getDefaultModel(providerId, "oauth");
|
|
564
716
|
if (!defaultModel) {
|
|
565
717
|
return `OpenAI Codex OAuth login succeeded, but no default model is configured for ${providerId}.`;
|
|
566
718
|
}
|
|
567
|
-
|
|
719
|
+
if (!provider?.apiKey) {
|
|
720
|
+
return `OpenAI Codex OAuth login succeeded, but the provider could not be activated. Tokens saved to ${ctx.registry.getAuthStorage().getPath()}`;
|
|
721
|
+
}
|
|
722
|
+
// Complete every fallible OAuth/provider/model preparation step before
|
|
723
|
+
// leaving a Grok-bound session. The provider object is created once and
|
|
724
|
+
// committed only after the fresh native session switch succeeds.
|
|
725
|
+
const preparedProvider = ctx.createProvider(providerId, provider.apiKey, provider.baseURL);
|
|
726
|
+
await transitionGrokSessionToNative(ctx);
|
|
727
|
+
const switched = switchToProviderModel(providerId, defaultModel, ctx, undefined, preparedProvider);
|
|
568
728
|
if (!switched) {
|
|
569
729
|
return `OpenAI Codex OAuth login succeeded, but the provider could not be activated. Tokens saved to ${ctx.registry.getAuthStorage().getPath()}`;
|
|
570
730
|
}
|
|
731
|
+
ctx.registry.setDefault(providerId);
|
|
571
732
|
return `OpenAI Codex OAuth login successful. Switched to ${displayModel(ctx.agent.model)}. Account: ${tokens.accountId || "unknown"}. Tokens saved to ${ctx.registry.getAuthStorage().getPath()}`;
|
|
572
733
|
},
|
|
573
734
|
},
|
|
@@ -576,6 +737,13 @@ const builtinSlashCommandEntries = [
|
|
|
576
737
|
description: "Switch model. Use /model <id> [--reasoning-effort <level>] or just /model to open picker.",
|
|
577
738
|
async handler(args, ctx) {
|
|
578
739
|
if (!args) {
|
|
740
|
+
if (hasExternalRuntimeSession(ctx)) {
|
|
741
|
+
if (isGrokSessionActive(ctx)) {
|
|
742
|
+
ctx.openPicker("model");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
return "This external runtime manages its model and reasoning settings. Bubble's model picker is unavailable until you switch to a native session.";
|
|
746
|
+
}
|
|
579
747
|
if (ctx.registry.getEnabled().length === 0) {
|
|
580
748
|
ctx.openPicker("model");
|
|
581
749
|
return;
|
|
@@ -588,30 +756,69 @@ const builtinSlashCommandEntries = [
|
|
|
588
756
|
return parsed.error;
|
|
589
757
|
}
|
|
590
758
|
if (!parsed.model) {
|
|
759
|
+
if (hasExternalRuntimeSession(ctx)) {
|
|
760
|
+
if (isGrokSessionActive(ctx)) {
|
|
761
|
+
ctx.openPicker("model");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
return "This external runtime manages its model and reasoning settings. Bubble's model picker is unavailable until you switch to a native session.";
|
|
765
|
+
}
|
|
591
766
|
ctx.openPicker("model");
|
|
592
767
|
return;
|
|
593
768
|
}
|
|
769
|
+
const explicitModelProvider = parsed.model.includes(":") ? parsed.model.split(":", 1)[0] : undefined;
|
|
770
|
+
if (isGrokSessionActive(ctx) && (!explicitModelProvider || isGrokSubscriptionProviderId(explicitModelProvider))) {
|
|
771
|
+
if (!ctx.externalRuntime)
|
|
772
|
+
return "Grok Subscription runtime is unavailable.";
|
|
773
|
+
const boundSessionId = getGrokSessionId(ctx);
|
|
774
|
+
if (boundSessionId)
|
|
775
|
+
await ctx.externalRuntime.hydrateSession(boundSessionId);
|
|
776
|
+
const requestedModel = parsed.model.includes(":")
|
|
777
|
+
? parsed.model.split(":").slice(1).join(":")
|
|
778
|
+
: parsed.model;
|
|
779
|
+
const selection = await ctx.externalRuntime.setModel(requestedModel, parsed.thinkingLevel);
|
|
780
|
+
const metadata = ctx.sessionManager?.getMetadata().externalRuntime;
|
|
781
|
+
if (metadata && isGrokSubscriptionProviderId(metadata.id)) {
|
|
782
|
+
ctx.sessionManager?.updateMetadata({
|
|
783
|
+
externalRuntime: {
|
|
784
|
+
...metadata,
|
|
785
|
+
modelId: selection.modelId,
|
|
786
|
+
reasoningEffort: selection.reasoningEffort,
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
ctx.sessionManager?.appendMarker("model_switch", selection.modelId ?? requestedModel);
|
|
791
|
+
ctx.sessionManager?.appendMarker("thinking_level_switch", selection.reasoningEffort);
|
|
792
|
+
ctx.onExternalRuntimeChange?.(ctx.sessionManager);
|
|
793
|
+
return `Grok model switched to ${selection.modelId ?? requestedModel}${selection.reasoningEffort !== "off" ? ` (${selection.reasoningEffort})` : ""}.`;
|
|
794
|
+
}
|
|
594
795
|
const defaultProvider = ctx.registry.getDefault()?.id || "openai";
|
|
595
796
|
const next = parsed.model.includes(":") ? parsed.model : encodeModel(defaultProvider, parsed.model);
|
|
596
797
|
const { providerId, modelId } = decodeModel(next);
|
|
597
798
|
const targetProviderId = providerId || defaultProvider;
|
|
598
799
|
await ctx.registry.prepareProvider(targetProviderId);
|
|
599
|
-
const
|
|
600
|
-
if (!
|
|
800
|
+
const targetProvider = ctx.registry.getConfigured().find((item) => item.id === targetProviderId);
|
|
801
|
+
if (!targetProvider?.apiKey) {
|
|
601
802
|
return `Provider ${targetProviderId} is not configured or has no active credentials.`;
|
|
602
803
|
}
|
|
804
|
+
const preparedProvider = ctx.createProvider(targetProviderId, targetProvider.apiKey, targetProvider.baseURL);
|
|
805
|
+
await transitionGrokSessionToNative(ctx);
|
|
806
|
+
switchToProviderModel(targetProviderId, modelId, ctx, parsed.thinkingLevel, preparedProvider);
|
|
603
807
|
return `Model switched to ${displaySelectedModel(next, ctx.agent.thinking)}.`;
|
|
604
808
|
},
|
|
605
809
|
},
|
|
606
810
|
{
|
|
607
811
|
name: "logout",
|
|
608
|
-
description: "Remove
|
|
812
|
+
description: "Remove local login credentials. Usage: /logout [openai|grok]",
|
|
609
813
|
async handler(args, ctx) {
|
|
610
814
|
const providerId = args?.trim() || "openai";
|
|
611
815
|
if (!providerId) {
|
|
612
816
|
ctx.openPicker("logout");
|
|
613
817
|
return;
|
|
614
818
|
}
|
|
819
|
+
if (isGrokSubscriptionProviderId(providerId)) {
|
|
820
|
+
return logoutGrokSubscription(ctx);
|
|
821
|
+
}
|
|
615
822
|
if (!ctx.registry.getAuthStorage().has(providerId)) {
|
|
616
823
|
return `No OAuth credentials found for ${providerId}.`;
|
|
617
824
|
}
|
|
@@ -629,6 +836,9 @@ const builtinSlashCommandEntries = [
|
|
|
629
836
|
else if (ctx.agent.providerId === providerId) {
|
|
630
837
|
ctx.agent.providerId = "";
|
|
631
838
|
}
|
|
839
|
+
// No-fallback logout mutates the routing world without a model switch
|
|
840
|
+
// (design §1.6): refresh the menu so it stops advertising the provider.
|
|
841
|
+
syncSystemPrompt(ctx, ctx.agent.model);
|
|
632
842
|
return `OAuth credentials for ${providerId} removed.`;
|
|
633
843
|
},
|
|
634
844
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Agent } from "../agent.js";
|
|
2
|
-
import type { SessionManager } from "../session.js";
|
|
2
|
+
import type { SessionManager, SessionMetadata } from "../session.js";
|
|
3
3
|
import type { Provider } from "../types.js";
|
|
4
4
|
import type { ProviderRegistry } from "../provider-registry.js";
|
|
5
5
|
import type { SkillRegistry } from "../skills/registry.js";
|
|
@@ -10,6 +10,7 @@ import type { LspService } from "../lsp/index.js";
|
|
|
10
10
|
import type { MemoryScope } from "../memory/index.js";
|
|
11
11
|
import type { ThemeMode } from "../config.js";
|
|
12
12
|
import type { ExternalHookController } from "../hooks/controller.js";
|
|
13
|
+
import type { ExternalRuntimeManager } from "../external-runtime/types.js";
|
|
13
14
|
/**
|
|
14
15
|
* Live progress for a manual `/compact` run, pushed to the TUI so it can render
|
|
15
16
|
* a progress bar. `phase` advances collecting → summarizing → applying;
|
|
@@ -61,6 +62,18 @@ export interface SlashCommandContext {
|
|
|
61
62
|
* while compacting and `null` to clear the indicator. Absent in non-TUI hosts.
|
|
62
63
|
*/
|
|
63
64
|
compactionProgress?: (progress: CompactionProgress | null) => void;
|
|
65
|
+
/** External agent runtime used by Grok subscription chat. */
|
|
66
|
+
externalRuntime?: ExternalRuntimeManager;
|
|
67
|
+
/**
|
|
68
|
+
* Atomically create and bind a fresh Bubble session with optional metadata.
|
|
69
|
+
* External runtimes are session-affine, so crossing the native/external
|
|
70
|
+
* boundary never reuses history.
|
|
71
|
+
*/
|
|
72
|
+
startFreshSession?: (metadata?: Partial<SessionMetadata>) => Promise<SessionManager>;
|
|
73
|
+
/** Stop the external runtime and atomically enter a fresh native session. */
|
|
74
|
+
transitionToNative?: () => Promise<SessionManager | undefined>;
|
|
75
|
+
/** Refresh TUI state after a command changes the external session binding. */
|
|
76
|
+
onExternalRuntimeChange?: (manager?: SessionManager) => void;
|
|
64
77
|
}
|
|
65
78
|
/**
|
|
66
79
|
* Return types for a slash command handler:
|
package/dist/system-prompt.d.ts
CHANGED
|
@@ -32,5 +32,7 @@ export interface SystemPromptOptions {
|
|
|
32
32
|
memoryPrompt?: string;
|
|
33
33
|
/** Durable child-agent profile prompt used for subagents. */
|
|
34
34
|
agentProfilePrompt?: string;
|
|
35
|
+
/** Rendered subagent model-routing menu (design §4); parents only. */
|
|
36
|
+
modelRoutingPrompt?: string;
|
|
35
37
|
}
|
|
36
38
|
export declare function buildSystemPrompt(options?: SystemPromptOptions): string;
|
|
@@ -24,15 +24,7 @@ export declare function createWaitAgentTool(): ToolRegistryEntry;
|
|
|
24
24
|
export declare function createSendInputTool(): ToolRegistryEntry;
|
|
25
25
|
export declare function createCloseAgentTool(): ToolRegistryEntry;
|
|
26
26
|
export declare function createListAgentsTool(): ToolRegistryEntry;
|
|
27
|
-
|
|
28
|
-
export declare const AGENT_TEAM_MIN_ITEMS = 2;
|
|
29
|
-
export declare const AGENT_TEAM_MAX_ITEMS = 32;
|
|
30
|
-
export declare function createAgentTeamTool(options?: AgentLifecycleToolOptions, sharedTrust?: ProjectProfileTrust): ToolRegistryEntry;
|
|
31
|
-
/** Specs bound for one agent_batch call (design v2 §1.3). */
|
|
32
|
-
export declare const AGENT_BATCH_MIN_SPECS = 2;
|
|
33
|
-
export declare const AGENT_BATCH_MAX_SPECS = 32;
|
|
34
|
-
export declare function createAgentBatchTool(options?: AgentLifecycleToolOptions, sharedTrust?: ProjectProfileTrust): ToolRegistryEntry;
|
|
35
|
-
export declare function createRunWorkflowTool(options?: AgentLifecycleToolOptions): ToolRegistryEntry;
|
|
27
|
+
export declare function createRunWorkflowTool(options?: AgentLifecycleToolOptions, sharedTrust?: ProjectProfileTrust): ToolRegistryEntry;
|
|
36
28
|
export declare function createWaitWorkflowTool(): ToolRegistryEntry;
|
|
37
29
|
export declare function createAgentLifecycleTools(options?: AgentLifecycleToolOptions): ToolRegistryEntry[];
|
|
38
30
|
export {};
|