@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
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { constants as fsConstants, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { chmod, lstat, mkdir, open, readFile, realpath, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { getBubbleHome } from "../bubble-home.js";
|
|
5
|
+
import { GrokRuntimeError } from "./grok-errors.js";
|
|
6
|
+
const DIRECTORY_MODE = 0o700;
|
|
7
|
+
const FILE_MODE = 0o600;
|
|
8
|
+
// Skills the pinned Grok CLI bundles and can materialize on its own. They are
|
|
9
|
+
// disabled by name because the runtime re-creates their files at startup.
|
|
10
|
+
const GROK_BUNDLED_SKILLS = [
|
|
11
|
+
"code-review",
|
|
12
|
+
"xlsx",
|
|
13
|
+
"create-skill",
|
|
14
|
+
"pptx",
|
|
15
|
+
"check-work",
|
|
16
|
+
"docx",
|
|
17
|
+
"imagine",
|
|
18
|
+
"help",
|
|
19
|
+
];
|
|
20
|
+
// `[skills] disabled` entries are written into TOML basic strings. Only accept
|
|
21
|
+
// names that cannot alter the document structure or smuggle escapes.
|
|
22
|
+
const SAFE_GROK_SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
23
|
+
export function isSafeGrokSkillName(name) {
|
|
24
|
+
return SAFE_GROK_SKILL_NAME.test(name);
|
|
25
|
+
}
|
|
26
|
+
function grokDisabledSkillsLine(extraDisabledSkills) {
|
|
27
|
+
for (const name of extraDisabledSkills) {
|
|
28
|
+
if (!isSafeGrokSkillName(name)) {
|
|
29
|
+
throw new GrokRuntimeError("profile_unsafe", "A discovered Grok skill name cannot be disabled safely.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const disabled = [...new Set([...GROK_BUNDLED_SKILLS, ...extraDisabledSkills])].sort();
|
|
33
|
+
return `disabled = [${disabled.map((name) => JSON.stringify(name)).join(", ")}]`;
|
|
34
|
+
}
|
|
35
|
+
// Keep credentials and runtime configuration isolated while allowing the
|
|
36
|
+
// official Grok runtime to work inside the user's selected workspace. Tool
|
|
37
|
+
// execution is still gated by ACP permission requests handled by Bubble.
|
|
38
|
+
export function buildGrokConfigToml(extraDisabledSkills = []) {
|
|
39
|
+
return `[cli]
|
|
40
|
+
auto_update = false
|
|
41
|
+
|
|
42
|
+
[session]
|
|
43
|
+
load_envrc = false
|
|
44
|
+
|
|
45
|
+
[sandbox]
|
|
46
|
+
profile = "strict"
|
|
47
|
+
auto_allow_bash = false
|
|
48
|
+
|
|
49
|
+
[features]
|
|
50
|
+
web_fetch = false
|
|
51
|
+
write_file = true
|
|
52
|
+
tool_search = false
|
|
53
|
+
lsp_tools = false
|
|
54
|
+
|
|
55
|
+
[subagents]
|
|
56
|
+
enabled = false
|
|
57
|
+
|
|
58
|
+
[memory]
|
|
59
|
+
enabled = false
|
|
60
|
+
|
|
61
|
+
[skills]
|
|
62
|
+
${grokDisabledSkillsLine(extraDisabledSkills)}
|
|
63
|
+
|
|
64
|
+
[compat.cursor]
|
|
65
|
+
skills = false
|
|
66
|
+
rules = false
|
|
67
|
+
agents = false
|
|
68
|
+
mcps = false
|
|
69
|
+
hooks = false
|
|
70
|
+
|
|
71
|
+
[compat.claude]
|
|
72
|
+
skills = false
|
|
73
|
+
rules = false
|
|
74
|
+
agents = false
|
|
75
|
+
mcps = false
|
|
76
|
+
hooks = false
|
|
77
|
+
|
|
78
|
+
# Grok's Claude permission compatibility is independent from the extension
|
|
79
|
+
# compatibility switches above. Mark the one-time import as complete so the
|
|
80
|
+
# runtime never reads workspace .claude/settings*.json permission rules.
|
|
81
|
+
[claude_compat]
|
|
82
|
+
imported = true
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
export function buildGrokRequirementsToml(extraDisabledSkills = []) {
|
|
86
|
+
return `[cli]
|
|
87
|
+
auto_update = false
|
|
88
|
+
|
|
89
|
+
[session]
|
|
90
|
+
load_envrc = false
|
|
91
|
+
|
|
92
|
+
[sandbox]
|
|
93
|
+
profile = "strict"
|
|
94
|
+
auto_allow_bash = false
|
|
95
|
+
|
|
96
|
+
[features]
|
|
97
|
+
web_fetch = false
|
|
98
|
+
write_file = true
|
|
99
|
+
tool_search = false
|
|
100
|
+
lsp_tools = false
|
|
101
|
+
|
|
102
|
+
[subagents]
|
|
103
|
+
enabled = false
|
|
104
|
+
|
|
105
|
+
[memory]
|
|
106
|
+
enabled = false
|
|
107
|
+
|
|
108
|
+
[skills]
|
|
109
|
+
${grokDisabledSkillsLine(extraDisabledSkills)}
|
|
110
|
+
|
|
111
|
+
[compat.cursor]
|
|
112
|
+
skills = false
|
|
113
|
+
rules = false
|
|
114
|
+
agents = false
|
|
115
|
+
mcps = false
|
|
116
|
+
hooks = false
|
|
117
|
+
|
|
118
|
+
[compat.claude]
|
|
119
|
+
skills = false
|
|
120
|
+
rules = false
|
|
121
|
+
agents = false
|
|
122
|
+
mcps = false
|
|
123
|
+
hooks = false
|
|
124
|
+
|
|
125
|
+
[claude_compat]
|
|
126
|
+
imported = true
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
export const GROK_CONFIG_TOML = buildGrokConfigToml();
|
|
130
|
+
export const GROK_REQUIREMENTS_TOML = buildGrokRequirementsToml();
|
|
131
|
+
export function getGrokProfile(bubbleHome = getBubbleHome()) {
|
|
132
|
+
const root = resolve(bubbleHome, "runtimes", "grok");
|
|
133
|
+
const grokHome = join(root, "grok-home");
|
|
134
|
+
return {
|
|
135
|
+
root,
|
|
136
|
+
home: join(root, "home"),
|
|
137
|
+
grokHome,
|
|
138
|
+
tmp: join(root, "tmp"),
|
|
139
|
+
workspace: join(root, "workspace"),
|
|
140
|
+
configPath: join(grokHome, "config.toml"),
|
|
141
|
+
requirementsPath: join(grokHome, "requirements.toml"),
|
|
142
|
+
lockPath: join(root, "runtime.lock"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function ensurePrivateDirectory(path, uid, repairMode = true) {
|
|
146
|
+
let created = false;
|
|
147
|
+
try {
|
|
148
|
+
const before = await lstat(path);
|
|
149
|
+
if (!before.isDirectory() || before.isSymbolicLink() || before.uid !== uid) {
|
|
150
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok profile contains an unsafe path.");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error.code !== "ENOENT")
|
|
155
|
+
throw error;
|
|
156
|
+
await mkdir(path, { recursive: true, mode: DIRECTORY_MODE });
|
|
157
|
+
created = true;
|
|
158
|
+
}
|
|
159
|
+
if (created || repairMode)
|
|
160
|
+
await chmod(path, DIRECTORY_MODE);
|
|
161
|
+
const after = await stat(path);
|
|
162
|
+
if (!after.isDirectory() || after.uid !== uid || (after.mode & 0o077) !== 0) {
|
|
163
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok profile must be private to the current user.");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Establish only the private directory needed to create runtime.lock. Existing
|
|
168
|
+
* profile content is validated but never chmodded or rewritten here, because
|
|
169
|
+
* another Bubble process may already own the lock and be using that profile.
|
|
170
|
+
*/
|
|
171
|
+
export async function prepareGrokProfileLockRoot(profile = getGrokProfile(), uid = process.getuid?.()) {
|
|
172
|
+
if (uid === undefined) {
|
|
173
|
+
throw new GrokRuntimeError("unsupported_platform", "The Grok runtime requires a POSIX user account.");
|
|
174
|
+
}
|
|
175
|
+
await ensurePrivateDirectory(dirname(profile.root), uid, false);
|
|
176
|
+
await ensurePrivateDirectory(profile.root, uid, false);
|
|
177
|
+
return profile;
|
|
178
|
+
}
|
|
179
|
+
async function writePrivateFile(path, contents, uid) {
|
|
180
|
+
try {
|
|
181
|
+
const existing = await lstat(path);
|
|
182
|
+
if (!existing.isFile() || existing.isSymbolicLink() || existing.uid !== uid) {
|
|
183
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok profile contains an unsafe configuration file.");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (error.code !== "ENOENT")
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
const handle = await open(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_TRUNC | fsConstants.O_NOFOLLOW, FILE_MODE);
|
|
191
|
+
try {
|
|
192
|
+
await handle.writeFile(contents, { encoding: "utf8" });
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
await handle.close();
|
|
196
|
+
}
|
|
197
|
+
await chmod(path, FILE_MODE);
|
|
198
|
+
const metadata = await stat(path);
|
|
199
|
+
if (!metadata.isFile() || metadata.uid !== uid || (metadata.mode & 0o077) !== 0) {
|
|
200
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok configuration must be private to the current user.");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export async function prepareGrokProfile(profile = getGrokProfile(), uid = process.getuid?.(), extraDisabledSkills = []) {
|
|
204
|
+
if (uid === undefined) {
|
|
205
|
+
throw new GrokRuntimeError("unsupported_platform", "The Grok runtime requires a POSIX user account.");
|
|
206
|
+
}
|
|
207
|
+
await ensurePrivateDirectory(dirname(profile.root), uid);
|
|
208
|
+
await ensurePrivateDirectory(profile.root, uid);
|
|
209
|
+
for (const path of [profile.home, profile.grokHome, profile.tmp, profile.workspace]) {
|
|
210
|
+
await ensurePrivateDirectory(path, uid);
|
|
211
|
+
}
|
|
212
|
+
const canonicalRoot = await realpath(profile.root);
|
|
213
|
+
for (const path of [profile.home, profile.grokHome, profile.tmp, profile.workspace]) {
|
|
214
|
+
const canonical = await realpath(path);
|
|
215
|
+
if (canonical !== canonicalRoot && !canonical.startsWith(`${canonicalRoot}${sep}`)) {
|
|
216
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok profile escaped its private root.");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
await writePrivateFile(profile.configPath, buildGrokConfigToml(extraDisabledSkills), uid);
|
|
220
|
+
await writePrivateFile(profile.requirementsPath, buildGrokRequirementsToml(extraDisabledSkills), uid);
|
|
221
|
+
return profile;
|
|
222
|
+
}
|
|
223
|
+
export function buildGrokChildEnv(profile, parentEnv = process.env, proxy) {
|
|
224
|
+
const env = {
|
|
225
|
+
HOME: profile.home,
|
|
226
|
+
GROK_HOME: profile.grokHome,
|
|
227
|
+
TMPDIR: profile.tmp,
|
|
228
|
+
PATH: "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
229
|
+
GROK_DISABLE_AUTOUPDATER: "1",
|
|
230
|
+
GROK_SANDBOX: "strict",
|
|
231
|
+
GROK_SANDBOX_AUTO_ALLOW_BASH: "0",
|
|
232
|
+
GROK_MEMORY: "0",
|
|
233
|
+
GROK_SUBAGENTS: "0",
|
|
234
|
+
GROK_WEB_FETCH: "0",
|
|
235
|
+
GROK_WRITE_FILE: "1",
|
|
236
|
+
GROK_TOOL_SEARCH: "0",
|
|
237
|
+
GROK_LSP_TOOLS: "0",
|
|
238
|
+
GROK_CURSOR_SKILLS_ENABLED: "0",
|
|
239
|
+
GROK_CURSOR_RULES_ENABLED: "0",
|
|
240
|
+
GROK_CURSOR_AGENTS_ENABLED: "0",
|
|
241
|
+
GROK_CURSOR_MCPS_ENABLED: "0",
|
|
242
|
+
GROK_CURSOR_HOOKS_ENABLED: "0",
|
|
243
|
+
GROK_CLAUDE_SKILLS_ENABLED: "0",
|
|
244
|
+
GROK_CLAUDE_RULES_ENABLED: "0",
|
|
245
|
+
GROK_CLAUDE_AGENTS_ENABLED: "0",
|
|
246
|
+
GROK_CLAUDE_MCPS_ENABLED: "0",
|
|
247
|
+
GROK_CLAUDE_HOOKS_ENABLED: "0",
|
|
248
|
+
// Pinned Grok 0.2.93 has a separate Claude permission compatibility gate.
|
|
249
|
+
// Keep its exact process override as defense in depth for every child.
|
|
250
|
+
_GROK_CLAUDE_MARKER_OVERRIDE: "1",
|
|
251
|
+
GROK_CRASH_HANDLER: "0",
|
|
252
|
+
};
|
|
253
|
+
const uid = process.getuid?.();
|
|
254
|
+
const safePath = parentEnv.PATH?.split(":").filter((entry) => {
|
|
255
|
+
if (!entry.startsWith("/") || /[\r\n\0]/.test(entry))
|
|
256
|
+
return false;
|
|
257
|
+
try {
|
|
258
|
+
const canonical = realpathSync(entry);
|
|
259
|
+
const metadata = statSync(canonical);
|
|
260
|
+
return metadata.isDirectory()
|
|
261
|
+
&& (uid === undefined || metadata.uid === 0 || metadata.uid === uid)
|
|
262
|
+
&& (metadata.mode & 0o022) === 0;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
if (safePath?.length)
|
|
269
|
+
env.PATH = [...new Set(safePath)].join(":");
|
|
270
|
+
for (const name of ["LANG", "LC_ALL", "TERM", "COLORTERM", "NO_COLOR"]) {
|
|
271
|
+
const value = parentEnv[name];
|
|
272
|
+
if (value && !/[\r\n\0]/.test(value))
|
|
273
|
+
env[name] = value;
|
|
274
|
+
}
|
|
275
|
+
if (proxy) {
|
|
276
|
+
env.HTTPS_PROXY = proxy;
|
|
277
|
+
env.ALL_PROXY = proxy;
|
|
278
|
+
}
|
|
279
|
+
return env;
|
|
280
|
+
}
|
|
281
|
+
export async function acquireGrokProfileLock(profile) {
|
|
282
|
+
const uid = process.getuid?.();
|
|
283
|
+
const openNewLock = async () => await open(profile.lockPath, "wx", FILE_MODE);
|
|
284
|
+
let handle;
|
|
285
|
+
try {
|
|
286
|
+
handle = await openNewLock();
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error.code !== "EEXIST")
|
|
290
|
+
throw error;
|
|
291
|
+
const metadata = await lstat(profile.lockPath);
|
|
292
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || uid === undefined || metadata.uid !== uid || (metadata.mode & 0o077) !== 0) {
|
|
293
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok runtime lock is unsafe.");
|
|
294
|
+
}
|
|
295
|
+
const rawPid = (await readFile(profile.lockPath, "utf8")).trim();
|
|
296
|
+
if (!/^[1-9][0-9]*$/.test(rawPid)) {
|
|
297
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok runtime lock is invalid.");
|
|
298
|
+
}
|
|
299
|
+
const pid = Number(rawPid);
|
|
300
|
+
if (!Number.isSafeInteger(pid) || pid > 2_147_483_647) {
|
|
301
|
+
throw new GrokRuntimeError("profile_unsafe", "The isolated Grok runtime lock is invalid.");
|
|
302
|
+
}
|
|
303
|
+
let stale = false;
|
|
304
|
+
try {
|
|
305
|
+
process.kill(pid, 0);
|
|
306
|
+
}
|
|
307
|
+
catch (probeError) {
|
|
308
|
+
if (probeError.code === "ESRCH")
|
|
309
|
+
stale = true;
|
|
310
|
+
else
|
|
311
|
+
throw new GrokRuntimeError("profile_locked", "The isolated Grok runtime is already in use.");
|
|
312
|
+
}
|
|
313
|
+
if (!stale) {
|
|
314
|
+
throw new GrokRuntimeError("profile_locked", "The isolated Grok runtime is already in use.");
|
|
315
|
+
}
|
|
316
|
+
await rm(profile.lockPath);
|
|
317
|
+
try {
|
|
318
|
+
handle = await openNewLock();
|
|
319
|
+
}
|
|
320
|
+
catch (retryError) {
|
|
321
|
+
if (retryError.code === "EEXIST") {
|
|
322
|
+
throw new GrokRuntimeError("profile_locked", "The isolated Grok runtime is already in use.");
|
|
323
|
+
}
|
|
324
|
+
throw retryError;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
await handle.writeFile(`${process.pid}\n`, { encoding: "utf8" });
|
|
328
|
+
let released = false;
|
|
329
|
+
return {
|
|
330
|
+
async release() {
|
|
331
|
+
if (released)
|
|
332
|
+
return;
|
|
333
|
+
released = true;
|
|
334
|
+
await handle.close();
|
|
335
|
+
await rm(profile.lockPath, { force: true });
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
export async function grokProfileHasAuth(profile) {
|
|
340
|
+
for (const path of [join(profile.grokHome, "auth.json"), join(profile.grokHome, "auth")]) {
|
|
341
|
+
try {
|
|
342
|
+
const metadata = await lstat(path);
|
|
343
|
+
if (metadata.isFile() || metadata.isDirectory())
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
if (error.code !== "ENOENT")
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
export async function clearGrokRuntimeData(profile, includeAuth) {
|
|
354
|
+
const names = [
|
|
355
|
+
"sessions",
|
|
356
|
+
"logs",
|
|
357
|
+
"log",
|
|
358
|
+
"crash",
|
|
359
|
+
"leader.sock",
|
|
360
|
+
"leader.pid",
|
|
361
|
+
"socket",
|
|
362
|
+
"sockets",
|
|
363
|
+
"active_sessions.json",
|
|
364
|
+
"active_sessions.lock",
|
|
365
|
+
"sandbox-events.jsonl",
|
|
366
|
+
"auth.json.lock",
|
|
367
|
+
];
|
|
368
|
+
if (includeAuth)
|
|
369
|
+
names.push("auth.json", "auth");
|
|
370
|
+
for (const name of names) {
|
|
371
|
+
await rm(join(profile.grokHome, name), { recursive: true, force: true });
|
|
372
|
+
}
|
|
373
|
+
// OAuth helpers may place browser hand-off logs or sockets under TMPDIR.
|
|
374
|
+
// The directory is dedicated to this isolated runtime, so clear and
|
|
375
|
+
// recreate it at every authentication boundary.
|
|
376
|
+
await rm(profile.tmp, { recursive: true, force: true });
|
|
377
|
+
await mkdir(profile.tmp, { mode: DIRECTORY_MODE });
|
|
378
|
+
await chmod(profile.tmp, DIRECTORY_MODE);
|
|
379
|
+
// The process workspace is intentionally disposable and never points at the user's project.
|
|
380
|
+
await rm(profile.workspace, { recursive: true, force: true });
|
|
381
|
+
await mkdir(profile.workspace, { mode: DIRECTORY_MODE });
|
|
382
|
+
await chmod(profile.workspace, DIRECTORY_MODE);
|
|
383
|
+
}
|
|
384
|
+
/** Remove extension surfaces that the pinned CLI may materialize at startup. */
|
|
385
|
+
export async function clearGrokGeneratedExtensions(profile) {
|
|
386
|
+
for (const name of [
|
|
387
|
+
"skills",
|
|
388
|
+
"plugins",
|
|
389
|
+
"marketplaces",
|
|
390
|
+
"hooks",
|
|
391
|
+
"agents",
|
|
392
|
+
"mcp",
|
|
393
|
+
"lsp",
|
|
394
|
+
"docs",
|
|
395
|
+
// The pinned CLI can materialize transcript-bearing archives and logs
|
|
396
|
+
// even with memory disabled. They are never inputs to a later turn.
|
|
397
|
+
"upload_queue",
|
|
398
|
+
"logs",
|
|
399
|
+
"log",
|
|
400
|
+
"crash",
|
|
401
|
+
"sandbox-events.jsonl",
|
|
402
|
+
"README.md",
|
|
403
|
+
".config-init.lock",
|
|
404
|
+
"managed_config.lock",
|
|
405
|
+
"auth.json.lock",
|
|
406
|
+
"active_sessions.json",
|
|
407
|
+
"active_sessions.lock",
|
|
408
|
+
"leader.sock",
|
|
409
|
+
"leader.pid",
|
|
410
|
+
"socket",
|
|
411
|
+
"sockets",
|
|
412
|
+
]) {
|
|
413
|
+
await rm(join(profile.grokHome, name), { recursive: true, force: true });
|
|
414
|
+
}
|
|
415
|
+
// Bundled skills/docs are written under isolated HOME rather than GROK_HOME.
|
|
416
|
+
// Remove them after every short-lived process so the next preflight/sidecar
|
|
417
|
+
// starts from the same empty extension surface it verified.
|
|
418
|
+
await rm(join(profile.home, ".grok"), { recursive: true, force: true });
|
|
419
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Canonical product/picker identity for the subscription-backed runtime. */
|
|
2
|
+
export declare const GROK_SUBSCRIPTION_PROVIDER_ID: "grok-subscription";
|
|
3
|
+
/** Short command alias accepted by `/provider`, `/login`, and `/logout`. */
|
|
4
|
+
export declare const GROK_SUBSCRIPTION_COMMAND_ALIAS: "grok";
|
|
5
|
+
export declare const GROK_SUBSCRIPTION_PROVIDER_NAME: "Grok Subscription";
|
|
6
|
+
export declare const GROK_SUBSCRIPTION_PROVIDER_LABEL: "Grok Subscription [OAuth]";
|
|
7
|
+
export declare const GROK_SUBSCRIPTION_PROVIDER: Readonly<{
|
|
8
|
+
id: "grok-subscription";
|
|
9
|
+
commandAlias: "grok";
|
|
10
|
+
name: "Grok Subscription";
|
|
11
|
+
label: "Grok Subscription [OAuth]";
|
|
12
|
+
}>;
|
|
13
|
+
export interface ExternalProviderPickerEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface GrokSubscriptionPickerOptions {
|
|
19
|
+
label?: string;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Normalize only Grok's public command alias; unrelated provider IDs pass through. */
|
|
23
|
+
export declare function normalizeGrokSubscriptionProviderId(providerId: string): string;
|
|
24
|
+
export declare function isGrokSubscriptionProviderId(providerId: string | undefined): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Append one canonical Grok row after native provider rows. Existing canonical
|
|
27
|
+
* or command-alias rows are replaced, so independently composed picker lists
|
|
28
|
+
* cannot show Grok twice. This helper is intentionally independent from
|
|
29
|
+
* ProviderRegistry: Grok Subscription remains an external runtime.
|
|
30
|
+
*/
|
|
31
|
+
export declare function withGrokSubscriptionProvider(entries: readonly ExternalProviderPickerEntry[], options?: GrokSubscriptionPickerOptions): ExternalProviderPickerEntry[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Canonical product/picker identity for the subscription-backed runtime. */
|
|
2
|
+
export const GROK_SUBSCRIPTION_PROVIDER_ID = "grok-subscription";
|
|
3
|
+
/** Short command alias accepted by `/provider`, `/login`, and `/logout`. */
|
|
4
|
+
export const GROK_SUBSCRIPTION_COMMAND_ALIAS = "grok";
|
|
5
|
+
export const GROK_SUBSCRIPTION_PROVIDER_NAME = "Grok Subscription";
|
|
6
|
+
export const GROK_SUBSCRIPTION_PROVIDER_LABEL = "Grok Subscription [OAuth]";
|
|
7
|
+
export const GROK_SUBSCRIPTION_PROVIDER = Object.freeze({
|
|
8
|
+
id: GROK_SUBSCRIPTION_PROVIDER_ID,
|
|
9
|
+
commandAlias: GROK_SUBSCRIPTION_COMMAND_ALIAS,
|
|
10
|
+
name: GROK_SUBSCRIPTION_PROVIDER_NAME,
|
|
11
|
+
label: GROK_SUBSCRIPTION_PROVIDER_LABEL,
|
|
12
|
+
});
|
|
13
|
+
/** Normalize only Grok's public command alias; unrelated provider IDs pass through. */
|
|
14
|
+
export function normalizeGrokSubscriptionProviderId(providerId) {
|
|
15
|
+
const candidate = providerId.trim().toLowerCase();
|
|
16
|
+
if (candidate === GROK_SUBSCRIPTION_COMMAND_ALIAS || candidate === GROK_SUBSCRIPTION_PROVIDER_ID) {
|
|
17
|
+
return GROK_SUBSCRIPTION_PROVIDER_ID;
|
|
18
|
+
}
|
|
19
|
+
return providerId;
|
|
20
|
+
}
|
|
21
|
+
export function isGrokSubscriptionProviderId(providerId) {
|
|
22
|
+
return providerId !== undefined
|
|
23
|
+
&& normalizeGrokSubscriptionProviderId(providerId) === GROK_SUBSCRIPTION_PROVIDER_ID;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Append one canonical Grok row after native provider rows. Existing canonical
|
|
27
|
+
* or command-alias rows are replaced, so independently composed picker lists
|
|
28
|
+
* cannot show Grok twice. This helper is intentionally independent from
|
|
29
|
+
* ProviderRegistry: Grok Subscription remains an external runtime.
|
|
30
|
+
*/
|
|
31
|
+
export function withGrokSubscriptionProvider(entries, options = {}) {
|
|
32
|
+
const withoutGrok = entries.filter((entry) => !isGrokSubscriptionProviderId(entry.id));
|
|
33
|
+
return [
|
|
34
|
+
...withoutGrok,
|
|
35
|
+
{
|
|
36
|
+
id: GROK_SUBSCRIPTION_PROVIDER_ID,
|
|
37
|
+
name: options.label ?? GROK_SUBSCRIPTION_PROVIDER_LABEL,
|
|
38
|
+
enabled: options.enabled ?? true,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AgentEvent } from "../types.js";
|
|
2
|
+
import type { ThinkingLevel } from "../types.js";
|
|
3
|
+
import type { PermissionMode } from "../types.js";
|
|
4
|
+
import type { ApprovalController } from "../approval/types.js";
|
|
5
|
+
import type { ExternalRuntimeManager, ExternalRuntimeRunOptions, ExternalRuntimeSession, ExternalRuntimeModel, ExternalRuntimeModelSelection, ExternalRuntimeStatus } from "./types.js";
|
|
6
|
+
import type { GrokBinaryDependencies } from "./grok-binary.js";
|
|
7
|
+
import { type GrokSpawn } from "./grok-process.js";
|
|
8
|
+
import { type GrokAcpFactory } from "./grok-acp.js";
|
|
9
|
+
import { type GrokNetworkRoute } from "./grok-network.js";
|
|
10
|
+
export declare const GROK_RUNTIME_CAPABILITIES: Readonly<{
|
|
11
|
+
chat: true;
|
|
12
|
+
tools: true;
|
|
13
|
+
memory: false;
|
|
14
|
+
subagents: false;
|
|
15
|
+
plan: false;
|
|
16
|
+
web: false;
|
|
17
|
+
sessionLoad: true;
|
|
18
|
+
workspace: true;
|
|
19
|
+
modelControl: true;
|
|
20
|
+
reasoningControl: true;
|
|
21
|
+
}>;
|
|
22
|
+
export interface GrokRuntimeDependencies extends GrokBinaryDependencies {
|
|
23
|
+
bubbleHome?: string;
|
|
24
|
+
parentEnv?: NodeJS.ProcessEnv;
|
|
25
|
+
spawn?: GrokSpawn;
|
|
26
|
+
acpFactory?: GrokAcpFactory;
|
|
27
|
+
oauthOpener?: GrokOAuthOpener;
|
|
28
|
+
networkResolver?: () => Promise<GrokNetworkRoute>;
|
|
29
|
+
workspace?: string;
|
|
30
|
+
approvalController?: ApprovalController;
|
|
31
|
+
getPermissionMode?: () => PermissionMode;
|
|
32
|
+
}
|
|
33
|
+
export type GrokOAuthOpener = (url: string, signal: AbortSignal) => Promise<void>;
|
|
34
|
+
export declare function grokLoginFailureMessage(diagnostic: string): string;
|
|
35
|
+
export declare class GrokRuntimeManager implements ExternalRuntimeManager {
|
|
36
|
+
private readonly deps;
|
|
37
|
+
private readonly spawn;
|
|
38
|
+
private readonly acpFactory;
|
|
39
|
+
private readonly oauthOpener;
|
|
40
|
+
private readonly profile;
|
|
41
|
+
private readonly workspace;
|
|
42
|
+
private binary?;
|
|
43
|
+
private acp?;
|
|
44
|
+
private lock?;
|
|
45
|
+
private state;
|
|
46
|
+
private currentSessionId?;
|
|
47
|
+
private attachedSessionId?;
|
|
48
|
+
private activeTurn?;
|
|
49
|
+
private nextGeneration;
|
|
50
|
+
private closingSidecar?;
|
|
51
|
+
private loginController?;
|
|
52
|
+
private loginOperation?;
|
|
53
|
+
private sessionLifecyclePhase;
|
|
54
|
+
private networkRoute?;
|
|
55
|
+
private networkRouteOperation?;
|
|
56
|
+
private modelId?;
|
|
57
|
+
private reasoningEffort;
|
|
58
|
+
private modelCache?;
|
|
59
|
+
private readonly activeTools;
|
|
60
|
+
constructor(deps?: GrokRuntimeDependencies);
|
|
61
|
+
private status;
|
|
62
|
+
private ensureNetworkRoute;
|
|
63
|
+
private prepareAndVerify;
|
|
64
|
+
private acquireProfileLock;
|
|
65
|
+
inspect(): Promise<ExternalRuntimeStatus>;
|
|
66
|
+
login(signal?: AbortSignal, onBrowserOpened?: () => void): Promise<void>;
|
|
67
|
+
private performLogin;
|
|
68
|
+
logout(): Promise<void>;
|
|
69
|
+
private ensureConnected;
|
|
70
|
+
private applyAcpModels;
|
|
71
|
+
newSession(): Promise<ExternalRuntimeSession>;
|
|
72
|
+
loadSession(id: string): Promise<ExternalRuntimeSession>;
|
|
73
|
+
hydrateSession(id: string, modelId?: string, reasoningEffort?: ThinkingLevel): Promise<ExternalRuntimeSession>;
|
|
74
|
+
listModels(): Promise<ExternalRuntimeModel[]>;
|
|
75
|
+
getModelSelection(): ExternalRuntimeModelSelection;
|
|
76
|
+
setModel(modelId: string, reasoningEffort?: ThinkingLevel): Promise<ExternalRuntimeModelSelection>;
|
|
77
|
+
private ensureSession;
|
|
78
|
+
run(prompt: string, options?: ExternalRuntimeRunOptions): AsyncIterable<AgentEvent>;
|
|
79
|
+
cancel(sessionId?: string | undefined): Promise<void>;
|
|
80
|
+
private handlePolicyViolation;
|
|
81
|
+
private handleSessionUpdate;
|
|
82
|
+
private handleCrash;
|
|
83
|
+
private closeSidecar;
|
|
84
|
+
private disconnect;
|
|
85
|
+
/**
|
|
86
|
+
* Closes the current sidecar and releases the profile lock. The manager is
|
|
87
|
+
* intentionally reusable: a later new/load/run call starts a fresh sidecar.
|
|
88
|
+
*/
|
|
89
|
+
dispose(): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
export declare function createGrokRuntimeManager(deps?: GrokRuntimeDependencies): GrokRuntimeManager;
|