@bubblebrain-ai/bubble 0.0.35 → 0.0.37
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/categories.d.ts +66 -2
- package/dist/agent/categories.js +120 -19
- package/dist/agent/discovery-barrier.js +1 -1
- package/dist/agent/profiles.d.ts +1 -0
- package/dist/agent/profiles.js +25 -11
- package/dist/agent/routing-catalog.d.ts +113 -0
- package/dist/agent/routing-catalog.js +240 -0
- package/dist/agent/workflow/control.d.ts +14 -0
- package/dist/agent/workflow/control.js +30 -9
- package/dist/agent.d.ts +75 -5
- package/dist/agent.js +313 -61
- 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 +4 -0
- package/dist/config.js +4 -0
- 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 +105 -27
- 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 +87 -41
- 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 +17 -7
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.js +22 -3
- 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 +19 -0
- package/dist/prompt/task-reminders.js +49 -0
- 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 +28 -5
- 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.js +9 -11
- 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 +649 -233
- package/dist/tui-ink/approval/approval-dialog.js +36 -0
- package/dist/tui-ink/composer-buffer.d.ts +55 -0
- package/dist/tui-ink/composer-buffer.js +317 -0
- 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 +35 -1
- package/dist/tui-ink/input-box.js +215 -122
- package/dist/tui-ink/message-list.js +5 -5
- package/dist/tui-ink/model-picker.d.ts +61 -4
- package/dist/tui-ink/model-picker.js +486 -133
- 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/run.d.ts +3 -0
- package/dist/tui-ink/run.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +31 -0
- package/dist/tui-ink/subagent-view.js +82 -1
- package/dist/types.d.ts +5 -5
- 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 +9 -0
- package/dist/variant/variant-resolver.js +17 -0
- package/package.json +2 -1
- package/dist/tools/task.d.ts +0 -2
- package/dist/tools/task.js +0 -58
package/dist/tui-ink/app.js
CHANGED
|
@@ -32,13 +32,13 @@ import { QuestionDialog } from "./question-dialog.js";
|
|
|
32
32
|
import { FeedbackDialog } from "./feedback-dialog.js";
|
|
33
33
|
import { collectFeedback } from "../feedback/collect.js";
|
|
34
34
|
import { isKeyReleaseEvent } from "./key-events.js";
|
|
35
|
-
import { errorMessage, formatModelSwitchError, switchAgentModel } from "../tui/model-switch.js";
|
|
35
|
+
import { errorMessage, formatModelSwitchError, modelSwitchTarget, switchAgentModel } from "../tui/model-switch.js";
|
|
36
36
|
import { formatImageUserDisplayText, nextImageDisplayLabelStart } from "../tui/image-display.js";
|
|
37
37
|
import { decideStartingSubmitFingerprint, submitPayloadFingerprint } from "./submit-dedupe.js";
|
|
38
38
|
import { isQueuedInputForCurrentSession, queuedAndPendingDisplayKeys, } from "./input-queue.js";
|
|
39
39
|
import { SessionPicker } from "./session-picker.js";
|
|
40
40
|
import { SubagentInspector } from "./subagent-inspector.js";
|
|
41
|
-
import { collectSubagentGroups, subagentSummary } from "./subagent-view.js";
|
|
41
|
+
import { accumulateLiveSubagentUpdate, collectSubagentGroups, mergeToolMetadata, pruneSettledLiveSubagentTools, subagentSummary } from "./subagent-view.js";
|
|
42
42
|
import { sessionDisplayName } from "../tui/session-display.js";
|
|
43
43
|
import { parseGoalCommand } from "../goal/command.js";
|
|
44
44
|
import { continuationPrompt, initialPrompt } from "../goal/prompts.js";
|
|
@@ -47,9 +47,21 @@ import { goalCompleteNotice, goalIndicatorLine, goalSummaryText } from "../goal/
|
|
|
47
47
|
import { tokenUsageTotal } from "../goal/usage.js";
|
|
48
48
|
import { formatInternalContextBlock } from "../agent/internal-reminder-sanitizer.js";
|
|
49
49
|
import { collectUsageStatsBundle, formatStatsPanelBody, rangeLabel } from "../stats/usage.js";
|
|
50
|
+
import { GrokRuntimeError } from "../external-runtime/grok-errors.js";
|
|
51
|
+
import { GROK_LOCAL_SLASH_COMMANDS, classifyGrokInput, isGrokLocalSlashCommand, } from "../external-runtime/grok-input-policy.js";
|
|
52
|
+
import { GROK_SUBSCRIPTION_PROVIDER_ID, isGrokSubscriptionProviderId, withGrokSubscriptionProvider, } from "../external-runtime/grok-provider.js";
|
|
53
|
+
import { classifyExternalRuntimeBinding, stopExternalRuntimeForSessionSwitch, } from "../external-runtime/session-policy.js";
|
|
50
54
|
import os from "node:os";
|
|
51
|
-
|
|
55
|
+
import { rmSync } from "node:fs";
|
|
56
|
+
function buildTips(agent, registry, grokActive = false) {
|
|
52
57
|
const tips = [];
|
|
58
|
+
if (grokActive) {
|
|
59
|
+
return [
|
|
60
|
+
"Grok Subscription is active with workspace tools and Bubble approvals",
|
|
61
|
+
"Use /model to choose the Grok model and reasoning effort",
|
|
62
|
+
"Use /logout grok to return to a fresh native session",
|
|
63
|
+
];
|
|
64
|
+
}
|
|
53
65
|
const hasProvider = registry.getEnabled().length > 0;
|
|
54
66
|
if (!hasProvider) {
|
|
55
67
|
tips.push("Run /login or /provider --add to configure a model");
|
|
@@ -189,33 +201,6 @@ function parsePartialArgs(buffer, previous) {
|
|
|
189
201
|
}
|
|
190
202
|
return result;
|
|
191
203
|
}
|
|
192
|
-
function mergeToolMetadata(current, incoming) {
|
|
193
|
-
if (!incoming)
|
|
194
|
-
return current;
|
|
195
|
-
if (current?.kind !== "subagent" || incoming.kind !== "subagent") {
|
|
196
|
-
return incoming;
|
|
197
|
-
}
|
|
198
|
-
const currentSubagents = Array.isArray(current.subagents) ? current.subagents : [];
|
|
199
|
-
const incomingSubagents = Array.isArray(incoming.subagents) ? incoming.subagents : [];
|
|
200
|
-
const byId = new Map();
|
|
201
|
-
for (const item of currentSubagents) {
|
|
202
|
-
const subAgentId = typeof item === "object" && item !== null && "subAgentId" in item
|
|
203
|
-
? String(item.subAgentId)
|
|
204
|
-
: "";
|
|
205
|
-
byId.set(subAgentId || `current:${byId.size}`, item);
|
|
206
|
-
}
|
|
207
|
-
for (const item of incomingSubagents) {
|
|
208
|
-
const subAgentId = typeof item === "object" && item !== null && "subAgentId" in item
|
|
209
|
-
? String(item.subAgentId)
|
|
210
|
-
: "";
|
|
211
|
-
byId.set(subAgentId || `incoming:${byId.size}`, item);
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
...current,
|
|
215
|
-
...incoming,
|
|
216
|
-
subagents: [...byId.values()],
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
204
|
/**
|
|
220
205
|
* Coerce a freshly-constructed DisplayMessage into one that carries a stable
|
|
221
206
|
* `key`. Centralizes the safety net so callers don't have to remember to call
|
|
@@ -238,8 +223,13 @@ export const INK_LOCAL_SLASH_COMMANDS = [
|
|
|
238
223
|
description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)",
|
|
239
224
|
},
|
|
240
225
|
];
|
|
241
|
-
export function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, onExit }) {
|
|
226
|
+
export function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, externalRuntime, onExit }) {
|
|
242
227
|
const [sessionManager, setSessionManager] = useState(initialSessionManager);
|
|
228
|
+
const [externalRuntimeBinding, setExternalRuntimeBinding] = useState(() => initialSessionManager?.getMetadata().externalRuntime);
|
|
229
|
+
const sessionManagerRef = useRef(sessionManager);
|
|
230
|
+
sessionManagerRef.current = sessionManager;
|
|
231
|
+
const externalRuntimeBindingRef = useRef(externalRuntimeBinding);
|
|
232
|
+
externalRuntimeBindingRef.current = externalRuntimeBinding;
|
|
243
233
|
const [themeMode, setThemeMode] = useState(initialThemeMode ?? "auto");
|
|
244
234
|
// `detectedTheme` is captured once at startup in main.ts. We keep it in state
|
|
245
235
|
// so future re-detection (e.g. if a user runs `/theme auto` after switching
|
|
@@ -276,9 +266,27 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
276
266
|
const [streamingReasoning, setStreamingReasoning] = useState("");
|
|
277
267
|
const [streamingTools, setStreamingTools] = useState([]);
|
|
278
268
|
const [streamingParts, setStreamingParts] = useState([]);
|
|
269
|
+
// Background-child updates that outlived their launching tool call's
|
|
270
|
+
// streaming round (toolCalls is cleared on every turn_start): keyed by the
|
|
271
|
+
// launching call id, they feed the inspector as synthetic tool calls so
|
|
272
|
+
// traces stay live e.g. while a wait_workflow blocks. The version counter
|
|
273
|
+
// triggers recompute since the ref mutates in place.
|
|
274
|
+
const liveSubagentToolsRef = useRef(new Map());
|
|
275
|
+
const [liveSubagentVersion, setLiveSubagentVersion] = useState(0);
|
|
276
|
+
// Transcript-reset paths (/clear, session switch, /rewind) must drop the
|
|
277
|
+
// accumulator too, or ghost subagent groups from the wiped conversation keep
|
|
278
|
+
// feeding the entry line and inspector for the rest of the process.
|
|
279
|
+
const clearLiveSubagentTools = useCallback(() => {
|
|
280
|
+
if (liveSubagentToolsRef.current.size === 0)
|
|
281
|
+
return;
|
|
282
|
+
liveSubagentToolsRef.current.clear();
|
|
283
|
+
setLiveSubagentVersion((version) => version + 1);
|
|
284
|
+
}, []);
|
|
279
285
|
// Live subagent groups for the inspector opened from the subagent entry line;
|
|
280
286
|
// recomputed each render so it reflects members as their events stream into the transcript.
|
|
281
|
-
const subagentGroups = useMemo(() => collectSubagentGroups(messages, streamingTools
|
|
287
|
+
const subagentGroups = useMemo(() => collectSubagentGroups(messages, [...streamingTools, ...liveSubagentToolsRef.current.values()]),
|
|
288
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
289
|
+
[messages, streamingTools, liveSubagentVersion]);
|
|
282
290
|
const subagentMembers = useMemo(() => subagentGroups.flatMap((g) => g.members), [subagentGroups]);
|
|
283
291
|
// Down-arrow from the composer focuses the subagent entry line; Enter then
|
|
284
292
|
// opens the inspector, Esc/Up returns to the composer (Claude Code parity).
|
|
@@ -310,6 +318,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
310
318
|
const [pendingQuestion, setPendingQuestion] = useState(null);
|
|
311
319
|
const [pendingFeedback, setPendingFeedback] = useState(null);
|
|
312
320
|
const [pickerMode, setPickerMode] = useState(null);
|
|
321
|
+
const [grokModels, setGrokModels] = useState([]);
|
|
313
322
|
const [statsPanel, setStatsPanel] = useState(null);
|
|
314
323
|
const [cursorResetEpoch, setCursorResetEpoch] = useState(0);
|
|
315
324
|
const [composerDraft, setComposerDraft] = useState(null);
|
|
@@ -333,7 +342,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
333
342
|
const [staticGeneration, setStaticGeneration] = useState(0);
|
|
334
343
|
const reprintTranscript = useCallback(() => {
|
|
335
344
|
if (process.stdout.isTTY) {
|
|
336
|
-
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
345
|
+
process.stdout.write("\x1b[0m\x1b[2J\x1b[3J\x1b[H");
|
|
337
346
|
}
|
|
338
347
|
setStaticGeneration((generation) => generation + 1);
|
|
339
348
|
}, []);
|
|
@@ -349,6 +358,9 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
349
358
|
const startingSubmitFingerprintRef = useRef(null);
|
|
350
359
|
const [startingSubmitFingerprint, setStartingSubmitFingerprint] = useState(null);
|
|
351
360
|
const nextRunIdRef = useRef(0);
|
|
361
|
+
// Invalidates late external-runtime events whenever the Bubble session or
|
|
362
|
+
// runtime binding changes. Native Agent events retain their existing path.
|
|
363
|
+
const externalRuntimeGenerationRef = useRef(0);
|
|
352
364
|
// Set true the moment /quit is invoked so we can hide dynamic UI (composer,
|
|
353
365
|
// waiting indicator, footer) before Ink snapshots its final frame into the
|
|
354
366
|
// shell scrollback. Without this, the last visible "> " input row stays
|
|
@@ -374,6 +386,15 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
374
386
|
cwd: args.cwd,
|
|
375
387
|
skillPaths: userConfig.getSkillPaths(),
|
|
376
388
|
});
|
|
389
|
+
const externalRuntimeBindingKind = classifyExternalRuntimeBinding(externalRuntimeBinding);
|
|
390
|
+
const externalSessionBound = externalRuntimeBindingKind !== "none";
|
|
391
|
+
const grokSessionBound = externalRuntimeBindingKind === "grok";
|
|
392
|
+
const unsupportedExternalSessionBound = externalRuntimeBindingKind === "unsupported";
|
|
393
|
+
const refreshExternalRuntimeBinding = useCallback((manager) => {
|
|
394
|
+
const next = (manager ?? sessionManagerRef.current)?.getMetadata().externalRuntime;
|
|
395
|
+
externalRuntimeBindingRef.current = next;
|
|
396
|
+
setExternalRuntimeBinding(next);
|
|
397
|
+
}, []);
|
|
377
398
|
useEffect(() => {
|
|
378
399
|
setCurrentUpdateNotice(updateNotice);
|
|
379
400
|
}, [updateNotice]);
|
|
@@ -572,6 +593,10 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
572
593
|
return;
|
|
573
594
|
}
|
|
574
595
|
if (key.ctrl && key.shift && input.toLowerCase() === "m" && !pickerMode) {
|
|
596
|
+
if (externalSessionBound) {
|
|
597
|
+
addMessage("assistant", "MCP is unavailable in external runtime sessions (no Bubble workspace access).");
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
575
600
|
if (!mcpManager || mcpManager.getStates().length === 0) {
|
|
576
601
|
addMessage("assistant", "No MCP servers configured.");
|
|
577
602
|
}
|
|
@@ -596,6 +621,35 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
596
621
|
}
|
|
597
622
|
// Ctrl+R: cycle thinking level (formerly Shift+Tab)
|
|
598
623
|
if (isCtrlLetterInput(input, key, "r") && !pickerMode) {
|
|
624
|
+
if (externalSessionBound) {
|
|
625
|
+
if (!grokSessionBound || !externalRuntime) {
|
|
626
|
+
addMessage("assistant", "The external runtime manages its own model and reasoning settings.");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const bound = externalRuntimeBindingRef.current;
|
|
630
|
+
void (bound?.sessionId
|
|
631
|
+
? externalRuntime.hydrateSession(bound.sessionId, bound.modelId, bound.reasoningEffort)
|
|
632
|
+
: Promise.resolve()).then(async () => await externalRuntime.listModels()).then(async (models) => {
|
|
633
|
+
const current = externalRuntime.getModelSelection();
|
|
634
|
+
const model = models.find((candidate) => candidate.id === current.modelId);
|
|
635
|
+
if (!model || model.reasoningLevels.length < 2) {
|
|
636
|
+
addMessage("assistant", `${current.modelId ?? "The current Grok model"} has no alternate reasoning effort.`);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const index = Math.max(0, model.reasoningLevels.indexOf(current.reasoningEffort));
|
|
640
|
+
const next = model.reasoningLevels[(index + 1) % model.reasoningLevels.length];
|
|
641
|
+
const selection = await externalRuntime.setModel(model.id, next);
|
|
642
|
+
const manager = sessionManagerRef.current;
|
|
643
|
+
const binding = manager?.getMetadata().externalRuntime;
|
|
644
|
+
if (manager && binding && isGrokSubscriptionProviderId(binding.id)) {
|
|
645
|
+
manager.updateMetadata({ externalRuntime: { ...binding, ...selection } });
|
|
646
|
+
manager.appendMarker("thinking_level_switch", selection.reasoningEffort);
|
|
647
|
+
refreshExternalRuntimeBinding(manager);
|
|
648
|
+
}
|
|
649
|
+
addMessage("assistant", `Grok reasoning effort set to ${selection.reasoningEffort}.`);
|
|
650
|
+
}).catch((error) => addMessage("error", `Failed to switch Grok reasoning: ${errorMessage(error)}`));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
599
653
|
const modelParts = agent.model.includes(":")
|
|
600
654
|
? agent.model.split(":")
|
|
601
655
|
: [agent.providerId || safeRegistry.getDefault()?.id || "openai", agent.model];
|
|
@@ -689,7 +743,8 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
689
743
|
// messages alone leaves a stale To-Do list on screen. /clear already reset
|
|
690
744
|
// the agent's todos; mirror that into the UI (same as session switch).
|
|
691
745
|
setTodos(agent.getTodos());
|
|
692
|
-
|
|
746
|
+
clearLiveSubagentTools();
|
|
747
|
+
}, [resetTranscript, agent, clearLiveSubagentTools]);
|
|
693
748
|
// Render a placeholder user row for input waiting to enter the run.
|
|
694
749
|
const addStatusUserMessage = useCallback((content, status) => {
|
|
695
750
|
const key = nextDisplayMessageKey("user");
|
|
@@ -708,7 +763,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
708
763
|
return { ...payload, imageDisplayStart };
|
|
709
764
|
}, []);
|
|
710
765
|
const submitDisplayText = useCallback((payload) => (formatImageUserDisplayText(payload.displayText ?? payload.text, payload.images.length, payload.imageDisplayStart)), []);
|
|
711
|
-
const currentSessionFile = useCallback(() =>
|
|
766
|
+
const currentSessionFile = useCallback(() => sessionManagerRef.current?.getSessionFile(), []);
|
|
712
767
|
const queueInput = useCallback((payload) => {
|
|
713
768
|
const preparedPayload = prepareSubmitDisplay(payload);
|
|
714
769
|
const displayKey = addStatusUserMessage(submitDisplayText(preparedPayload), "queued");
|
|
@@ -728,6 +783,19 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
728
783
|
setPendingSteerCount(pendingSteersRef.current.size);
|
|
729
784
|
}, [addStatusUserMessage, currentSessionFile, prepareSubmitDisplay, queueInput, submitDisplayText]);
|
|
730
785
|
const openPicker = useCallback((mode, providerId) => {
|
|
786
|
+
if (mode === "model" && grokSessionBound && externalRuntime) {
|
|
787
|
+
setStatsPanel(null);
|
|
788
|
+
setGrokModels([]);
|
|
789
|
+
setPickerMode("grok-model");
|
|
790
|
+
const bound = externalRuntimeBindingRef.current;
|
|
791
|
+
void (bound?.sessionId
|
|
792
|
+
? externalRuntime.hydrateSession(bound.sessionId, bound.modelId, bound.reasoningEffort)
|
|
793
|
+
: Promise.resolve()).then(async () => await externalRuntime.listModels()).then(setGrokModels).catch((error) => {
|
|
794
|
+
addMessage("error", `Failed to load Grok models: ${errorMessage(error)}`);
|
|
795
|
+
setPickerMode(null);
|
|
796
|
+
});
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
731
799
|
if (mode === "key") {
|
|
732
800
|
setKeyProviderId(providerId ?? null);
|
|
733
801
|
}
|
|
@@ -736,11 +804,36 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
736
804
|
}
|
|
737
805
|
setStatsPanel(null);
|
|
738
806
|
setPickerMode(mode);
|
|
739
|
-
}, []);
|
|
807
|
+
}, [addMessage, externalRuntime, grokSessionBound]);
|
|
740
808
|
const closePicker = useCallback(() => {
|
|
741
809
|
setPickerMode(null);
|
|
742
810
|
setCursorResetEpoch((epoch) => epoch + 1);
|
|
743
811
|
}, []);
|
|
812
|
+
const handleGrokModelSelect = useCallback((encoded) => {
|
|
813
|
+
if (!externalRuntime)
|
|
814
|
+
return;
|
|
815
|
+
const run = async () => {
|
|
816
|
+
const parsed = JSON.parse(encoded);
|
|
817
|
+
const boundSessionId = externalRuntimeBindingRef.current?.sessionId;
|
|
818
|
+
if (boundSessionId)
|
|
819
|
+
await externalRuntime.hydrateSession(boundSessionId);
|
|
820
|
+
const selection = await externalRuntime.setModel(parsed.modelId, parsed.reasoningEffort);
|
|
821
|
+
const manager = sessionManagerRef.current;
|
|
822
|
+
const binding = manager?.getMetadata().externalRuntime;
|
|
823
|
+
if (manager && binding && isGrokSubscriptionProviderId(binding.id)) {
|
|
824
|
+
manager.updateMetadata({ externalRuntime: { ...binding, ...selection } });
|
|
825
|
+
manager.appendMarker("model_switch", selection.modelId ?? parsed.modelId);
|
|
826
|
+
manager.appendMarker("thinking_level_switch", selection.reasoningEffort);
|
|
827
|
+
refreshExternalRuntimeBinding(manager);
|
|
828
|
+
}
|
|
829
|
+
addMessage("assistant", `Grok model switched to ${selection.modelId ?? parsed.modelId}${selection.reasoningEffort !== "off" ? ` (${selection.reasoningEffort})` : ""}.`);
|
|
830
|
+
closePicker();
|
|
831
|
+
};
|
|
832
|
+
void run().catch((error) => {
|
|
833
|
+
addMessage("error", `Failed to switch Grok model: ${errorMessage(error)}`);
|
|
834
|
+
closePicker();
|
|
835
|
+
});
|
|
836
|
+
}, [addMessage, closePicker, externalRuntime, refreshExternalRuntimeBinding]);
|
|
744
837
|
const fillComposer = useCallback((text) => {
|
|
745
838
|
setComposerDraft((current) => ({
|
|
746
839
|
text,
|
|
@@ -790,23 +883,8 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
790
883
|
setStatsPanel(null);
|
|
791
884
|
setCursorResetEpoch((epoch) => epoch + 1);
|
|
792
885
|
}, []);
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
addMessage("error", "Session switching is not available in this mode.");
|
|
796
|
-
closePicker();
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
if (activeAbortRef.current) {
|
|
800
|
-
addMessage("error", "Stop the current run before switching sessions.");
|
|
801
|
-
closePicker();
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
const result = switchSession(sessionFile);
|
|
805
|
-
if ("error" in result) {
|
|
806
|
-
addMessage("error", `Failed to switch session: ${result.error}`);
|
|
807
|
-
closePicker();
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
886
|
+
const applySessionSwitch = useCallback((result, notice) => {
|
|
887
|
+
externalRuntimeGenerationRef.current += 1;
|
|
810
888
|
const queuedDisplayKeys = queuedAndPendingDisplayKeys(queuedInputsRef.current, pendingSteersRef.current.values());
|
|
811
889
|
queuedInputsRef.current = [];
|
|
812
890
|
pendingSteersRef.current.clear();
|
|
@@ -815,27 +893,122 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
815
893
|
setPendingSteerCount(0);
|
|
816
894
|
setStartingSubmit(null);
|
|
817
895
|
clearComposerDraft();
|
|
896
|
+
sessionManagerRef.current = result.manager;
|
|
818
897
|
setSessionManager(result.manager);
|
|
898
|
+
const nextExternalBinding = result.manager.getMetadata().externalRuntime;
|
|
899
|
+
externalRuntimeBindingRef.current = nextExternalBinding;
|
|
900
|
+
setExternalRuntimeBinding(nextExternalBinding);
|
|
819
901
|
setTodos(agent.getTodos());
|
|
902
|
+
clearLiveSubagentTools();
|
|
820
903
|
resetTranscript(() => [
|
|
821
904
|
...reconstructDisplayMessages(agent.messages).filter((message) => !queuedDisplayKeys.has(message.key ?? "")),
|
|
822
|
-
withMessageKey({ role: "assistant", content:
|
|
905
|
+
...(notice ? [withMessageKey({ role: "assistant", content: notice })] : []),
|
|
823
906
|
]);
|
|
824
907
|
closePicker();
|
|
825
|
-
|
|
908
|
+
return result.manager;
|
|
909
|
+
}, [agent, clearComposerDraft, clearLiveSubagentTools, closePicker, resetTranscript, setStartingSubmit]);
|
|
910
|
+
const prepareFreshSession = useCallback((metadata) => {
|
|
911
|
+
const fresh = SessionManager.createFresh(args.cwd);
|
|
912
|
+
try {
|
|
913
|
+
if (metadata && Object.keys(metadata).length > 0) {
|
|
914
|
+
fresh.updateMetadata(metadata);
|
|
915
|
+
}
|
|
916
|
+
return fresh;
|
|
917
|
+
}
|
|
918
|
+
catch (error) {
|
|
919
|
+
rmSync(fresh.getSessionFile(), { force: true });
|
|
920
|
+
throw error;
|
|
921
|
+
}
|
|
922
|
+
}, [args.cwd]);
|
|
923
|
+
const commitFreshSession = useCallback((fresh) => {
|
|
924
|
+
if (!switchSession) {
|
|
925
|
+
rmSync(fresh.getSessionFile(), { force: true });
|
|
926
|
+
throw new Error("Starting a fresh session is not available in this mode.");
|
|
927
|
+
}
|
|
928
|
+
const result = switchSession(fresh.getSessionFile());
|
|
929
|
+
if ("error" in result) {
|
|
930
|
+
rmSync(fresh.getSessionFile(), { force: true });
|
|
931
|
+
throw new Error(result.error);
|
|
932
|
+
}
|
|
933
|
+
return applySessionSwitch(result);
|
|
934
|
+
}, [applySessionSwitch, switchSession]);
|
|
935
|
+
const startFreshSession = useCallback(async (metadata) => {
|
|
936
|
+
return commitFreshSession(prepareFreshSession(metadata));
|
|
937
|
+
}, [commitFreshSession, prepareFreshSession]);
|
|
938
|
+
const transitionToNative = useCallback(async () => {
|
|
939
|
+
if (!externalSessionBound)
|
|
940
|
+
return sessionManagerRef.current;
|
|
941
|
+
const candidate = prepareFreshSession();
|
|
942
|
+
externalRuntimeGenerationRef.current += 1;
|
|
943
|
+
const boundSessionId = externalRuntimeBinding?.sessionId;
|
|
944
|
+
try {
|
|
945
|
+
// Validate/prepare the native provider before calling this function.
|
|
946
|
+
// The external sidecar must be fully stopped before the new Bubble
|
|
947
|
+
// session is committed. If commit then fails, the old binding remains
|
|
948
|
+
// authoritative and the reusable manager will lazy-load it next time.
|
|
949
|
+
await stopExternalRuntimeForSessionSwitch(externalRuntime, boundSessionId);
|
|
950
|
+
return commitFreshSession(candidate);
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
rmSync(candidate.getSessionFile(), { force: true });
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
}, [commitFreshSession, externalRuntime, externalRuntimeBinding?.sessionId, externalSessionBound, prepareFreshSession]);
|
|
957
|
+
const handleSessionSelect = useCallback((sessionFile) => {
|
|
958
|
+
const run = async () => {
|
|
959
|
+
if (!switchSession) {
|
|
960
|
+
addMessage("error", "Session switching is not available in this mode.");
|
|
961
|
+
closePicker();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (activeAbortRef.current) {
|
|
965
|
+
addMessage("error", "Stop the current run before switching sessions.");
|
|
966
|
+
closePicker();
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (externalSessionBound) {
|
|
970
|
+
externalRuntimeGenerationRef.current += 1;
|
|
971
|
+
const boundSessionId = externalRuntimeBinding?.sessionId;
|
|
972
|
+
await stopExternalRuntimeForSessionSwitch(externalRuntime, boundSessionId);
|
|
973
|
+
}
|
|
974
|
+
const result = switchSession(sessionFile);
|
|
975
|
+
if ("error" in result) {
|
|
976
|
+
addMessage("error", `Failed to switch session: ${result.error}`);
|
|
977
|
+
closePicker();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
applySessionSwitch(result, `⤷ Resumed session: ${sessionDisplayName(result.manager)}`);
|
|
981
|
+
};
|
|
982
|
+
void run().catch((error) => {
|
|
983
|
+
addMessage("error", `Failed to switch session: ${errorMessage(error)}`);
|
|
984
|
+
closePicker();
|
|
985
|
+
});
|
|
986
|
+
}, [activeAbortRef, addMessage, applySessionSwitch, closePicker, externalRuntime, externalRuntimeBinding?.sessionId, externalSessionBound, switchSession]);
|
|
826
987
|
const handleModelSelect = useCallback((model, selectedThinkingLevel) => {
|
|
827
988
|
const run = async () => {
|
|
989
|
+
const target = modelSwitchTarget(model, agent.providerId || safeRegistry.getDefault()?.id);
|
|
990
|
+
await safeRegistry.prepareProvider(target.providerId);
|
|
991
|
+
const targetProvider = safeRegistry.getConfigured().find((item) => item.id === target.providerId);
|
|
992
|
+
if (!targetProvider?.apiKey || !createProvider) {
|
|
993
|
+
throw new Error(`Provider ${target.providerId} is not configured or has no active credentials.`);
|
|
994
|
+
}
|
|
995
|
+
// Construct once as a side-effect-free preflight before stopping Grok.
|
|
996
|
+
// switchAgentModel constructs the committed instance after the fresh
|
|
997
|
+
// native session has been installed.
|
|
998
|
+
const preparedProvider = createProvider(target.providerId, targetProvider.apiKey, targetProvider.baseURL);
|
|
999
|
+
const targetSessionManager = await transitionToNative();
|
|
828
1000
|
const nextThinkingLevel = await switchAgentModel({
|
|
829
1001
|
model,
|
|
830
1002
|
agent,
|
|
831
1003
|
registry: safeRegistry,
|
|
832
1004
|
createProvider,
|
|
1005
|
+
preparedProvider,
|
|
833
1006
|
workingDir: args.cwd,
|
|
834
1007
|
systemPromptOptions: agent.getSystemPromptToolOptions(),
|
|
835
1008
|
thinkingLevel: selectedThinkingLevel,
|
|
836
1009
|
rememberModel: (nextModel) => userConfig.pushRecentModel(nextModel),
|
|
837
1010
|
setThinkingLevel,
|
|
838
|
-
sessionManager,
|
|
1011
|
+
sessionManager: targetSessionManager,
|
|
839
1012
|
});
|
|
840
1013
|
// Binary thinking toggles and thinking-only models use internal level
|
|
841
1014
|
// placeholders; do not present those placeholders as real effort grades.
|
|
@@ -854,7 +1027,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
854
1027
|
addMessage("error", formatModelSwitchError(model, error));
|
|
855
1028
|
closePicker();
|
|
856
1029
|
});
|
|
857
|
-
}, [agent, addMessage, closePicker, sessionManager, userConfig, safeRegistry, createProvider]);
|
|
1030
|
+
}, [agent, addMessage, closePicker, sessionManager, transitionToNative, userConfig, safeRegistry, createProvider]);
|
|
858
1031
|
const handleThemeHighlight = useCallback((mode) => {
|
|
859
1032
|
setThemeMode(mode);
|
|
860
1033
|
}, []);
|
|
@@ -868,58 +1041,15 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
868
1041
|
setThemeMode(themePickerRevertRef.current);
|
|
869
1042
|
closePicker();
|
|
870
1043
|
}, [closePicker]);
|
|
871
|
-
const
|
|
872
|
-
const run = async () => {
|
|
873
|
-
await safeRegistry.prepareProvider(providerId);
|
|
874
|
-
const configured = safeRegistry.getConfigured();
|
|
875
|
-
const p = configured.find((x) => x.id === providerId);
|
|
876
|
-
const builtin = BUILTIN_PROVIDERS.find((x) => x.id === providerId);
|
|
877
|
-
if (!p && !builtin) {
|
|
878
|
-
addMessage("error", `Provider ${providerId} not found.`);
|
|
879
|
-
closePicker();
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
if (!p?.apiKey) {
|
|
883
|
-
if (!p && builtin) {
|
|
884
|
-
safeRegistry.addProvider(providerId, "");
|
|
885
|
-
}
|
|
886
|
-
safeRegistry.setDefault(providerId);
|
|
887
|
-
setKeyProviderId(providerId);
|
|
888
|
-
setPickerMode("key");
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
safeRegistry.setDefault(providerId);
|
|
892
|
-
agent.setProvider(createProvider(providerId, p.apiKey, p.baseURL));
|
|
893
|
-
agent.providerId = providerId;
|
|
894
|
-
addMessage("assistant", `Switched to provider ${p.name}. Use /model to pick a model.`);
|
|
895
|
-
closePicker();
|
|
896
|
-
};
|
|
897
|
-
void run().catch((error) => {
|
|
898
|
-
addMessage("error", `Failed to switch provider ${providerId}: ${errorMessage(error)}`);
|
|
899
|
-
closePicker();
|
|
900
|
-
});
|
|
901
|
-
}, [addMessage, agent, closePicker, createProvider, safeRegistry]);
|
|
902
|
-
const handleProviderAddSelect = useCallback((providerId) => {
|
|
903
|
-
const ok = safeRegistry.addProvider(providerId, "");
|
|
904
|
-
if (!ok) {
|
|
905
|
-
addMessage("error", `Provider ${providerId} could not be added.`);
|
|
906
|
-
closePicker();
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
safeRegistry.setDefault(providerId);
|
|
910
|
-
setKeyProviderId(providerId);
|
|
911
|
-
setPickerMode("key");
|
|
912
|
-
}, [addMessage, closePicker, safeRegistry]);
|
|
913
|
-
const handleLoginProviderSelect = useCallback(async (providerId) => {
|
|
1044
|
+
const executeProviderCommand = useCallback(async (command) => {
|
|
914
1045
|
closePicker();
|
|
915
|
-
const command = `/login ${providerId}`;
|
|
916
1046
|
const { handled, result } = await slashRegistry.execute(command, {
|
|
917
1047
|
agent,
|
|
918
1048
|
addMessage,
|
|
919
1049
|
clearMessages,
|
|
920
1050
|
cwd: args.cwd,
|
|
921
|
-
exit:
|
|
922
|
-
sessionManager,
|
|
1051
|
+
exit: requestExit,
|
|
1052
|
+
sessionManager: sessionManagerRef.current,
|
|
923
1053
|
createProvider: createProvider ?? (() => {
|
|
924
1054
|
throw new Error("Provider creation not available");
|
|
925
1055
|
}),
|
|
@@ -943,67 +1073,157 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
943
1073
|
getResolvedTheme: () => themeResolved,
|
|
944
1074
|
setThemeMode: applyThemeMode,
|
|
945
1075
|
openStats: openStatsPanel,
|
|
1076
|
+
externalRuntime,
|
|
1077
|
+
startFreshSession,
|
|
1078
|
+
transitionToNative,
|
|
1079
|
+
onExternalRuntimeChange: refreshExternalRuntimeBinding,
|
|
946
1080
|
});
|
|
947
|
-
if (handled && result)
|
|
1081
|
+
if (handled && result)
|
|
948
1082
|
addMessage("assistant", result);
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1083
|
+
}, [
|
|
1084
|
+
addMessage,
|
|
1085
|
+
agent,
|
|
1086
|
+
applyThemeMode,
|
|
1087
|
+
args.cwd,
|
|
1088
|
+
bashAllowlist,
|
|
1089
|
+
clearMessages,
|
|
1090
|
+
closePicker,
|
|
1091
|
+
createProvider,
|
|
1092
|
+
externalRuntime,
|
|
1093
|
+
fillComposer,
|
|
1094
|
+
flushMemory,
|
|
1095
|
+
hookController,
|
|
1096
|
+
lspService,
|
|
1097
|
+
mcpManager,
|
|
1098
|
+
openFeedback,
|
|
1099
|
+
openPicker,
|
|
1100
|
+
openRewindPicker,
|
|
1101
|
+
openSessionPicker,
|
|
1102
|
+
openStatsPanel,
|
|
1103
|
+
refreshExternalRuntimeBinding,
|
|
1104
|
+
requestExit,
|
|
1105
|
+
runMemoryCompaction,
|
|
1106
|
+
runMemoryRefresh,
|
|
1107
|
+
runMemorySummary,
|
|
1108
|
+
safeRegistry,
|
|
1109
|
+
safeSkillRegistry,
|
|
1110
|
+
settingsManager,
|
|
1111
|
+
startFreshSession,
|
|
1112
|
+
themeMode,
|
|
1113
|
+
themeResolved,
|
|
1114
|
+
transitionToNative,
|
|
1115
|
+
]);
|
|
1116
|
+
const handleProviderSelect = useCallback((providerId) => {
|
|
1117
|
+
const run = async () => {
|
|
1118
|
+
if (isGrokSubscriptionProviderId(providerId)) {
|
|
1119
|
+
if (grokSessionBound) {
|
|
1120
|
+
closePicker();
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
await executeProviderCommand(`/provider --set ${GROK_SUBSCRIPTION_PROVIDER_ID}`);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
await safeRegistry.prepareProvider(providerId);
|
|
1127
|
+
const configured = safeRegistry.getConfigured();
|
|
1128
|
+
const p = configured.find((x) => x.id === providerId);
|
|
1129
|
+
const builtin = BUILTIN_PROVIDERS.find((x) => x.id === providerId);
|
|
1130
|
+
if (!p && !builtin) {
|
|
1131
|
+
addMessage("error", `Provider ${providerId} not found.`);
|
|
1132
|
+
closePicker();
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (!p?.apiKey) {
|
|
1136
|
+
if (!p && builtin) {
|
|
1137
|
+
safeRegistry.addProvider(providerId, "");
|
|
1138
|
+
}
|
|
1139
|
+
setKeyProviderId(providerId);
|
|
1140
|
+
setPickerMode("key");
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (!createProvider)
|
|
962
1144
|
throw new Error("Provider creation not available");
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
getResolvedTheme: () => themeResolved,
|
|
982
|
-
setThemeMode: applyThemeMode,
|
|
983
|
-
openStats: openStatsPanel,
|
|
1145
|
+
// Construct the target before stopping a Grok sidecar. A bad provider
|
|
1146
|
+
// configuration must leave the current external session untouched.
|
|
1147
|
+
const nextProvider = createProvider(providerId, p.apiKey, p.baseURL);
|
|
1148
|
+
await transitionToNative();
|
|
1149
|
+
agent.setProvider(nextProvider);
|
|
1150
|
+
agent.providerId = providerId;
|
|
1151
|
+
try {
|
|
1152
|
+
safeRegistry.setDefault(providerId);
|
|
1153
|
+
}
|
|
1154
|
+
catch (error) {
|
|
1155
|
+
addMessage("error", `Switched providers, but could not remember the default: ${errorMessage(error)}`);
|
|
1156
|
+
}
|
|
1157
|
+
addMessage("assistant", `Switched to provider ${p.name}. Use /model to pick a model.`);
|
|
1158
|
+
closePicker();
|
|
1159
|
+
};
|
|
1160
|
+
void run().catch((error) => {
|
|
1161
|
+
addMessage("error", `Failed to switch provider ${providerId}: ${errorMessage(error)}`);
|
|
1162
|
+
closePicker();
|
|
984
1163
|
});
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1164
|
+
}, [addMessage, agent, closePicker, createProvider, executeProviderCommand, grokSessionBound, safeRegistry, transitionToNative]);
|
|
1165
|
+
const handleProviderAddSelect = useCallback((providerId) => {
|
|
1166
|
+
const run = async () => {
|
|
1167
|
+
if (isGrokSubscriptionProviderId(providerId)) {
|
|
1168
|
+
await executeProviderCommand(`/provider --add ${GROK_SUBSCRIPTION_PROVIDER_ID}`);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const ok = safeRegistry.addProvider(providerId, "");
|
|
1172
|
+
if (!ok) {
|
|
1173
|
+
addMessage("error", `Provider ${providerId} could not be added.`);
|
|
1174
|
+
closePicker();
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
setKeyProviderId(providerId);
|
|
1178
|
+
setPickerMode("key");
|
|
1179
|
+
};
|
|
1180
|
+
void run().catch((error) => {
|
|
1181
|
+
addMessage("error", `Failed to add provider ${providerId}: ${errorMessage(error)}`);
|
|
1182
|
+
closePicker();
|
|
1183
|
+
});
|
|
1184
|
+
}, [addMessage, closePicker, executeProviderCommand, safeRegistry]);
|
|
1185
|
+
const handleLoginProviderSelect = useCallback((providerId) => {
|
|
1186
|
+
void executeProviderCommand(`/login ${providerId}`).catch((error) => {
|
|
1187
|
+
addMessage("error", `Failed to log in to ${providerId}: ${errorMessage(error)}`);
|
|
1188
|
+
});
|
|
1189
|
+
}, [addMessage, executeProviderCommand]);
|
|
1190
|
+
const handleLogoutProviderSelect = useCallback((providerId) => {
|
|
1191
|
+
void executeProviderCommand(`/logout ${providerId}`).catch((error) => {
|
|
1192
|
+
addMessage("error", `Failed to log out of ${providerId}: ${errorMessage(error)}`);
|
|
1193
|
+
});
|
|
1194
|
+
}, [addMessage, executeProviderCommand]);
|
|
989
1195
|
const handleKeySubmit = useCallback((key) => {
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
1196
|
+
const run = async () => {
|
|
1197
|
+
const targetId = keyProviderId || safeRegistry.getDefault()?.id;
|
|
1198
|
+
if (!targetId) {
|
|
1199
|
+
addMessage("error", "No provider selected.");
|
|
1200
|
+
closePicker();
|
|
1201
|
+
setKeyProviderId(null);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
safeRegistry.updateProviderKey(targetId, key);
|
|
1205
|
+
const p = safeRegistry.getConfigured().find((x) => x.id === targetId);
|
|
1206
|
+
if (p && createProvider) {
|
|
1207
|
+
const nextProvider = createProvider(targetId, key, p.baseURL);
|
|
1208
|
+
await transitionToNative();
|
|
1209
|
+
agent.setProvider(nextProvider);
|
|
1210
|
+
agent.providerId = targetId;
|
|
1211
|
+
try {
|
|
1212
|
+
safeRegistry.setDefault(targetId);
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
addMessage("error", `API key saved, but could not remember the default: ${errorMessage(error)}`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
addMessage("assistant", `API key updated for ${p?.name || targetId} to ${maskKey(key)}.`);
|
|
993
1219
|
closePicker();
|
|
994
1220
|
setKeyProviderId(null);
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
agent.providerId = targetId;
|
|
1002
|
-
}
|
|
1003
|
-
addMessage("assistant", `API key updated for ${p?.name || targetId} to ${maskKey(key)}.`);
|
|
1004
|
-
closePicker();
|
|
1005
|
-
setKeyProviderId(null);
|
|
1006
|
-
}, [addMessage, agent, closePicker, createProvider, keyProviderId, safeRegistry]);
|
|
1221
|
+
};
|
|
1222
|
+
void run().catch((error) => {
|
|
1223
|
+
addMessage("error", `Failed to update provider key: ${errorMessage(error)}`);
|
|
1224
|
+
closePicker();
|
|
1225
|
+
});
|
|
1226
|
+
}, [addMessage, agent, closePicker, createProvider, keyProviderId, safeRegistry, transitionToNative]);
|
|
1007
1227
|
const handleSubmit = useCallback(async (payload) => {
|
|
1008
1228
|
const initialPayload = typeof payload === "string" ? { text: payload, images: [] } : payload;
|
|
1009
1229
|
const input = initialPayload.text;
|
|
@@ -1019,6 +1239,13 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1019
1239
|
requestExit();
|
|
1020
1240
|
return;
|
|
1021
1241
|
}
|
|
1242
|
+
if (externalSessionBound) {
|
|
1243
|
+
// External ACP turns do not support Bubble's boundary steer
|
|
1244
|
+
// protocol. Preserve ordering by sending every additional input to
|
|
1245
|
+
// the next turn instead.
|
|
1246
|
+
queueInput(initialPayload);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1022
1249
|
const steerEligible = !displayInput.trim().startsWith("/") &&
|
|
1023
1250
|
!input.includes("@") &&
|
|
1024
1251
|
images.length === 0;
|
|
@@ -1030,6 +1257,21 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1030
1257
|
}
|
|
1031
1258
|
return;
|
|
1032
1259
|
}
|
|
1260
|
+
const grokInputDecision = externalSessionBound
|
|
1261
|
+
? classifyGrokInput({ text: input, imageCount: images.length })
|
|
1262
|
+
: undefined;
|
|
1263
|
+
if (grokInputDecision?.kind === "blocked") {
|
|
1264
|
+
addMessage("error", grokInputDecision.message);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (grokInputDecision?.kind === "prompt" && unsupportedExternalSessionBound) {
|
|
1268
|
+
addMessage("error", "This session has an unsupported external runtime binding. Native tools and providers are disabled; use /session, /provider, or /model to enter a fresh native session.");
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (grokInputDecision?.kind === "prompt" && grokSessionBound && !externalRuntime) {
|
|
1272
|
+
addMessage("error", "This session belongs to Grok Subscription, but its runtime is unavailable in this TUI. Use /model, /provider, or /session to enter a fresh native session.");
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1033
1275
|
const submitFingerprint = submitPayloadFingerprint(initialPayload);
|
|
1034
1276
|
const startingDecision = decideStartingSubmitFingerprint(startingSubmitFingerprintRef.current, submitFingerprint);
|
|
1035
1277
|
if (startingDecision === "ignore")
|
|
@@ -1043,23 +1285,50 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1043
1285
|
try {
|
|
1044
1286
|
const runAgentInput = async (actualInput, displayInput, attachedImages = [], runOptions = {}) => {
|
|
1045
1287
|
const runSessionFile = currentSessionFile();
|
|
1046
|
-
const
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1288
|
+
const runSessionManager = sessionManagerRef.current;
|
|
1289
|
+
const runExternalRuntime = isGrokSubscriptionProviderId(externalRuntimeBindingRef.current?.id);
|
|
1290
|
+
const externalSessionId = runExternalRuntime
|
|
1291
|
+
? externalRuntimeBindingRef.current?.sessionId
|
|
1292
|
+
: undefined;
|
|
1293
|
+
const externalModelId = runExternalRuntime
|
|
1294
|
+
? externalRuntimeBindingRef.current?.modelId ?? "subscription-default"
|
|
1295
|
+
: undefined;
|
|
1296
|
+
const externalGeneration = runExternalRuntime
|
|
1297
|
+
? ++externalRuntimeGenerationRef.current
|
|
1298
|
+
: undefined;
|
|
1299
|
+
const isCurrentExternalRun = () => !runExternalRuntime || (externalRuntimeGenerationRef.current === externalGeneration
|
|
1300
|
+
&& currentSessionFile() === runSessionFile
|
|
1301
|
+
&& isGrokSubscriptionProviderId(externalRuntimeBindingRef.current?.id)
|
|
1302
|
+
&& externalRuntimeBindingRef.current?.sessionId === externalSessionId);
|
|
1303
|
+
if (runExternalRuntime) {
|
|
1304
|
+
if (!externalRuntime || typeof actualInput !== "string" || !externalSessionId || !runSessionManager) {
|
|
1305
|
+
addMessage("error", "Grok subscription session binding is unavailable. Start a fresh session with /login grok.");
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const boundModelId = externalRuntimeBindingRef.current?.modelId;
|
|
1309
|
+
if (boundModelId) {
|
|
1310
|
+
await externalRuntime.hydrateSession(externalSessionId, boundModelId, externalRuntimeBindingRef.current?.reasoningEffort);
|
|
1053
1311
|
}
|
|
1054
|
-
return;
|
|
1055
1312
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
addMessage("
|
|
1313
|
+
else {
|
|
1314
|
+
const activeProviderId = agent.providerId || safeRegistry.getDefault()?.id;
|
|
1315
|
+
const hasActiveProvider = !!activeProviderId && safeRegistry.getEnabled().some((provider) => provider.id === activeProviderId);
|
|
1316
|
+
if (!hasActiveProvider) {
|
|
1317
|
+
addMessage("error", "No provider configured. Use /login for ChatGPT or /provider --add <id> before sending a prompt.");
|
|
1318
|
+
if (runOptions.goalRun && goalStore?.snapshot()?.status === "active") {
|
|
1319
|
+
goalStore.pause();
|
|
1320
|
+
addMessage("assistant", stopReasonNotice("error"));
|
|
1321
|
+
}
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (!agent.model) {
|
|
1325
|
+
addMessage("error", "No model selected. Use /model after /login or provider setup.");
|
|
1326
|
+
if (runOptions.goalRun && goalStore?.snapshot()?.status === "active") {
|
|
1327
|
+
goalStore.pause();
|
|
1328
|
+
addMessage("assistant", stopReasonNotice("error"));
|
|
1329
|
+
}
|
|
1330
|
+
return;
|
|
1061
1331
|
}
|
|
1062
|
-
return;
|
|
1063
1332
|
}
|
|
1064
1333
|
const displayContent = formatImageUserDisplayText(displayInput, attachedImages.length, runOptions.imageDisplayStart);
|
|
1065
1334
|
if (!runOptions.hidden) {
|
|
@@ -1070,6 +1339,9 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1070
1339
|
// The new user row commits to native scrollback; the terminal keeps
|
|
1071
1340
|
// the prompt in view, so there is no app-side "snap to bottom" to do.
|
|
1072
1341
|
}
|
|
1342
|
+
if (runExternalRuntime) {
|
|
1343
|
+
runSessionManager.appendMessage({ role: "user", content: actualInput });
|
|
1344
|
+
}
|
|
1073
1345
|
setIsRunning(true);
|
|
1074
1346
|
runStartRef.current = Date.now();
|
|
1075
1347
|
setStreamingContent("");
|
|
@@ -1084,6 +1356,13 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1084
1356
|
let runErrored = false;
|
|
1085
1357
|
const toolCalls = [];
|
|
1086
1358
|
const assistantParts = [];
|
|
1359
|
+
// Entries whose members all reached a final status are now covered by
|
|
1360
|
+
// the settled transcript; drop them so the accumulator stays bounded
|
|
1361
|
+
// across a long session. Running entries survive — for a workflow
|
|
1362
|
+
// spanning turns they are the only live view of its members.
|
|
1363
|
+
if (pruneSettledLiveSubagentTools(liveSubagentToolsRef.current)) {
|
|
1364
|
+
setLiveSubagentVersion((version) => version + 1);
|
|
1365
|
+
}
|
|
1087
1366
|
const abortController = new AbortController();
|
|
1088
1367
|
activeAbortRef.current = abortController;
|
|
1089
1368
|
setStartingSubmit(null);
|
|
@@ -1117,7 +1396,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1117
1396
|
toolCalls.length > 0 ||
|
|
1118
1397
|
assistantParts.length > 0);
|
|
1119
1398
|
const commitAssistantMessage = (taskElapsedMs) => {
|
|
1120
|
-
if (!hasAssistantOutput())
|
|
1399
|
+
if (!hasAssistantOutput() || !isCurrentExternalRun())
|
|
1121
1400
|
return;
|
|
1122
1401
|
const currentParts = snapshotDisplayParts(assistantParts);
|
|
1123
1402
|
const currentToolCalls = [...toolCalls];
|
|
@@ -1143,6 +1422,16 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1143
1422
|
msg.taskElapsedMs = taskElapsedMs;
|
|
1144
1423
|
}
|
|
1145
1424
|
updateDisplayMessages((prev) => [...prev, msg]);
|
|
1425
|
+
if (runExternalRuntime) {
|
|
1426
|
+
runSessionManager.appendMessage({
|
|
1427
|
+
role: "assistant",
|
|
1428
|
+
content: partContent,
|
|
1429
|
+
...(assistantReasoning ? { reasoning: assistantReasoning } : {}),
|
|
1430
|
+
providerId: "grok",
|
|
1431
|
+
model: "grok-subscription",
|
|
1432
|
+
modelId: externalModelId,
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1146
1435
|
};
|
|
1147
1436
|
const clearAssistantStream = () => {
|
|
1148
1437
|
// A timer firing after this reset would resurrect the just-committed
|
|
@@ -1158,10 +1447,33 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1158
1447
|
assistantParts.length = 0;
|
|
1159
1448
|
};
|
|
1160
1449
|
try {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1450
|
+
const eventStream = runExternalRuntime
|
|
1451
|
+
? externalRuntime.run(actualInput, {
|
|
1452
|
+
sessionId: externalSessionId,
|
|
1453
|
+
signal: abortController.signal,
|
|
1454
|
+
generation: externalGeneration,
|
|
1455
|
+
})
|
|
1456
|
+
: agent.run(actualInput, args.cwd, {
|
|
1457
|
+
abortSignal: abortController.signal,
|
|
1458
|
+
inputController,
|
|
1459
|
+
});
|
|
1460
|
+
for await (const event of eventStream) {
|
|
1461
|
+
if (runExternalRuntime) {
|
|
1462
|
+
if (!isCurrentExternalRun()) {
|
|
1463
|
+
abortController.abort(new AgentAbortError("Stale Grok runtime generation."));
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
if (event.type !== "turn_start"
|
|
1467
|
+
&& event.type !== "text_delta"
|
|
1468
|
+
&& event.type !== "reasoning_delta"
|
|
1469
|
+
&& event.type !== "tool_start"
|
|
1470
|
+
&& event.type !== "tool_update"
|
|
1471
|
+
&& event.type !== "tool_end"
|
|
1472
|
+
&& event.type !== "turn_end") {
|
|
1473
|
+
await externalRuntime.cancel(externalSessionId).catch(() => undefined);
|
|
1474
|
+
throw new GrokRuntimeError("policy_violation", "Grok emitted an unsupported external runtime event.");
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1165
1477
|
switch (event.type) {
|
|
1166
1478
|
case "turn_start":
|
|
1167
1479
|
// A fresh provider call is starting. Everything worth keeping
|
|
@@ -1268,6 +1580,15 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1268
1580
|
setStreamingTools([...toolCalls]);
|
|
1269
1581
|
syncStreamingParts();
|
|
1270
1582
|
}
|
|
1583
|
+
else if (accumulateLiveSubagentUpdate(liveSubagentToolsRef.current, {
|
|
1584
|
+
id: event.id,
|
|
1585
|
+
name: event.name,
|
|
1586
|
+
metadata: event.update.metadata,
|
|
1587
|
+
})) {
|
|
1588
|
+
// The launching call already settled out of this round's
|
|
1589
|
+
// toolCalls; absorbed into the live accumulator instead.
|
|
1590
|
+
setLiveSubagentVersion((version) => version + 1);
|
|
1591
|
+
}
|
|
1271
1592
|
break;
|
|
1272
1593
|
}
|
|
1273
1594
|
case "todos_updated": {
|
|
@@ -1352,15 +1673,23 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1352
1673
|
}
|
|
1353
1674
|
}
|
|
1354
1675
|
catch (err) {
|
|
1355
|
-
|
|
1356
|
-
if (
|
|
1676
|
+
const staleExternalRun = runExternalRuntime && !isCurrentExternalRun();
|
|
1677
|
+
if (!staleExternalRun)
|
|
1678
|
+
commitAssistantMessage();
|
|
1679
|
+
const cancelledError = err instanceof AgentAbortError
|
|
1680
|
+
|| err?.name === "AbortError"
|
|
1681
|
+
|| (err instanceof GrokRuntimeError && err.code === "cancelled");
|
|
1682
|
+
if (staleExternalRun) {
|
|
1683
|
+
runCancelled = true;
|
|
1684
|
+
}
|
|
1685
|
+
else if (cancelledError) {
|
|
1357
1686
|
runCancelled = true;
|
|
1358
1687
|
// commitAssistantMessage already appended the partial answer; the
|
|
1359
1688
|
// interrupt is otherwise a pure append (the partial + a "Interrupted"
|
|
1360
1689
|
// row). Off a multiplexer, append just the interrupt row so settled
|
|
1361
1690
|
// history is never reprinted — no flash. Under tmux/screen, fall back
|
|
1362
1691
|
// to the full reprint that rebuilds from the canonical agent.messages.
|
|
1363
|
-
if (isMultiplexedTerminal()) {
|
|
1692
|
+
if (!runExternalRuntime && isMultiplexedTerminal()) {
|
|
1364
1693
|
resetTranscript(() => reconstructDisplayMessages(agent.messages));
|
|
1365
1694
|
}
|
|
1366
1695
|
else {
|
|
@@ -1378,7 +1707,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1378
1707
|
runErrored = true;
|
|
1379
1708
|
updateDisplayMessages((prev) => [
|
|
1380
1709
|
...prev,
|
|
1381
|
-
withMessageKey({ role: "error", content: err
|
|
1710
|
+
withMessageKey({ role: "error", content: errorMessage(err) }),
|
|
1382
1711
|
]);
|
|
1383
1712
|
}
|
|
1384
1713
|
}
|
|
@@ -1390,6 +1719,23 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1390
1719
|
const cancelled = abortController.signal.aborted;
|
|
1391
1720
|
if (cancelled)
|
|
1392
1721
|
runCancelled = true;
|
|
1722
|
+
if (runExternalRuntime && runCancelled) {
|
|
1723
|
+
// External ACP owns its own conversation state, so Bubble persists
|
|
1724
|
+
// an explicit boundary for its local transcript. Without this,
|
|
1725
|
+
// a partial response looks successfully completed after resume.
|
|
1726
|
+
runSessionManager.appendMessage({
|
|
1727
|
+
role: "assistant",
|
|
1728
|
+
content: INTERRUPTED_ASSISTANT_CONTENT,
|
|
1729
|
+
error: {
|
|
1730
|
+
name: "MessageAbortedError",
|
|
1731
|
+
message: "Assistant response was interrupted by the user.",
|
|
1732
|
+
aborted: true,
|
|
1733
|
+
},
|
|
1734
|
+
providerId: "grok",
|
|
1735
|
+
model: "grok-subscription",
|
|
1736
|
+
modelId: externalModelId,
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1393
1739
|
for (const leftover of inputController.clear()) {
|
|
1394
1740
|
const steer = pendingSteersRef.current.get(leftover.id);
|
|
1395
1741
|
pendingSteersRef.current.delete(leftover.id);
|
|
@@ -1408,25 +1754,32 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1408
1754
|
sessionFile: steer?.sessionFile ?? runSessionFile,
|
|
1409
1755
|
});
|
|
1410
1756
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1757
|
+
const ownsCurrentUiGeneration = !runExternalRuntime || isCurrentExternalRun();
|
|
1758
|
+
if (ownsCurrentUiGeneration) {
|
|
1759
|
+
setPendingSteerCount(0);
|
|
1760
|
+
setQueuedCount(queuedInputsRef.current.length);
|
|
1761
|
+
}
|
|
1413
1762
|
if (inputControllerRef.current === inputController)
|
|
1414
1763
|
inputControllerRef.current = null;
|
|
1415
1764
|
if (activeAbortRef.current === abortController)
|
|
1416
1765
|
activeAbortRef.current = null;
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1766
|
+
if (ownsCurrentUiGeneration) {
|
|
1767
|
+
setIsRunning(false);
|
|
1768
|
+
runStartRef.current = null;
|
|
1769
|
+
setStreamingContent("");
|
|
1770
|
+
setStreamingReasoning("");
|
|
1771
|
+
setStreamingTools([]);
|
|
1772
|
+
setStreamingParts([]);
|
|
1773
|
+
}
|
|
1774
|
+
if (!runExternalRuntime) {
|
|
1775
|
+
maybeContinueGoal({
|
|
1776
|
+
runCancelled,
|
|
1777
|
+
runErrored,
|
|
1778
|
+
isGoalRun: !!runOptions.goalRun,
|
|
1779
|
+
runTokens: goalRunTokens,
|
|
1780
|
+
usageReported: goalRunUsageReported,
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1430
1783
|
}
|
|
1431
1784
|
};
|
|
1432
1785
|
const kickGoalTurn = (prompt, visibleInput) => {
|
|
@@ -1553,7 +1906,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1553
1906
|
};
|
|
1554
1907
|
// Slash commands and skill invocations drop any attached images —
|
|
1555
1908
|
// they're meant for pure command routing.
|
|
1556
|
-
if (
|
|
1909
|
+
if (input.trimStart().startsWith("/")) {
|
|
1557
1910
|
// Fast-path `/quit` and `/exit` before slash-registry / skill
|
|
1558
1911
|
// resolution. This guarantees a literal "/quit" always exits even if
|
|
1559
1912
|
// a skill or alias of the same name is later registered. The
|
|
@@ -1564,14 +1917,16 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1564
1917
|
requestExit();
|
|
1565
1918
|
return;
|
|
1566
1919
|
}
|
|
1567
|
-
if (
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1920
|
+
if (!externalSessionBound) {
|
|
1921
|
+
if (/^\/goal(?:\s|$)/.test(input.trim())) {
|
|
1922
|
+
await handleGoalCommand(input);
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const skillInvocation = parseSkillInvocation(input, safeSkillRegistry);
|
|
1926
|
+
if (skillInvocation) {
|
|
1927
|
+
await runAgentInput(skillInvocation.actualPrompt, displayInput);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1575
1930
|
}
|
|
1576
1931
|
const { handled, result, inject } = await slashRegistry.execute(input, {
|
|
1577
1932
|
agent,
|
|
@@ -1604,6 +1959,10 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1604
1959
|
setThemeMode: applyThemeMode,
|
|
1605
1960
|
openStats: openStatsPanel,
|
|
1606
1961
|
compactionProgress: setCompaction,
|
|
1962
|
+
externalRuntime,
|
|
1963
|
+
startFreshSession,
|
|
1964
|
+
transitionToNative,
|
|
1965
|
+
onExternalRuntimeChange: refreshExternalRuntimeBinding,
|
|
1607
1966
|
});
|
|
1608
1967
|
if (handled) {
|
|
1609
1968
|
if (agent.mode !== permissionMode) {
|
|
@@ -1627,7 +1986,9 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1627
1986
|
}
|
|
1628
1987
|
else if (result.startsWith("⏪")) {
|
|
1629
1988
|
// /rewind truncated agent.messages — rebuild the transcript from
|
|
1630
|
-
// the rewound state before appending the summary.
|
|
1989
|
+
// the rewound state before appending the summary. Subagents
|
|
1990
|
+
// rewound out of history must not linger in the live accumulator.
|
|
1991
|
+
clearLiveSubagentTools();
|
|
1631
1992
|
resetTranscript(() => [
|
|
1632
1993
|
...reconstructDisplayMessages(agent.messages),
|
|
1633
1994
|
{ role: "assistant", content: result },
|
|
@@ -1642,6 +2003,14 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1642
2003
|
}
|
|
1643
2004
|
return;
|
|
1644
2005
|
}
|
|
2006
|
+
if (externalSessionBound) {
|
|
2007
|
+
addMessage("error", "That command is unavailable in this external runtime session.");
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
if (grokSessionBound) {
|
|
2012
|
+
await runAgentInput(input, displayInput);
|
|
2013
|
+
return;
|
|
1645
2014
|
}
|
|
1646
2015
|
const expansion = await expandAtMentions(input, args.cwd);
|
|
1647
2016
|
if (expansion.missing.length > 0) {
|
|
@@ -1667,7 +2036,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1667
2036
|
setStartingSubmit(null);
|
|
1668
2037
|
}
|
|
1669
2038
|
}
|
|
1670
|
-
}, [addMessage, agent, args.cwd, openPicker, openSessionPicker, openRewindPicker, openStatsPanel, createProvider, currentSessionFile, fillComposer, prepareSubmitDisplay, safeRegistry, safeSkillRegistry, updateDisplayMessages, queueInput, submitSteer, requestExit, setStartingSubmit]);
|
|
2039
|
+
}, [addMessage, agent, args.cwd, openPicker, openSessionPicker, openRewindPicker, openStatsPanel, createProvider, currentSessionFile, externalRuntime, externalRuntimeBinding, externalSessionBound, fillComposer, grokSessionBound, prepareSubmitDisplay, refreshExternalRuntimeBinding, safeRegistry, safeSkillRegistry, startFreshSession, transitionToNative, unsupportedExternalSessionBound, updateDisplayMessages, queueInput, submitSteer, requestExit, setStartingSubmit]);
|
|
1671
2040
|
// Drain the queue once the run ends and no modal needs the user first.
|
|
1672
2041
|
// The placeholder row is removed right before resubmitting — handleSubmit
|
|
1673
2042
|
// renders the message again as a regular user row.
|
|
@@ -1699,7 +2068,9 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1699
2068
|
const timer = setTimeout(drainQueuedInput, 0);
|
|
1700
2069
|
return () => clearTimeout(timer);
|
|
1701
2070
|
}, [isRunning, queuedCount, startingSubmitFingerprint, pendingPlan, pendingApproval, pendingQuestion, pendingFeedback, pickerMode, statsPanel, drainQueuedInput]);
|
|
1702
|
-
const currentProviderId =
|
|
2071
|
+
const currentProviderId = externalSessionBound
|
|
2072
|
+
? (grokSessionBound ? GROK_SUBSCRIPTION_PROVIDER_ID : undefined)
|
|
2073
|
+
: agent.providerId || safeRegistry.getDefault()?.id;
|
|
1703
2074
|
const keyTarget = keyProviderId
|
|
1704
2075
|
? safeRegistry.getConfigured().find((p) => p.id === keyProviderId)
|
|
1705
2076
|
: safeRegistry.getDefault();
|
|
@@ -1726,8 +2097,14 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1726
2097
|
const showThinkingLabel = Boolean(thinkingLevel)
|
|
1727
2098
|
&& thinkingLevel !== "off"
|
|
1728
2099
|
&& (isThinkingToggle || isThinkingOnly || availableThinkingLevels.length > 2);
|
|
1729
|
-
const welcomeBannerNode = showWelcome ? (_jsx(WelcomeBanner, { terminalColumns: terminalColumns, tips: buildTips(agent, safeRegistry), updateNotice: currentUpdateNotice, cwd: friendlyCwd(args.cwd), sessionLabel: sessionBasename(currentSessionFile()), providerId: agent.providerId || safeRegistry.getDefault()?.id, modelId:
|
|
1730
|
-
|
|
2100
|
+
const welcomeBannerNode = showWelcome ? (_jsx(WelcomeBanner, { terminalColumns: terminalColumns, tips: buildTips(agent, safeRegistry, grokSessionBound), updateNotice: currentUpdateNotice, cwd: friendlyCwd(args.cwd), sessionLabel: sessionBasename(currentSessionFile()), providerId: grokSessionBound ? "grok" : externalSessionBound ? undefined : agent.providerId || safeRegistry.getDefault()?.id, modelId: grokSessionBound ? externalRuntimeBinding?.modelId : externalSessionBound ? undefined : agent.apiModel, modelLabel: externalSessionBound
|
|
2101
|
+
? (grokSessionBound
|
|
2102
|
+
? `Grok Subscription${externalRuntimeBinding?.modelId ? ` · ${externalRuntimeBinding.modelId}` : ""}`
|
|
2103
|
+
: "Unsupported external runtime · recovery-only mode")
|
|
2104
|
+
: agent.model ? displayModel(agent.model) : undefined, thinkingLabel: grokSessionBound
|
|
2105
|
+
? externalRuntimeBinding?.reasoningEffort
|
|
2106
|
+
: !externalSessionBound && showThinkingLabel ? thinkingLevel : undefined })) : null;
|
|
2107
|
+
const commandPaletteItems = useMemo(() => buildCommandPaletteItems(safeSkillRegistry, externalSessionBound), [externalSessionBound, safeSkillRegistry]);
|
|
1731
2108
|
const mcpReconnectItems = useMemo(() => buildMcpReconnectItems(mcpManager), [mcpManager]);
|
|
1732
2109
|
// No fixed-height frame: settled rows flow into the terminal's native
|
|
1733
2110
|
// scrollback via <Static>, and only the dynamic bottom stack (streaming
|
|
@@ -1735,7 +2112,14 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1735
2112
|
// to its content keeps the composer pinned just below the latest output the
|
|
1736
2113
|
// way ordinary shell programs do.
|
|
1737
2114
|
const mainWidth = Math.max(40, terminalColumns);
|
|
1738
|
-
return (_jsx(ThemeProvider, { value: palette, children: _jsxs(Box, { flexDirection: "column", width: mainWidth,
|
|
2115
|
+
return (_jsx(ThemeProvider, { value: palette, children: _jsxs(Box, { flexDirection: "column", width: mainWidth, children: [_jsx(MessageList, { messages: messages, streamingContent: streamingContent, streamingReasoning: streamingReasoning, streamingTools: streamingTools, streamingParts: streamingParts, terminalColumns: mainWidth, showThinking: showThinking, verboseTrace: verboseTrace, pendingApproval: approvalHint, nowTick: nowTick, welcomeBanner: welcomeBannerNode, staticGeneration: staticGeneration, paddingX: 1, maxStreamRows: Math.max(6, terminalRows - 10) }), pickerMode === "grok-model" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: grokModels.length === 0 ? (_jsx(Text, { children: "Loading Grok subscription models\u2026" })) : (_jsx(ProviderPicker, { title: "Select Grok Model and Reasoning", providers: grokModels.flatMap((model) => model.reasoningLevels.map((effort) => ({
|
|
2116
|
+
id: JSON.stringify({ modelId: model.id, reasoningEffort: effort }),
|
|
2117
|
+
name: `${model.name}${effort === "off" ? "" : ` · ${effort}`}`,
|
|
2118
|
+
enabled: true,
|
|
2119
|
+
}))), current: JSON.stringify({
|
|
2120
|
+
modelId: externalRuntimeBinding?.modelId,
|
|
2121
|
+
reasoningEffort: externalRuntimeBinding?.reasoningEffort ?? "high",
|
|
2122
|
+
}), onSelect: handleGrokModelSelect, onCancel: closePicker })) })), pickerMode === "model" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ModelPicker, { registry: safeRegistry, current: agent.model, currentThinkingLevel: thinkingLevel, recent: userConfig.getRecentModels(), onSelect: handleModelSelect, onCancel: closePicker }) })), pickerMode === "provider" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: withGrokSubscriptionProvider(BUILTIN_PROVIDERS
|
|
1739
2123
|
.filter((p) => isUserVisibleProvider(p.id))
|
|
1740
2124
|
.map((p) => {
|
|
1741
2125
|
const configured = safeRegistry.getConfigured().find((item) => item.id === p.id);
|
|
@@ -1745,13 +2129,21 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1745
2129
|
name: `${p.name} [${configuredLabel}]`,
|
|
1746
2130
|
enabled: true,
|
|
1747
2131
|
};
|
|
1748
|
-
}), current: currentProviderId, onSelect: handleProviderSelect, onCancel: closePicker }) })), pickerMode === "provider-add" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
2132
|
+
})), current: currentProviderId, onSelect: handleProviderSelect, onCancel: closePicker }) })), pickerMode === "provider-add" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: withGrokSubscriptionProvider(BUILTIN_PROVIDERS
|
|
1749
2133
|
.filter((p) => isUserVisibleProvider(p.id))
|
|
1750
|
-
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleProviderAddSelect, onCancel: closePicker, title: "Add Provider" }) })), pickerMode === "login" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers:
|
|
2134
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true }))), current: currentProviderId, onSelect: handleProviderAddSelect, onCancel: closePicker, title: "Add Provider" }) })), pickerMode === "login" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers:
|
|
2135
|
+
// The native grok provider is OAuth-capable, so it appears via
|
|
2136
|
+
// supportsOAuth like OpenAI — no extra hardcoded row.
|
|
2137
|
+
BUILTIN_PROVIDERS
|
|
1751
2138
|
.filter((p) => isUserVisibleProvider(p.id) && safeRegistry.supportsOAuth(p.id))
|
|
1752
|
-
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLoginProviderSelect, onCancel: closePicker, title: "Select Login Provider" }) })), pickerMode === "logout" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers:
|
|
1753
|
-
|
|
1754
|
-
|
|
2139
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLoginProviderSelect, onCancel: closePicker, title: "Select Login Provider" }) })), pickerMode === "logout" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: [
|
|
2140
|
+
...safeRegistry.getConfigured()
|
|
2141
|
+
.filter((p) => safeRegistry.getAuthStorage().has(p.id))
|
|
2142
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })),
|
|
2143
|
+
...(grokSessionBound
|
|
2144
|
+
? [{ id: GROK_SUBSCRIPTION_PROVIDER_ID, name: "Grok Subscription [local login]", enabled: true }]
|
|
2145
|
+
: []),
|
|
2146
|
+
], current: currentProviderId, onSelect: handleLogoutProviderSelect, onCancel: closePicker, title: "Select Logout Provider" }) })), pickerMode === "key" && keyTarget && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(KeyPicker, { providerName: keyTarget.name, onSubmit: handleKeySubmit, onCancel: () => {
|
|
1755
2147
|
closePicker();
|
|
1756
2148
|
setKeyProviderId(null);
|
|
1757
2149
|
} }) })), pickerMode === "skill" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SkillPicker, { skills: safeSkillRegistry.summaries(), onSelect: (name) => {
|
|
@@ -1808,27 +2200,49 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1808
2200
|
else if (result.kind === "error") {
|
|
1809
2201
|
addMessage("error", `Feedback failed: ${result.message}`);
|
|
1810
2202
|
}
|
|
1811
|
-
} }) })), !isExiting && compaction && (_jsx(Box, { flexShrink: 0, backgroundColor: palette.background, children: _jsx(CompactionProgressCard, { progress: compaction }) })), !isExiting && isRunning && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && (
|
|
2203
|
+
} }) })), !isExiting && compaction && (_jsx(Box, { flexShrink: 0, backgroundColor: palette.background, children: _jsx(CompactionProgressCard, { progress: compaction }) })), !isExiting && isRunning && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && (
|
|
2204
|
+
// The streaming tail supplies its own bottom margin once it has
|
|
2205
|
+
// visible output, but right after a prompt is sent the tail renders
|
|
2206
|
+
// nothing and the spinner would hug the user's message bubble — give
|
|
2207
|
+
// it a top gap only in that empty-tail state to avoid double spacing.
|
|
2208
|
+
_jsx(Box, { paddingX: 1, paddingBottom: 1, paddingTop: streamingParts.length === 0
|
|
2209
|
+
&& streamingContent.length === 0
|
|
2210
|
+
&& streamingTools.length === 0
|
|
2211
|
+
&& (streamingReasoning.length === 0 || !(showThinking || verboseTrace))
|
|
2212
|
+
? 1
|
|
2213
|
+
: 0, flexShrink: 0, backgroundColor: palette.background, children: _jsx(WaitingIndicator, { tools: streamingTools, hasStreamingText: streamingContent.length > 0, hasStreamingReasoning: streamingReasoning.length > 0, streamedChars: streamingContent.length + streamingReasoning.length, nowTick: nowTick, pendingSteerCount: pendingSteerCount, queuedCount: queuedCount }) })), !isExiting && !pickerMode && !statsPanel && (_jsx(Box, { paddingBottom: 1, flexShrink: 0, backgroundColor: palette.background, children: _jsx(InputBox, { onSubmit: handleSubmit, onQueue: isRunning ? queueInput : undefined, onArrowDownAtBottom: () => {
|
|
1812
2214
|
if (subagentMembers.length > 0 && !pickerMode)
|
|
1813
2215
|
setSubagentEntryFocused(true);
|
|
1814
|
-
}, disabled: !!pendingPlan || !!pendingApproval || !!pendingQuestion || !!pendingFeedback || !!statsPanel || subagentEntryFocused, cursorResetEpoch: cursorResetEpoch, draftText: composerDraft?.text, draftEpoch: composerDraft?.epoch, onDraftApplied: clearComposerDraft, skillRegistry: safeSkillRegistry, localSlashCommands: [...INK_LOCAL_SLASH_COMMANDS], terminalColumns: mainWidth, cwd: args.cwd, sessionFile: currentSessionFile(), nextImageLabelStart: nextImageDisplayLabelStartRef.current }) })), !isExiting && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && subagentMembers.length > 0 && (_jsxs(Box, { paddingX: 1, flexShrink: 0,
|
|
2216
|
+
}, disabled: !!pendingPlan || !!pendingApproval || !!pendingQuestion || !!pendingFeedback || !!statsPanel || subagentEntryFocused, cursorResetEpoch: cursorResetEpoch, draftText: composerDraft?.text, draftEpoch: composerDraft?.epoch, onDraftApplied: clearComposerDraft, skillRegistry: externalSessionBound ? undefined : safeSkillRegistry, localSlashCommands: externalSessionBound ? [] : [...INK_LOCAL_SLASH_COMMANDS], allowedSlashCommands: unsupportedExternalSessionBound ? GROK_LOCAL_SLASH_COMMANDS : undefined, allowWorkspaceMentions: !unsupportedExternalSessionBound, allowImageAttachments: !externalSessionBound, terminalColumns: mainWidth, cwd: args.cwd, sessionFile: currentSessionFile(), nextImageLabelStart: nextImageDisplayLabelStartRef.current }) })), !isExiting && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && subagentMembers.length > 0 && (_jsxs(Box, { paddingX: 1, flexShrink: 0, children: [_jsx(Text, { bold: subagentEntryFocused, color: subagentEntryFocused ? palette.accent : palette.toolName, children: subagentEntryFocused ? "> ↳ " : " ↳ " }), _jsxs(Text, { color: subagentEntryFocused ? palette.accent : palette.muted, children: [subagentMembers.length, " subagent", subagentMembers.length === 1 ? "" : "s", " \u00B7 ", subagentSummary(subagentMembers), " \u00B7 "] }), _jsx(Text, { color: palette.accent, children: subagentEntryFocused ? "Enter open · Esc back" : "↓ to inspect traces" })] })), !isExiting && (_jsx(Box, { flexShrink: 0, children: _jsx(FooterBar, { data: buildFooterData({
|
|
2217
|
+
mode: permissionMode,
|
|
2218
|
+
goalLine: externalSessionBound ? undefined : goalLine,
|
|
2219
|
+
runtimeLabel: externalSessionBound
|
|
2220
|
+
? (grokSessionBound
|
|
2221
|
+
? `Grok Subscription${externalRuntimeBinding?.modelId ? ` · ${externalRuntimeBinding.modelId}` : ""}${externalRuntimeBinding?.reasoningEffort && externalRuntimeBinding.reasoningEffort !== "off" ? ` · ${externalRuntimeBinding.reasoningEffort}` : ""} · workspace`
|
|
2222
|
+
: "Unsupported external runtime · recovery-only")
|
|
2223
|
+
: undefined,
|
|
2224
|
+
}) }) }))] }) }));
|
|
1815
2225
|
}
|
|
1816
|
-
function buildCommandPaletteItems(skillRegistry) {
|
|
2226
|
+
function buildCommandPaletteItems(skillRegistry, grokSessionBound = false) {
|
|
1817
2227
|
const items = new Map();
|
|
1818
2228
|
const add = (item) => {
|
|
1819
2229
|
const key = `${item.action ?? "command"}:${item.value}`;
|
|
1820
2230
|
if (!items.has(key))
|
|
1821
2231
|
items.set(key, item);
|
|
1822
2232
|
};
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
2233
|
+
if (!grokSessionBound) {
|
|
2234
|
+
for (const command of INK_LOCAL_SLASH_COMMANDS) {
|
|
2235
|
+
add({
|
|
2236
|
+
label: `/${command.name}`,
|
|
2237
|
+
detail: command.description,
|
|
2238
|
+
value: command.name,
|
|
2239
|
+
command: `/${command.name}`,
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
1830
2242
|
}
|
|
1831
2243
|
for (const command of slashRegistry.list()) {
|
|
2244
|
+
if (grokSessionBound && !isGrokLocalSlashCommand(command.name))
|
|
2245
|
+
continue;
|
|
1832
2246
|
const source = command.source === "mcp" ? " :mcp" : "";
|
|
1833
2247
|
const sourceLabel = command.sourceLabel ? `[${command.sourceLabel}] ` : "";
|
|
1834
2248
|
add({
|
|
@@ -1838,14 +2252,16 @@ function buildCommandPaletteItems(skillRegistry) {
|
|
|
1838
2252
|
command: `/${command.name}`,
|
|
1839
2253
|
});
|
|
1840
2254
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2255
|
+
if (!grokSessionBound) {
|
|
2256
|
+
for (const skill of skillRegistry.summaries()) {
|
|
2257
|
+
add({
|
|
2258
|
+
label: `/${skill.name} :skill`,
|
|
2259
|
+
detail: `[${skill.source}] ${skill.description}`,
|
|
2260
|
+
value: skill.name,
|
|
2261
|
+
command: `/${skill.name}`,
|
|
2262
|
+
action: "insert-skill",
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
1849
2265
|
}
|
|
1850
2266
|
return [...items.values()];
|
|
1851
2267
|
}
|