@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
|
@@ -87,6 +87,17 @@ export class PermissionAwareApprovalController {
|
|
|
87
87
|
return { tool: "Lsp", path: req.path, cwd: this.options.cwd };
|
|
88
88
|
case "agent_profile":
|
|
89
89
|
return { tool: "AgentProfile" };
|
|
90
|
+
case "external_tool": {
|
|
91
|
+
const command = externalCommand(req.rawInput);
|
|
92
|
+
if (req.kind === "execute" && command !== undefined) {
|
|
93
|
+
return { tool: "Bash", command };
|
|
94
|
+
}
|
|
95
|
+
const path = req.locations?.[0]?.path;
|
|
96
|
+
if ((req.kind === "edit" || req.kind === "delete" || req.kind === "move") && path) {
|
|
97
|
+
return { tool: "Edit", path, cwd: this.options.cwd };
|
|
98
|
+
}
|
|
99
|
+
return { tool: req.title.trim() || req.kind.trim() || "ExternalTool" };
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
checkRequestRules(req) {
|
|
@@ -176,6 +187,8 @@ function approvalTarget(req) {
|
|
|
176
187
|
return "Lsp";
|
|
177
188
|
case "agent_profile":
|
|
178
189
|
return "AgentProfile";
|
|
190
|
+
case "external_tool":
|
|
191
|
+
return req.title.trim() || req.kind.trim() || "ExternalTool";
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
function summarizeApprovalRequest(req) {
|
|
@@ -192,5 +205,20 @@ function summarizeApprovalRequest(req) {
|
|
|
192
205
|
return { type: req.type, path: req.path, operation: req.operation };
|
|
193
206
|
case "agent_profile":
|
|
194
207
|
return { type: req.type, name: req.name, path: req.path, contentHash: req.contentHash };
|
|
208
|
+
case "external_tool":
|
|
209
|
+
return {
|
|
210
|
+
type: req.type,
|
|
211
|
+
toolCallId: req.toolCallId,
|
|
212
|
+
title: req.title,
|
|
213
|
+
kind: req.kind,
|
|
214
|
+
locations: req.locations,
|
|
215
|
+
rawInput: req.rawInput,
|
|
216
|
+
};
|
|
195
217
|
}
|
|
196
218
|
}
|
|
219
|
+
function externalCommand(rawInput) {
|
|
220
|
+
if (typeof rawInput !== "object" || rawInput === null || Array.isArray(rawInput))
|
|
221
|
+
return undefined;
|
|
222
|
+
const command = rawInput.command;
|
|
223
|
+
return typeof command === "string" ? command : undefined;
|
|
224
|
+
}
|
package/dist/approval/types.d.ts
CHANGED
|
@@ -61,7 +61,27 @@ export interface AgentProfileApprovalRequest {
|
|
|
61
61
|
/** First lines of the profile prompt so the user can judge it. */
|
|
62
62
|
promptPreview: string;
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Permission gate for a tool exposed by an external ACP runtime.
|
|
66
|
+
*
|
|
67
|
+
* Keep this structural rather than importing ACP SDK types so the approval
|
|
68
|
+
* layer remains provider-agnostic. External runtimes are responsible for
|
|
69
|
+
* copying the relevant, user-visible fields from the protocol request.
|
|
70
|
+
*/
|
|
71
|
+
export interface ExternalToolApprovalRequest {
|
|
72
|
+
type: "external_tool";
|
|
73
|
+
toolCallId: string;
|
|
74
|
+
/** Human-readable tool name supplied by the external runtime. */
|
|
75
|
+
title: string;
|
|
76
|
+
/** ACP tool category (for example execute, edit, read, or other). */
|
|
77
|
+
kind: string;
|
|
78
|
+
rawInput?: unknown;
|
|
79
|
+
locations?: Array<{
|
|
80
|
+
path: string;
|
|
81
|
+
line?: number | null;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
export type ApprovalRequest = EditApprovalRequest | WriteApprovalRequest | PatchApprovalRequest | BashApprovalRequest | LspApprovalRequest | AgentProfileApprovalRequest | ExternalToolApprovalRequest;
|
|
65
85
|
export type ApprovalDecision = {
|
|
66
86
|
action: "approve";
|
|
67
87
|
feedback?: string;
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI argument parsing.
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type PermissionMode, type ThinkingLevel } from "./types.js";
|
|
5
5
|
export type CliCommand = "default" | "serve" | "update";
|
|
6
6
|
export interface CliArgs {
|
|
7
7
|
command: CliCommand;
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI argument parsing.
|
|
3
3
|
*/
|
|
4
|
+
import { THINKING_LEVELS } from "./types.js";
|
|
4
5
|
import { isThinkingLevel } from "./variant/thinking-level.js";
|
|
5
6
|
export function parseArgs(argv) {
|
|
6
7
|
const args = {
|
|
@@ -102,7 +103,7 @@ Options (default):
|
|
|
102
103
|
-r, --resume Resume a previous session (latest by default)
|
|
103
104
|
--session <name> Session name to create or resume
|
|
104
105
|
--reasoning Enable reasoning mode at medium effort
|
|
105
|
-
--reasoning-effort <l> Set reasoning effort:
|
|
106
|
+
--reasoning-effort <l> Set reasoning effort: ${THINKING_LEVELS.join("|")}
|
|
106
107
|
--plan Start in plan mode (read-only investigation; propose before executing)
|
|
107
108
|
--dangerously-skip-permissions
|
|
108
109
|
Enable bypass mode (auto-approve EVERY tool; disables all safety prompts)
|
package/dist/config.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Uses a single JSON file in Bubble home, normally ~/.bubble/config.json.
|
|
5
5
|
*/
|
|
6
6
|
import { type AgentCategoriesConfig } from "./agent/categories.js";
|
|
7
|
+
import { type AgentRoutingConfig } from "./agent/routing-catalog.js";
|
|
7
8
|
import type { ProviderProfile } from "./provider-registry.js";
|
|
8
9
|
import type { ThinkingLevel } from "./types.js";
|
|
9
10
|
export type ThemeMode = "auto" | "light" | "dark";
|
|
@@ -31,13 +32,13 @@ export interface UserConfigData {
|
|
|
31
32
|
providers?: ProviderProfile[];
|
|
32
33
|
defaultProvider?: string;
|
|
33
34
|
agentCategories?: AgentCategoriesConfig;
|
|
35
|
+
/** Subagent model-routing knobs: autoTier (default true), allowCrossProvider (default true). */
|
|
36
|
+
agentRouting?: Partial<AgentRoutingConfig>;
|
|
34
37
|
subagents?: SubagentsUserConfig;
|
|
35
38
|
}
|
|
36
39
|
export interface SubagentsUserConfig {
|
|
37
40
|
/** Global cap on concurrently running children. Default 8. */
|
|
38
41
|
maxActiveSubagents?: number;
|
|
39
|
-
/** Absolute per-child soft token cap. Default 200000. */
|
|
40
|
-
childTokenCap?: number;
|
|
41
42
|
}
|
|
42
43
|
export declare class UserConfig {
|
|
43
44
|
private data;
|
|
@@ -64,9 +65,10 @@ export declare class UserConfig {
|
|
|
64
65
|
setThemeMode(mode: ThemeMode): void;
|
|
65
66
|
setThemeOverrides(overrides: Record<string, string>): void;
|
|
66
67
|
getAgentCategories(): AgentCategoriesConfig;
|
|
68
|
+
getAgentRouting(): AgentRoutingConfig;
|
|
67
69
|
getSubagents(): SubagentsUserConfig;
|
|
68
70
|
}
|
|
69
|
-
export declare function shouldProbeTerminalTheme(
|
|
71
|
+
export declare function shouldProbeTerminalTheme(_config: ThemeConfig): boolean;
|
|
70
72
|
export declare function effectiveThemeModeForTerminal(config: ThemeConfig, detectedTheme: Exclude<ThemeMode, "auto">): ThemeMode;
|
|
71
73
|
/** Mask an API key for safe display. */
|
|
72
74
|
export declare function maskKey(key: string): string;
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { getBubbleHome } from "./bubble-home.js";
|
|
9
9
|
import { sanitizeAgentCategories } from "./agent/categories.js";
|
|
10
|
+
import { sanitizeAgentRouting } from "./agent/routing-catalog.js";
|
|
10
11
|
const HIDDEN_PROVIDER_IDS = new Set(["openrouter", "openai-codex"]);
|
|
11
12
|
function getConfigPath() {
|
|
12
13
|
return join(getBubbleHome(), "config.json");
|
|
@@ -45,9 +46,6 @@ function sanitizeSubagentsConfig(value) {
|
|
|
45
46
|
if (typeof raw.maxActiveSubagents === "number" && Number.isFinite(raw.maxActiveSubagents)) {
|
|
46
47
|
out.maxActiveSubagents = Math.max(1, Math.floor(raw.maxActiveSubagents));
|
|
47
48
|
}
|
|
48
|
-
if (typeof raw.childTokenCap === "number" && Number.isFinite(raw.childTokenCap)) {
|
|
49
|
-
out.childTokenCap = Math.max(1_000, Math.floor(raw.childTokenCap));
|
|
50
|
-
}
|
|
51
49
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
52
50
|
}
|
|
53
51
|
function sanitizeTheme(value) {
|
|
@@ -207,12 +205,19 @@ export class UserConfig {
|
|
|
207
205
|
getAgentCategories() {
|
|
208
206
|
return sanitizeAgentCategories(this.data.agentCategories);
|
|
209
207
|
}
|
|
208
|
+
getAgentRouting() {
|
|
209
|
+
return sanitizeAgentRouting(this.data.agentRouting);
|
|
210
|
+
}
|
|
210
211
|
getSubagents() {
|
|
211
212
|
return sanitizeSubagentsConfig(this.data.subagents) ?? {};
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
|
-
export function shouldProbeTerminalTheme(
|
|
215
|
-
|
|
215
|
+
export function shouldProbeTerminalTheme(_config) {
|
|
216
|
+
// Always probe: even a forced theme needs the real terminal background to
|
|
217
|
+
// decide between inheriting it (theme matches the terminal) and painting
|
|
218
|
+
// its own canvas (forced theme mismatching the terminal, where the
|
|
219
|
+
// palette's foregrounds would otherwise be unreadable).
|
|
220
|
+
return true;
|
|
216
221
|
}
|
|
217
222
|
export function effectiveThemeModeForTerminal(config, detectedTheme) {
|
|
218
223
|
if (isLegacyBareDarkTheme(config) && detectedTheme === "light")
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* fails.
|
|
8
8
|
*/
|
|
9
9
|
import type { CompactOptions, CompactResult } from "./compact.js";
|
|
10
|
-
import type { Message, Provider, ProviderMessage } from "../types.js";
|
|
10
|
+
import type { Message, Provider, ProviderMessage, ThinkingLevel } from "../types.js";
|
|
11
11
|
export interface LLMCompactOptions extends CompactOptions {
|
|
12
12
|
provider: Provider;
|
|
13
13
|
model: string;
|
|
14
|
-
thinkingLevel?:
|
|
14
|
+
thinkingLevel?: ThinkingLevel;
|
|
15
15
|
}
|
|
16
16
|
export declare const COMPACT_SYSTEM_PROMPT = "You are a conversation summarizer. Your job is to produce a structured\nsummary of an earlier portion of a software-engineering assistant's\nconversation so that the assistant can continue working without the full\nhistory. Preserve fidelity over brevity where the user's intent, file\npaths, or decisions are concerned. Output ONLY the summary, no preamble.";
|
|
17
17
|
export declare const COMPACT_INSTRUCTIONS = "Summarize the conversation above using exactly these 9 sections, each\npreceded by the literal heading on its own line. If a section has no\ncontent, write \"None\".\n\n1. Primary Request and Intent\n - What the user ultimately wants, in their own framing.\n\n2. Key Technical Concepts\n - Libraries, frameworks, architectural patterns referenced.\n\n3. Files and Code Sections\n - Files read, written, or discussed. Include full paths and a one-line note.\n\n4. Errors and Fixes\n - Bugs encountered and how they were resolved.\n\n5. Problem Solving\n - Non-trivial debugging or design decisions.\n\n6. All User Messages\n - Every user message, verbatim, in order. Do not summarize here.\n\n7. Pending Tasks\n - Work that was planned but not yet completed.\n\n8. Current Work\n - What was being actively worked on when the summary was taken.\n\n9. Optional Next Step\n - The single most natural next action, if obvious.";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { GrokProfile } from "./grok-profile.js";
|
|
3
|
+
import type { GrokSpawn } from "./grok-process.js";
|
|
4
|
+
import { GrokRuntimeError } from "./grok-errors.js";
|
|
5
|
+
export interface GrokAcpHandlers {
|
|
6
|
+
onSessionUpdate(notification: acp.SessionNotification): void | Promise<void>;
|
|
7
|
+
onPermissionRequest(params: acp.RequestPermissionRequest): Promise<string | undefined>;
|
|
8
|
+
onPermissionViolation(sessionId?: string): void | Promise<void>;
|
|
9
|
+
onCrash(error: GrokRuntimeError): void;
|
|
10
|
+
}
|
|
11
|
+
export interface GrokAcpClient {
|
|
12
|
+
initialize(): Promise<void>;
|
|
13
|
+
newSession(): Promise<GrokAcpSessionState>;
|
|
14
|
+
loadSession(sessionId: string): Promise<GrokAcpSessionState>;
|
|
15
|
+
setModel(sessionId: string, modelId: string): Promise<GrokAcpModelState | undefined>;
|
|
16
|
+
prompt(sessionId: string, prompt: string, generation: number, signal?: AbortSignal): Promise<acp.PromptResponse>;
|
|
17
|
+
cancel(sessionId: string): Promise<void>;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export interface GrokAcpModelInfo {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface GrokAcpModelState {
|
|
26
|
+
currentModelId: string;
|
|
27
|
+
availableModels: GrokAcpModelInfo[];
|
|
28
|
+
}
|
|
29
|
+
export interface GrokAcpSessionState {
|
|
30
|
+
id: string;
|
|
31
|
+
models?: GrokAcpModelState;
|
|
32
|
+
}
|
|
33
|
+
export type GrokAcpFactory = (options: {
|
|
34
|
+
binary: string;
|
|
35
|
+
profile: GrokProfile;
|
|
36
|
+
workspace: string;
|
|
37
|
+
modelId?: string;
|
|
38
|
+
reasoningEffort?: string;
|
|
39
|
+
permissionMode?: "default" | "acceptEdits" | "plan" | "bypassPermissions";
|
|
40
|
+
env: NodeJS.ProcessEnv;
|
|
41
|
+
spawn: GrokSpawn;
|
|
42
|
+
handlers: GrokAcpHandlers;
|
|
43
|
+
}) => GrokAcpClient;
|
|
44
|
+
export declare const GROK_ACP_ARGS: (workspace: string, modelId?: string, reasoningEffort?: string, permissionMode?: "default" | "acceptEdits" | "plan" | "bypassPermissions") => readonly string[];
|
|
45
|
+
/**
|
|
46
|
+
* Reassemble stderr before redaction so secrets split across arbitrary child
|
|
47
|
+
* process chunks cannot bypass token patterns. If the private in-memory raw
|
|
48
|
+
* buffer exceeds its cap, discard it wholesale and expose only a fixed notice.
|
|
49
|
+
*/
|
|
50
|
+
export declare class GrokDiagnosticBuffer {
|
|
51
|
+
private raw;
|
|
52
|
+
private overflowed;
|
|
53
|
+
append(chunk: Buffer | string): void;
|
|
54
|
+
sanitized(maxLength?: number): string;
|
|
55
|
+
}
|
|
56
|
+
export declare const createDefaultGrokAcpClient: GrokAcpFactory;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
3
|
+
import { sanitizeGrokDiagnostic } from "./grok-process.js";
|
|
4
|
+
import { GrokRuntimeError } from "./grok-errors.js";
|
|
5
|
+
import { createBoundedGrokNdjsonStream } from "./grok-ndjson.js";
|
|
6
|
+
export const GROK_ACP_ARGS = (workspace, modelId, reasoningEffort, permissionMode = "default") => [
|
|
7
|
+
"--no-auto-update",
|
|
8
|
+
"--cwd",
|
|
9
|
+
workspace,
|
|
10
|
+
"--no-memory",
|
|
11
|
+
"--no-subagents",
|
|
12
|
+
"--no-plan",
|
|
13
|
+
"--disable-web-search",
|
|
14
|
+
"--tools",
|
|
15
|
+
"Read,Edit,Grep,Bash",
|
|
16
|
+
"--permission-mode",
|
|
17
|
+
permissionMode,
|
|
18
|
+
"--sandbox",
|
|
19
|
+
"strict",
|
|
20
|
+
...(modelId ? ["--model", modelId] : []),
|
|
21
|
+
...(modelId && reasoningEffort && reasoningEffort !== "off" ? ["--reasoning-effort", reasoningEffort] : []),
|
|
22
|
+
"agent",
|
|
23
|
+
"--no-leader",
|
|
24
|
+
"stdio",
|
|
25
|
+
];
|
|
26
|
+
function processCrash(code, signal, diagnostic) {
|
|
27
|
+
const reason = code === null ? `signal ${signal ?? "unknown"}` : `exit code ${code}`;
|
|
28
|
+
return new GrokRuntimeError("process_crashed", `Grok runtime stopped unexpectedly (${reason}).`, diagnostic || undefined);
|
|
29
|
+
}
|
|
30
|
+
const MAX_GROK_STDERR_RAW_BYTES = 64 * 1024;
|
|
31
|
+
/**
|
|
32
|
+
* Reassemble stderr before redaction so secrets split across arbitrary child
|
|
33
|
+
* process chunks cannot bypass token patterns. If the private in-memory raw
|
|
34
|
+
* buffer exceeds its cap, discard it wholesale and expose only a fixed notice.
|
|
35
|
+
*/
|
|
36
|
+
export class GrokDiagnosticBuffer {
|
|
37
|
+
raw = "";
|
|
38
|
+
overflowed = false;
|
|
39
|
+
append(chunk) {
|
|
40
|
+
if (this.overflowed)
|
|
41
|
+
return;
|
|
42
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
43
|
+
if (Buffer.byteLength(this.raw, "utf8") + Buffer.byteLength(text, "utf8") > MAX_GROK_STDERR_RAW_BYTES) {
|
|
44
|
+
this.raw = "";
|
|
45
|
+
this.overflowed = true;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.raw += text;
|
|
49
|
+
}
|
|
50
|
+
sanitized(maxLength = 2048) {
|
|
51
|
+
if (this.overflowed)
|
|
52
|
+
return "Grok ACP stderr exceeded the safe diagnostic limit.";
|
|
53
|
+
return sanitizeGrokDiagnostic(this.raw, maxLength);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isRecord(value) {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
function parseModelState(value) {
|
|
60
|
+
if (!isRecord(value) || typeof value.currentModelId !== "string" || !Array.isArray(value.availableModels))
|
|
61
|
+
return undefined;
|
|
62
|
+
const availableModels = [];
|
|
63
|
+
for (const item of value.availableModels) {
|
|
64
|
+
if (!isRecord(item))
|
|
65
|
+
return undefined;
|
|
66
|
+
const id = typeof item.modelId === "string" ? item.modelId : typeof item.id === "string" ? item.id : undefined;
|
|
67
|
+
if (!id)
|
|
68
|
+
return undefined;
|
|
69
|
+
availableModels.push({
|
|
70
|
+
id,
|
|
71
|
+
name: typeof item.name === "string" ? item.name : id,
|
|
72
|
+
...(typeof item.description === "string" ? { description: item.description } : {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (availableModels.length === 0 || !availableModels.some((model) => model.id === value.currentModelId))
|
|
76
|
+
return undefined;
|
|
77
|
+
return { currentModelId: value.currentModelId, availableModels };
|
|
78
|
+
}
|
|
79
|
+
function parseSessionResponse(value, fallbackId) {
|
|
80
|
+
if (!isRecord(value))
|
|
81
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP returned an invalid session response.");
|
|
82
|
+
const id = typeof value.sessionId === "string" ? value.sessionId : fallbackId;
|
|
83
|
+
if (!id)
|
|
84
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP session response omitted its session ID.");
|
|
85
|
+
const models = value.models === undefined ? undefined : parseModelState(value.models);
|
|
86
|
+
if (value.models !== undefined && !models) {
|
|
87
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP returned an invalid model catalog.");
|
|
88
|
+
}
|
|
89
|
+
return { id, ...(models ? { models } : {}) };
|
|
90
|
+
}
|
|
91
|
+
function parseInterceptedSessionUpdate(message) {
|
|
92
|
+
if (!("method" in message) || message.method !== acp.methods.client.session.update)
|
|
93
|
+
return undefined;
|
|
94
|
+
if ("id" in message) {
|
|
95
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP sent session/update as a request.");
|
|
96
|
+
}
|
|
97
|
+
if (!isRecord(message.params)
|
|
98
|
+
|| typeof message.params.sessionId !== "string"
|
|
99
|
+
|| !isRecord(message.params.update)
|
|
100
|
+
|| typeof message.params.update.sessionUpdate !== "string") {
|
|
101
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP sent an invalid session update.");
|
|
102
|
+
}
|
|
103
|
+
if (message.params._meta !== undefined && message.params._meta !== null && !isRecord(message.params._meta)) {
|
|
104
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP sent invalid session metadata.");
|
|
105
|
+
}
|
|
106
|
+
return message.params;
|
|
107
|
+
}
|
|
108
|
+
async function settlesWithin(promise, timeoutMs) {
|
|
109
|
+
return await new Promise((resolve) => {
|
|
110
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
111
|
+
timer.unref?.();
|
|
112
|
+
void promise.then(() => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
resolve(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
class DefaultGrokAcpClient {
|
|
119
|
+
options;
|
|
120
|
+
child;
|
|
121
|
+
connection;
|
|
122
|
+
stderr = new GrokDiagnosticBuffer();
|
|
123
|
+
disposed = false;
|
|
124
|
+
initialized = false;
|
|
125
|
+
crashReported = false;
|
|
126
|
+
crashError;
|
|
127
|
+
exited;
|
|
128
|
+
notificationTail = Promise.resolve();
|
|
129
|
+
constructor(options) {
|
|
130
|
+
this.options = options;
|
|
131
|
+
const child = options.spawn(options.binary, GROK_ACP_ARGS(options.workspace, options.modelId, options.reasoningEffort, options.permissionMode), {
|
|
132
|
+
cwd: options.workspace,
|
|
133
|
+
env: options.env,
|
|
134
|
+
shell: false,
|
|
135
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
136
|
+
});
|
|
137
|
+
this.child = child;
|
|
138
|
+
this.exited = new Promise((resolve) => {
|
|
139
|
+
child.once("exit", () => resolve());
|
|
140
|
+
child.once("error", () => resolve());
|
|
141
|
+
});
|
|
142
|
+
if (!child.stdin || !child.stdout) {
|
|
143
|
+
child.kill();
|
|
144
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP did not provide stdio pipes.");
|
|
145
|
+
}
|
|
146
|
+
child.stderr?.on("data", (chunk) => {
|
|
147
|
+
this.stderr.append(chunk);
|
|
148
|
+
});
|
|
149
|
+
child.once("error", () => this.reportCrash(processCrash(null, null, this.stderr.sanitized())));
|
|
150
|
+
child.once("exit", (code, signal) => {
|
|
151
|
+
if (!this.disposed)
|
|
152
|
+
this.reportCrash(processCrash(code, signal, this.stderr.sanitized()));
|
|
153
|
+
});
|
|
154
|
+
const rejectHostCapability = async (sessionId) => {
|
|
155
|
+
await this.enqueuePermissionViolation(sessionId);
|
|
156
|
+
throw acp.RequestError.requestCancelled(undefined, "This host capability is not exposed by Bubble's Grok runtime.");
|
|
157
|
+
};
|
|
158
|
+
const app = acp
|
|
159
|
+
.client({ name: "bubble-grok-runtime" })
|
|
160
|
+
.onRequest(acp.methods.client.session.requestPermission, async ({ params }) => {
|
|
161
|
+
const optionId = await options.handlers.onPermissionRequest(params);
|
|
162
|
+
if (!optionId || !params.options.some((option) => option.optionId === optionId)) {
|
|
163
|
+
return { outcome: { outcome: "cancelled" } };
|
|
164
|
+
}
|
|
165
|
+
return { outcome: { outcome: "selected", optionId } };
|
|
166
|
+
})
|
|
167
|
+
.onRequest(acp.methods.client.fs.readTextFile, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
168
|
+
.onRequest(acp.methods.client.fs.writeTextFile, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
169
|
+
.onRequest(acp.methods.client.terminal.create, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
170
|
+
.onRequest(acp.methods.client.terminal.output, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
171
|
+
.onRequest(acp.methods.client.terminal.release, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
172
|
+
.onRequest(acp.methods.client.terminal.waitForExit, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
173
|
+
.onRequest(acp.methods.client.terminal.kill, async ({ params }) => await rejectHostCapability(params.sessionId))
|
|
174
|
+
.onRequest(acp.methods.client.elicitation.create, async ({ params }) => await rejectHostCapability("sessionId" in params && typeof params.sessionId === "string" ? params.sessionId : undefined))
|
|
175
|
+
.onNotification(acp.methods.client.elicitation.complete, () => this.enqueuePermissionViolation(undefined));
|
|
176
|
+
const input = Writable.toWeb(child.stdin);
|
|
177
|
+
const output = Readable.toWeb(child.stdout);
|
|
178
|
+
const stream = createBoundedGrokNdjsonStream(input, output, {
|
|
179
|
+
interceptIncoming: (message) => {
|
|
180
|
+
const notification = parseInterceptedSessionUpdate(message);
|
|
181
|
+
if (!notification)
|
|
182
|
+
return false;
|
|
183
|
+
void this.enqueueSessionUpdate(notification);
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
186
|
+
onProtocolError: (error) => {
|
|
187
|
+
this.reportCrash(error);
|
|
188
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
189
|
+
child.kill("SIGTERM");
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
this.connection = app.connect(stream);
|
|
193
|
+
}
|
|
194
|
+
reportCrash(error) {
|
|
195
|
+
if (this.crashReported || this.disposed)
|
|
196
|
+
return;
|
|
197
|
+
this.crashReported = true;
|
|
198
|
+
this.crashError = error;
|
|
199
|
+
this.options.handlers.onCrash(error);
|
|
200
|
+
}
|
|
201
|
+
enqueueSessionUpdate(notification) {
|
|
202
|
+
const work = this.notificationTail.then(async () => {
|
|
203
|
+
try {
|
|
204
|
+
await this.options.handlers.onSessionUpdate(notification);
|
|
205
|
+
}
|
|
206
|
+
catch (cause) {
|
|
207
|
+
const error = cause instanceof GrokRuntimeError
|
|
208
|
+
? cause
|
|
209
|
+
: new GrokRuntimeError("protocol_error", "Grok ACP notification handling failed.");
|
|
210
|
+
this.reportCrash(error);
|
|
211
|
+
if (this.child.exitCode === null && this.child.signalCode === null)
|
|
212
|
+
this.child.kill("SIGTERM");
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
this.notificationTail = work;
|
|
216
|
+
return work;
|
|
217
|
+
}
|
|
218
|
+
enqueuePermissionViolation(sessionId) {
|
|
219
|
+
const work = this.notificationTail.then(async () => {
|
|
220
|
+
try {
|
|
221
|
+
await this.options.handlers.onPermissionViolation(sessionId);
|
|
222
|
+
}
|
|
223
|
+
catch (cause) {
|
|
224
|
+
const error = cause instanceof GrokRuntimeError
|
|
225
|
+
? cause
|
|
226
|
+
: new GrokRuntimeError("protocol_error", "Grok ACP policy handling failed.");
|
|
227
|
+
this.reportCrash(error);
|
|
228
|
+
if (this.child.exitCode === null && this.child.signalCode === null)
|
|
229
|
+
this.child.kill("SIGTERM");
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
this.notificationTail = work;
|
|
233
|
+
return work;
|
|
234
|
+
}
|
|
235
|
+
async drainNotifications() {
|
|
236
|
+
// Incoming requests/notifications are dispatched independently from the
|
|
237
|
+
// response promise. Let already-parsed work join the ordered tail before
|
|
238
|
+
// deciding that initialize/new/load completed safely.
|
|
239
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
240
|
+
await this.notificationTail;
|
|
241
|
+
if (this.crashError)
|
|
242
|
+
throw this.crashError;
|
|
243
|
+
}
|
|
244
|
+
async initialize() {
|
|
245
|
+
if (this.initialized)
|
|
246
|
+
return;
|
|
247
|
+
let response;
|
|
248
|
+
try {
|
|
249
|
+
response = await this.connection.agent.request(acp.methods.agent.initialize, {
|
|
250
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
251
|
+
clientCapabilities: { session: { configOptions: { boolean: {} } } },
|
|
252
|
+
clientInfo: { name: "bubble", title: "Bubble", version: "1" },
|
|
253
|
+
});
|
|
254
|
+
await this.drainNotifications();
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
if (this.crashError)
|
|
258
|
+
throw this.crashError;
|
|
259
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP initialization failed.", this.stderr.sanitized() || undefined);
|
|
260
|
+
}
|
|
261
|
+
if (response.protocolVersion !== acp.PROTOCOL_VERSION) {
|
|
262
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP returned an unsupported protocol version.");
|
|
263
|
+
}
|
|
264
|
+
if (response.agentCapabilities?.loadSession !== true) {
|
|
265
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP must support session loading for isolated turn rotation.");
|
|
266
|
+
}
|
|
267
|
+
const authMethods = response.authMethods ?? [];
|
|
268
|
+
const cached = authMethods.find((method) => method.id === "cached_token");
|
|
269
|
+
if (!cached) {
|
|
270
|
+
throw new GrokRuntimeError("not_authenticated", "Sign in to Grok before starting a subscription session.");
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
await this.connection.agent.request(acp.methods.agent.authenticate, { methodId: "cached_token" });
|
|
274
|
+
await this.drainNotifications();
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
if (this.crashError)
|
|
278
|
+
throw this.crashError;
|
|
279
|
+
throw new GrokRuntimeError("not_authenticated", "The cached Grok login is unavailable or expired.");
|
|
280
|
+
}
|
|
281
|
+
this.initialized = true;
|
|
282
|
+
}
|
|
283
|
+
async newSession() {
|
|
284
|
+
await this.initialize();
|
|
285
|
+
try {
|
|
286
|
+
const response = await this.connection.agent.request("session/new", {
|
|
287
|
+
cwd: this.options.workspace,
|
|
288
|
+
additionalDirectories: [],
|
|
289
|
+
mcpServers: [],
|
|
290
|
+
});
|
|
291
|
+
await this.drainNotifications();
|
|
292
|
+
return parseSessionResponse(response);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (this.crashError)
|
|
296
|
+
throw this.crashError;
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async loadSession(sessionId) {
|
|
301
|
+
await this.initialize();
|
|
302
|
+
try {
|
|
303
|
+
const response = await this.connection.agent.request("session/load", {
|
|
304
|
+
sessionId,
|
|
305
|
+
cwd: this.options.workspace,
|
|
306
|
+
additionalDirectories: [],
|
|
307
|
+
mcpServers: [],
|
|
308
|
+
});
|
|
309
|
+
await this.drainNotifications();
|
|
310
|
+
return parseSessionResponse(response, sessionId);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (this.crashError)
|
|
314
|
+
throw this.crashError;
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async setModel(sessionId, modelId) {
|
|
319
|
+
await this.initialize();
|
|
320
|
+
try {
|
|
321
|
+
const response = await this.connection.agent.request("session/set_model", { sessionId, modelId });
|
|
322
|
+
await this.drainNotifications();
|
|
323
|
+
if (!isRecord(response))
|
|
324
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP returned an invalid model switch response.");
|
|
325
|
+
if (response.models === undefined)
|
|
326
|
+
return undefined;
|
|
327
|
+
const models = parseModelState(response.models);
|
|
328
|
+
if (!models)
|
|
329
|
+
throw new GrokRuntimeError("protocol_error", "Grok ACP returned an invalid model catalog.");
|
|
330
|
+
return models;
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (this.crashError)
|
|
334
|
+
throw this.crashError;
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async prompt(sessionId, prompt, generation, signal) {
|
|
339
|
+
await this.initialize();
|
|
340
|
+
try {
|
|
341
|
+
const response = await this.connection.agent.request(acp.methods.agent.session.prompt, {
|
|
342
|
+
sessionId,
|
|
343
|
+
prompt: [{ type: "text", text: prompt }],
|
|
344
|
+
_meta: { bubbleGeneration: generation },
|
|
345
|
+
}, { cancellationSignal: signal });
|
|
346
|
+
// The SDK dispatches notification handlers independently from response
|
|
347
|
+
// settlement. Yield once so all notifications parsed before the prompt
|
|
348
|
+
// response can join our ordered tail, then drain it before rotating the
|
|
349
|
+
// sidecar at the turn boundary.
|
|
350
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
351
|
+
await this.notificationTail;
|
|
352
|
+
return response;
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
// Stream closure can reject the request one microtask before ChildProcess
|
|
356
|
+
// emits exit. Give the exit handler a chance to preserve the safe crash
|
|
357
|
+
// diagnostic instead of collapsing it into a generic protocol error.
|
|
358
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
359
|
+
if (this.crashError)
|
|
360
|
+
throw this.crashError;
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async cancel(sessionId) {
|
|
365
|
+
if (this.disposed)
|
|
366
|
+
return;
|
|
367
|
+
await this.connection.agent.notify(acp.methods.agent.session.cancel, { sessionId });
|
|
368
|
+
}
|
|
369
|
+
async dispose() {
|
|
370
|
+
if (this.disposed)
|
|
371
|
+
return;
|
|
372
|
+
this.disposed = true;
|
|
373
|
+
this.connection.close();
|
|
374
|
+
this.child.stdin?.end();
|
|
375
|
+
let stopped = await settlesWithin(this.exited, 250);
|
|
376
|
+
if (!stopped && this.child.exitCode === null && this.child.signalCode === null) {
|
|
377
|
+
this.child.kill("SIGTERM");
|
|
378
|
+
stopped = await settlesWithin(this.exited, 1_000);
|
|
379
|
+
}
|
|
380
|
+
if (!stopped && this.child.exitCode === null && this.child.signalCode === null) {
|
|
381
|
+
this.child.kill("SIGKILL");
|
|
382
|
+
await settlesWithin(this.exited, 1_000);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
export const createDefaultGrokAcpClient = (options) => new DefaultGrokAcpClient(options);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ExternalRuntimeBinaryInfo } from "./types.js";
|
|
2
|
+
export declare const GROK_PINNED_VERSION = "0.2.93";
|
|
3
|
+
export declare const GROK_PINNED_SHA256 = "2a97ba675bd992aa9b981e2e83776460d94f469b510c0b8efe28b50d236d767c";
|
|
4
|
+
export interface GrokBinaryDependencies {
|
|
5
|
+
platform?: NodeJS.Platform;
|
|
6
|
+
arch?: string;
|
|
7
|
+
uid?: number;
|
|
8
|
+
binaryPath?: string;
|
|
9
|
+
pathEnv?: string;
|
|
10
|
+
userHome?: string;
|
|
11
|
+
expectedVersion?: string;
|
|
12
|
+
expectedSha256?: string;
|
|
13
|
+
hashFile?: (path: string) => Promise<string>;
|
|
14
|
+
readVersion?: (path: string) => Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveGrokBinary(deps?: GrokBinaryDependencies): Promise<string>;
|
|
17
|
+
export declare function sha256File(path: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* The executable's directory chain is part of the trust boundary. Even a
|
|
20
|
+
* read-only, user-owned file can be swapped between verification and spawn if
|
|
21
|
+
* another local user can rename a writable ancestor directory entry.
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyGrokBinaryAncestors(path: string, uid?: number | undefined): Promise<void>;
|
|
24
|
+
export declare function parseGrokVersion(output: string): string | undefined;
|
|
25
|
+
export declare function verifyGrokBinary(deps?: GrokBinaryDependencies): Promise<ExternalRuntimeBinaryInfo>;
|