grokbox 0.1.0-alpha.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/README.zh-CN.md +4 -4
- package/dist/index.js +849 -86
- package/package.json +1 -1
- package/skills/core.md +4 -1
package/dist/index.js
CHANGED
|
@@ -3201,9 +3201,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3201
3201
|
helpWidth: context.helpWidth,
|
|
3202
3202
|
outputHasColors: context.hasColors
|
|
3203
3203
|
});
|
|
3204
|
-
const
|
|
3205
|
-
if (context.hasColors) return
|
|
3206
|
-
return this._outputConfiguration.stripColor(
|
|
3204
|
+
const text2 = helper.formatHelp(this, helper);
|
|
3205
|
+
if (context.hasColors) return text2;
|
|
3206
|
+
return this._outputConfiguration.stripColor(text2);
|
|
3207
3207
|
}
|
|
3208
3208
|
/**
|
|
3209
3209
|
* @typedef HelpContext
|
|
@@ -3363,7 +3363,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3363
3363
|
* @param {(string | Function)} text - string to add, or a function returning a string
|
|
3364
3364
|
* @return {Command} `this` command for chaining
|
|
3365
3365
|
*/
|
|
3366
|
-
addHelpText(position,
|
|
3366
|
+
addHelpText(position, text2) {
|
|
3367
3367
|
const allowedValues = ["beforeAll", "before", "after", "afterAll"];
|
|
3368
3368
|
if (!allowedValues.includes(position)) {
|
|
3369
3369
|
throw new Error(`Unexpected value for position to addHelpText.
|
|
@@ -3372,10 +3372,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3372
3372
|
const helpEvent = `${position}Help`;
|
|
3373
3373
|
this.on(helpEvent, (context) => {
|
|
3374
3374
|
let helpStr;
|
|
3375
|
-
if (typeof
|
|
3376
|
-
helpStr =
|
|
3375
|
+
if (typeof text2 === "function") {
|
|
3376
|
+
helpStr = text2({ error: context.error, command: context.command });
|
|
3377
3377
|
} else {
|
|
3378
|
-
helpStr =
|
|
3378
|
+
helpStr = text2;
|
|
3379
3379
|
}
|
|
3380
3380
|
if (helpStr) {
|
|
3381
3381
|
context.write(`${helpStr}
|
|
@@ -3474,7 +3474,7 @@ import { fileURLToPath } from "node:url";
|
|
|
3474
3474
|
// package.json
|
|
3475
3475
|
var package_default = {
|
|
3476
3476
|
name: "grokbox",
|
|
3477
|
-
version: "0.1.
|
|
3477
|
+
version: "0.1.1",
|
|
3478
3478
|
packageManager: "bun@1.3.14",
|
|
3479
3479
|
description: "Unofficial CLI and control plane for Grok Bot cloud computers",
|
|
3480
3480
|
license: "MIT",
|
|
@@ -3675,6 +3675,8 @@ var LEAF_COMMANDS = [
|
|
|
3675
3675
|
{ flags: "--keepalive-interval-ms <n>", description: "Sandbox keeper interval" },
|
|
3676
3676
|
{ flags: "--quota-source <source>", description: "Explicit quota source (cursor-web)" },
|
|
3677
3677
|
{ flags: "--quota-access-token-ref <ref>", description: "Quota-only env:/file:/keychain: token reference" },
|
|
3678
|
+
{ flags: "--server-access-token-ref <ref>", description: "Grok Bot Server account token reference" },
|
|
3679
|
+
{ flags: "--server-backend-url <url>", description: "Grok Bot Server backend URL" },
|
|
3678
3680
|
{ flags: "--secret-stdin-file <field=path>", description: "Read one secret from stdin into a protected file" }
|
|
3679
3681
|
]),
|
|
3680
3682
|
stdin: "secret",
|
|
@@ -3704,6 +3706,8 @@ var LEAF_COMMANDS = [
|
|
|
3704
3706
|
{ flags: "--keepalive-interval-ms <n>", description: "Sandbox keeper interval" },
|
|
3705
3707
|
{ flags: "--quota-source <source>", description: "Explicit quota source (cursor-web)" },
|
|
3706
3708
|
{ flags: "--quota-access-token-ref <ref>", description: "Quota-only env:/file:/keychain: token reference" },
|
|
3709
|
+
{ flags: "--server-access-token-ref <ref>", description: "Grok Bot Server account token reference" },
|
|
3710
|
+
{ flags: "--server-backend-url <url>", description: "Grok Bot Server backend URL" },
|
|
3707
3711
|
{ flags: "--secret-stdin-file <field=path>", description: "Read one secret from stdin into a protected file" }
|
|
3708
3712
|
]),
|
|
3709
3713
|
stdin: "secret",
|
|
@@ -3995,7 +3999,7 @@ var LEAF_COMMANDS = [
|
|
|
3995
3999
|
{
|
|
3996
4000
|
path: ["agents", "routines", "list"],
|
|
3997
4001
|
usage: "grokbox agents routines list <agent> [--json|--table]",
|
|
3998
|
-
summary: "List one agent's
|
|
4002
|
+
summary: "List one agent's cloud-backed Routine metadata without prompts or credentials.",
|
|
3999
4003
|
arguments: [
|
|
4000
4004
|
{ syntax: "<agent>", description: "Agent ID or unambiguous name/title", role: "agent", kinds: ["agent"] }
|
|
4001
4005
|
],
|
|
@@ -4071,7 +4075,7 @@ var LEAF_COMMANDS = [
|
|
|
4071
4075
|
},
|
|
4072
4076
|
{
|
|
4073
4077
|
path: ["agents", "routines", "webhook", "credential"],
|
|
4074
|
-
usage: "grokbox agents routines webhook credential <agent> <routine-id> --confirm [--reveal-key]",
|
|
4078
|
+
usage: "grokbox agents routines webhook credential <agent> <routine-id> --confirm [--reveal-key] [--verify]",
|
|
4075
4079
|
summary: "Read or mint one webhook Routine credential; key is hidden unless explicitly revealed.",
|
|
4076
4080
|
arguments: [
|
|
4077
4081
|
{ syntax: "<agent>", description: "Agent ID or unambiguous name/title", role: "agent", kinds: ["agent"] },
|
|
@@ -4079,7 +4083,8 @@ var LEAF_COMMANDS = [
|
|
|
4079
4083
|
],
|
|
4080
4084
|
options: options([
|
|
4081
4085
|
{ flags: "--confirm", description: "Confirm credential access/mint", required: true },
|
|
4082
|
-
{ flags: "--reveal-key", description: "Include the webhook key and routine prompt in stdout" }
|
|
4086
|
+
{ flags: "--reveal-key", description: "Include the webhook key and routine prompt in stdout" },
|
|
4087
|
+
{ flags: "--verify", description: "Check the webhook key without invoking this Routine" }
|
|
4083
4088
|
], { timeout: true }),
|
|
4084
4089
|
stdin: "none",
|
|
4085
4090
|
table: false,
|
|
@@ -5153,18 +5158,18 @@ function asNumber(value, fallback = 0) {
|
|
|
5153
5158
|
function asString(value, fallback = "") {
|
|
5154
5159
|
return typeof value === "string" ? value : fallback;
|
|
5155
5160
|
}
|
|
5156
|
-
function stripOneTrailingNewline(
|
|
5157
|
-
if (
|
|
5158
|
-
if (
|
|
5159
|
-
return
|
|
5161
|
+
function stripOneTrailingNewline(text2) {
|
|
5162
|
+
if (text2.endsWith("\r\n")) return text2.slice(0, -2);
|
|
5163
|
+
if (text2.endsWith("\n")) return text2.slice(0, -1);
|
|
5164
|
+
return text2;
|
|
5160
5165
|
}
|
|
5161
5166
|
var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
5162
5167
|
function assertUuidV4(value, name) {
|
|
5163
5168
|
if (!UUID_V4.test(value)) throw usage(`${name} must be a UUID v4.`);
|
|
5164
5169
|
return value;
|
|
5165
5170
|
}
|
|
5166
|
-
function utf8Bytes(
|
|
5167
|
-
return new TextEncoder().encode(
|
|
5171
|
+
function utf8Bytes(text2) {
|
|
5172
|
+
return new TextEncoder().encode(text2).byteLength;
|
|
5168
5173
|
}
|
|
5169
5174
|
|
|
5170
5175
|
// src/config/profile.ts
|
|
@@ -5192,10 +5197,12 @@ var PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
5192
5197
|
"daemon_socket",
|
|
5193
5198
|
"ssh_host",
|
|
5194
5199
|
"sandbox",
|
|
5195
|
-
"quota"
|
|
5200
|
+
"quota",
|
|
5201
|
+
"server"
|
|
5196
5202
|
]);
|
|
5197
5203
|
var SANDBOX_KEYS = /* @__PURE__ */ new Set(["access_token_ref", "keepalive_interval_ms"]);
|
|
5198
5204
|
var QUOTA_KEYS = /* @__PURE__ */ new Set(["source", "access_token_ref"]);
|
|
5205
|
+
var SERVER_KEYS = /* @__PURE__ */ new Set(["access_token_ref", "backend_url"]);
|
|
5199
5206
|
var GLOBAL_KEYS = /* @__PURE__ */ new Set(["version", "current_profile"]);
|
|
5200
5207
|
var PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
5201
5208
|
var SSH_HOST = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,254}$/;
|
|
@@ -5313,6 +5320,19 @@ function validateProfile(input) {
|
|
|
5313
5320
|
if (accessTokenRef === void 0) throw invalid("quota.access_token_ref is required.");
|
|
5314
5321
|
quota = { source, access_token_ref: accessTokenRef };
|
|
5315
5322
|
}
|
|
5323
|
+
let server;
|
|
5324
|
+
if (input.server !== void 0) {
|
|
5325
|
+
if (!isRecord(input.server)) throw invalid("server must be an object.");
|
|
5326
|
+
assertKnownKeys(input.server, SERVER_KEYS, "server");
|
|
5327
|
+
const accessTokenRef = validateSecretRef(
|
|
5328
|
+
optionalString(input.server, "access_token_ref"),
|
|
5329
|
+
"server.access_token_ref"
|
|
5330
|
+
);
|
|
5331
|
+
if (accessTokenRef === void 0) throw invalid("server.access_token_ref is required.");
|
|
5332
|
+
const backendUrl = validateUrl(optionalString(input.server, "backend_url"), "server.backend_url");
|
|
5333
|
+
server = { access_token_ref: accessTokenRef, backend_url: backendUrl };
|
|
5334
|
+
if (server.backend_url === void 0) delete server.backend_url;
|
|
5335
|
+
}
|
|
5316
5336
|
const profile = {
|
|
5317
5337
|
version: 1,
|
|
5318
5338
|
transport,
|
|
@@ -5328,7 +5348,8 @@ function validateProfile(input) {
|
|
|
5328
5348
|
return value === void 0 ? void 0 : assertSshHost(value);
|
|
5329
5349
|
})(),
|
|
5330
5350
|
sandbox,
|
|
5331
|
-
quota
|
|
5351
|
+
quota,
|
|
5352
|
+
server
|
|
5332
5353
|
};
|
|
5333
5354
|
for (const key of Object.keys(profile)) {
|
|
5334
5355
|
if (profile[key] === void 0) delete profile[key];
|
|
@@ -5344,9 +5365,9 @@ function validateGlobalConfig(input) {
|
|
|
5344
5365
|
return current === void 0 ? { version: 1 } : { version: 1, current_profile: current };
|
|
5345
5366
|
}
|
|
5346
5367
|
async function readJson(path, missing) {
|
|
5347
|
-
let
|
|
5368
|
+
let text2;
|
|
5348
5369
|
try {
|
|
5349
|
-
|
|
5370
|
+
text2 = await readFile2(path, "utf8");
|
|
5350
5371
|
} catch (error) {
|
|
5351
5372
|
if (error.code === "ENOENT" && missing === "null") return null;
|
|
5352
5373
|
if (error.code === "ENOENT") {
|
|
@@ -5355,7 +5376,7 @@ async function readJson(path, missing) {
|
|
|
5355
5376
|
throw invalid("Configuration file is unreadable.");
|
|
5356
5377
|
}
|
|
5357
5378
|
try {
|
|
5358
|
-
return JSON.parse(
|
|
5379
|
+
return JSON.parse(text2);
|
|
5359
5380
|
} catch {
|
|
5360
5381
|
throw invalid("Configuration file is not valid JSON.");
|
|
5361
5382
|
}
|
|
@@ -5695,13 +5716,13 @@ function lostResponse(method) {
|
|
|
5695
5716
|
}
|
|
5696
5717
|
return new CliError("daemon_unreachable", "Daemon is unreachable.", { retryable: true });
|
|
5697
5718
|
}
|
|
5698
|
-
function parseResponse(
|
|
5719
|
+
function parseResponse(text2, status) {
|
|
5699
5720
|
if (status === 401 || status === 403) {
|
|
5700
5721
|
throw new CliError("daemon_unauthorized", "Daemon rejected the shared credential.");
|
|
5701
5722
|
}
|
|
5702
5723
|
let parsed;
|
|
5703
5724
|
try {
|
|
5704
|
-
parsed = JSON.parse(
|
|
5725
|
+
parsed = JSON.parse(text2);
|
|
5705
5726
|
} catch {
|
|
5706
5727
|
throw new CliError("daemon_unreachable", "Daemon returned invalid JSON.");
|
|
5707
5728
|
}
|
|
@@ -5795,7 +5816,7 @@ var LocalDaemonClient = class extends VersionedDaemonClient {
|
|
|
5795
5816
|
headers: { "content-type": "application/json", "content-length": Buffer.byteLength(body) }
|
|
5796
5817
|
},
|
|
5797
5818
|
(res) => {
|
|
5798
|
-
let
|
|
5819
|
+
let text2 = "";
|
|
5799
5820
|
let bytes = 0;
|
|
5800
5821
|
res.setEncoding("utf8");
|
|
5801
5822
|
res.on("data", (chunk) => {
|
|
@@ -5804,12 +5825,12 @@ var LocalDaemonClient = class extends VersionedDaemonClient {
|
|
|
5804
5825
|
req.destroy(new Error("response too large"));
|
|
5805
5826
|
return;
|
|
5806
5827
|
}
|
|
5807
|
-
|
|
5828
|
+
text2 += chunk;
|
|
5808
5829
|
});
|
|
5809
5830
|
res.on("end", () => {
|
|
5810
5831
|
cleanup();
|
|
5811
5832
|
try {
|
|
5812
|
-
resolve5(parseResponse(
|
|
5833
|
+
resolve5(parseResponse(text2, res.statusCode ?? 0));
|
|
5813
5834
|
} catch (error) {
|
|
5814
5835
|
if (error instanceof CliError && error.code !== "daemon_unreachable") reject(error);
|
|
5815
5836
|
else reject(lostResponse(method));
|
|
@@ -6511,10 +6532,10 @@ var GatewayClient = class {
|
|
|
6511
6532
|
if (input.stream) {
|
|
6512
6533
|
clearTimeout(timer);
|
|
6513
6534
|
if (!response.ok) {
|
|
6514
|
-
const
|
|
6535
|
+
const text3 = await response.text();
|
|
6515
6536
|
let parsed;
|
|
6516
6537
|
try {
|
|
6517
|
-
parsed =
|
|
6538
|
+
parsed = text3.length === 0 ? null : JSON.parse(text3);
|
|
6518
6539
|
} catch {
|
|
6519
6540
|
parsed = null;
|
|
6520
6541
|
}
|
|
@@ -6522,9 +6543,9 @@ var GatewayClient = class {
|
|
|
6522
6543
|
}
|
|
6523
6544
|
return { status: response.status, body: null, text: "", response };
|
|
6524
6545
|
}
|
|
6525
|
-
let
|
|
6546
|
+
let text2 = "";
|
|
6526
6547
|
try {
|
|
6527
|
-
|
|
6548
|
+
text2 = await response.text();
|
|
6528
6549
|
} catch {
|
|
6529
6550
|
clearTimeout(timer);
|
|
6530
6551
|
if (write) {
|
|
@@ -6538,14 +6559,14 @@ var GatewayClient = class {
|
|
|
6538
6559
|
clearTimeout(timer);
|
|
6539
6560
|
}
|
|
6540
6561
|
let body = null;
|
|
6541
|
-
if (
|
|
6562
|
+
if (text2.length > 0) {
|
|
6542
6563
|
try {
|
|
6543
|
-
body = JSON.parse(
|
|
6564
|
+
body = JSON.parse(text2);
|
|
6544
6565
|
} catch {
|
|
6545
6566
|
body = null;
|
|
6546
6567
|
}
|
|
6547
6568
|
}
|
|
6548
|
-
return { status: response.status, body, text, response };
|
|
6569
|
+
return { status: response.status, body, text: text2, response };
|
|
6549
6570
|
}
|
|
6550
6571
|
interpret(result) {
|
|
6551
6572
|
if (result.status >= 200 && result.status < 300) return result;
|
|
@@ -6666,10 +6687,10 @@ function ioFromOpts(opts) {
|
|
|
6666
6687
|
function rejectTable(table, allowed) {
|
|
6667
6688
|
if (table && !allowed) throw usage("This command does not support --table.");
|
|
6668
6689
|
}
|
|
6669
|
-
async function readPrompt(
|
|
6690
|
+
async function readPrompt(text2, deps) {
|
|
6670
6691
|
let prompt;
|
|
6671
|
-
if (
|
|
6672
|
-
prompt =
|
|
6692
|
+
if (text2 !== void 0) {
|
|
6693
|
+
prompt = text2;
|
|
6673
6694
|
} else if (deps.stdinIsTTY) {
|
|
6674
6695
|
throw usage("Provide --text or pipe stdin. Interactive prompts are not supported.");
|
|
6675
6696
|
} else {
|
|
@@ -7536,11 +7557,11 @@ function jitteredInterval(intervalMs, randomUUID7) {
|
|
|
7536
7557
|
const factor = 1 - JITTER_RATIO + sample * JITTER_RATIO * 2;
|
|
7537
7558
|
return Math.max(1e3, Math.round(intervalMs * factor));
|
|
7538
7559
|
}
|
|
7539
|
-
function failureDelay(intervalMs,
|
|
7540
|
-
if (
|
|
7560
|
+
function failureDelay(intervalMs, failure2, consecutiveFailures, retryAfterMs) {
|
|
7561
|
+
if (failure2 === "rate_limited") {
|
|
7541
7562
|
return Math.max(intervalMs, retryAfterMs ?? FAILURE_BASE_BACKOFF_MS);
|
|
7542
7563
|
}
|
|
7543
|
-
if (
|
|
7564
|
+
if (failure2 === "provider_unavailable" || failure2 === "request_timeout") {
|
|
7544
7565
|
const backoff = Math.min(
|
|
7545
7566
|
FAILURE_BASE_BACKOFF_MS * 2 ** Math.max(0, consecutiveFailures - 1),
|
|
7546
7567
|
FAILURE_MAX_BACKOFF_MS
|
|
@@ -8427,15 +8448,15 @@ function validateDaemonConfig(input) {
|
|
|
8427
8448
|
};
|
|
8428
8449
|
}
|
|
8429
8450
|
async function readDaemonConfig(configDir) {
|
|
8430
|
-
let
|
|
8451
|
+
let text2;
|
|
8431
8452
|
try {
|
|
8432
|
-
|
|
8453
|
+
text2 = await readFile5(daemonConfigPath(configDir), "utf8");
|
|
8433
8454
|
} catch (error) {
|
|
8434
8455
|
if (error.code === "ENOENT") return { version: 1 };
|
|
8435
8456
|
throw new CliError("profile_invalid", "Daemon config is unreadable.");
|
|
8436
8457
|
}
|
|
8437
8458
|
try {
|
|
8438
|
-
return validateDaemonConfig(JSON.parse(
|
|
8459
|
+
return validateDaemonConfig(JSON.parse(text2));
|
|
8439
8460
|
} catch (error) {
|
|
8440
8461
|
if (error instanceof CliError) throw error;
|
|
8441
8462
|
throw new CliError("profile_invalid", "Daemon config is not valid JSON.");
|
|
@@ -8733,7 +8754,7 @@ function displayFromEnviron(environ) {
|
|
|
8733
8754
|
}
|
|
8734
8755
|
function inspectDesktopProc(cmdline) {
|
|
8735
8756
|
const args = cmdline.split("\0").filter((part) => part.length > 0);
|
|
8736
|
-
const
|
|
8757
|
+
const text2 = args.join(" ");
|
|
8737
8758
|
let startWindow = NaN;
|
|
8738
8759
|
for (const [index, arg] of args.entries()) {
|
|
8739
8760
|
if (arg === "start-window" || arg.endsWith("/start-window") || arg.endsWith("/start-window.sh")) {
|
|
@@ -8745,7 +8766,7 @@ function inspectDesktopProc(cmdline) {
|
|
|
8745
8766
|
const slash = Math.max(argv0.lastIndexOf("/"), argv0.lastIndexOf("\\"));
|
|
8746
8767
|
const base = argv0.slice(slash + 1);
|
|
8747
8768
|
const grok = base === "grok" || args.some((arg) => arg === "grok" || arg.endsWith("/grok"));
|
|
8748
|
-
const taskTool = /\bTask\(/.test(
|
|
8769
|
+
const taskTool = /\bTask\(/.test(text2);
|
|
8749
8770
|
return {
|
|
8750
8771
|
grok,
|
|
8751
8772
|
task: taskTool,
|
|
@@ -10820,15 +10841,15 @@ var GovernedFilesystem = class _GovernedFilesystem {
|
|
|
10820
10841
|
}
|
|
10821
10842
|
const sha256 = createHash5("sha256").update(content).digest("hex");
|
|
10822
10843
|
try {
|
|
10823
|
-
const
|
|
10824
|
-
if (!
|
|
10844
|
+
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
10845
|
+
if (!text2.includes("\0")) {
|
|
10825
10846
|
return {
|
|
10826
10847
|
path: target.remotePath,
|
|
10827
10848
|
root: target.root.name,
|
|
10828
10849
|
size: content.length,
|
|
10829
10850
|
sha256,
|
|
10830
10851
|
encoding: "utf8",
|
|
10831
|
-
content:
|
|
10852
|
+
content: text2
|
|
10832
10853
|
};
|
|
10833
10854
|
}
|
|
10834
10855
|
} catch {
|
|
@@ -11452,17 +11473,17 @@ async function readBody(req) {
|
|
|
11452
11473
|
return body;
|
|
11453
11474
|
}
|
|
11454
11475
|
function writeResponse(res, status, body) {
|
|
11455
|
-
const
|
|
11476
|
+
const text2 = JSON.stringify(body);
|
|
11456
11477
|
res.writeHead(status, {
|
|
11457
11478
|
"content-type": "application/json",
|
|
11458
|
-
"content-length": Buffer.byteLength(
|
|
11479
|
+
"content-length": Buffer.byteLength(text2)
|
|
11459
11480
|
});
|
|
11460
|
-
res.end(
|
|
11481
|
+
res.end(text2);
|
|
11461
11482
|
}
|
|
11462
|
-
function parseRequest(
|
|
11483
|
+
function parseRequest(text2) {
|
|
11463
11484
|
let parsed;
|
|
11464
11485
|
try {
|
|
11465
|
-
parsed = JSON.parse(
|
|
11486
|
+
parsed = JSON.parse(text2);
|
|
11466
11487
|
} catch {
|
|
11467
11488
|
throw new CliError("gateway_bad_request", "Daemon request is not valid JSON.");
|
|
11468
11489
|
}
|
|
@@ -12848,10 +12869,10 @@ async function queryCursorWebQuota(deps, token, timeoutMs) {
|
|
|
12848
12869
|
{ failureCode: "unexpected_status", httpStatus: response.status }
|
|
12849
12870
|
);
|
|
12850
12871
|
}
|
|
12851
|
-
const
|
|
12872
|
+
const text2 = await readBoundedText(response);
|
|
12852
12873
|
let payload;
|
|
12853
12874
|
try {
|
|
12854
|
-
payload = JSON.parse(
|
|
12875
|
+
payload = JSON.parse(text2);
|
|
12855
12876
|
} catch {
|
|
12856
12877
|
throw protocolFailure("response_not_json");
|
|
12857
12878
|
}
|
|
@@ -13342,8 +13363,8 @@ function redactJson(value, redacted) {
|
|
|
13342
13363
|
}
|
|
13343
13364
|
async function readJsonFile(path) {
|
|
13344
13365
|
try {
|
|
13345
|
-
const
|
|
13346
|
-
return JSON.parse(
|
|
13366
|
+
const text2 = await readFile8(path, "utf8");
|
|
13367
|
+
return JSON.parse(text2);
|
|
13347
13368
|
} catch (error) {
|
|
13348
13369
|
if (error.code === "ENOENT") return void 0;
|
|
13349
13370
|
if (error instanceof SyntaxError) return void 0;
|
|
@@ -13429,19 +13450,19 @@ async function copyMemoryShard(sourceRoot, destRoot, shardDir, exportPrefix, lay
|
|
|
13429
13450
|
});
|
|
13430
13451
|
}
|
|
13431
13452
|
}
|
|
13432
|
-
function tokenMatches(
|
|
13453
|
+
function tokenMatches(text2, name) {
|
|
13433
13454
|
if (name.length < 3) return false;
|
|
13434
13455
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13435
|
-
return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}([^A-Za-z0-9_-]|$)`).test(
|
|
13456
|
+
return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}([^A-Za-z0-9_-]|$)`).test(text2);
|
|
13436
13457
|
}
|
|
13437
|
-
function collectWorkflowNames(
|
|
13458
|
+
function collectWorkflowNames(text2, known) {
|
|
13438
13459
|
const found = /* @__PURE__ */ new Set();
|
|
13439
|
-
for (const match of
|
|
13460
|
+
for (const match of text2.matchAll(WORKFLOW_REF)) {
|
|
13440
13461
|
const name = match[1];
|
|
13441
13462
|
if (name) found.add(name);
|
|
13442
13463
|
}
|
|
13443
13464
|
for (const name of known) {
|
|
13444
|
-
if (tokenMatches(
|
|
13465
|
+
if (tokenMatches(text2, name)) found.add(name);
|
|
13445
13466
|
}
|
|
13446
13467
|
return [...found].sort();
|
|
13447
13468
|
}
|
|
@@ -14143,18 +14164,18 @@ async function runFsWrite(deps, remotePath, raw) {
|
|
|
14143
14164
|
const io = ioFromOpts(raw);
|
|
14144
14165
|
const expectedSha256 = expectedHash(raw.expectedSha256);
|
|
14145
14166
|
const client2 = await filesystemClient(deps, io.timeoutMs, FS_WRITE_CAPABILITY);
|
|
14146
|
-
let
|
|
14147
|
-
if (raw.text !== void 0)
|
|
14167
|
+
let text2;
|
|
14168
|
+
if (raw.text !== void 0) text2 = raw.text;
|
|
14148
14169
|
else if (deps.stdinIsTTY) throw usage("Provide --text or pipe stdin.");
|
|
14149
|
-
else
|
|
14150
|
-
const content = Buffer.from(
|
|
14170
|
+
else text2 = await deps.readStdin();
|
|
14171
|
+
const content = Buffer.from(text2, "utf8");
|
|
14151
14172
|
if (content.length > FS_WRITE_MAX_BYTES) throw usage("Text write exceeds the 1 MiB limit.");
|
|
14152
14173
|
const sha256 = createHash7("sha256").update(content).digest("hex");
|
|
14153
14174
|
const operationId2 = deps.randomUUID();
|
|
14154
14175
|
writeSuccess(deps.stdout, await mutationCall(client2, "fsWrite", {
|
|
14155
14176
|
operationId: operationId2,
|
|
14156
14177
|
path: remotePath,
|
|
14157
|
-
contentUtf8:
|
|
14178
|
+
contentUtf8: text2,
|
|
14158
14179
|
...expectedSha256 ? { expectedSha256 } : {}
|
|
14159
14180
|
}, remotePath, { size: content.length, sha256 }));
|
|
14160
14181
|
}
|
|
@@ -14789,6 +14810,15 @@ function patchFromOptions(raw) {
|
|
|
14789
14810
|
});
|
|
14790
14811
|
}
|
|
14791
14812
|
}
|
|
14813
|
+
if (raw.serverAccessTokenRef !== void 0 || raw.serverBackendUrl !== void 0) {
|
|
14814
|
+
patch.server = {};
|
|
14815
|
+
if (raw.serverAccessTokenRef !== void 0) {
|
|
14816
|
+
patch.server.access_token_ref = raw.serverAccessTokenRef;
|
|
14817
|
+
}
|
|
14818
|
+
if (raw.serverBackendUrl !== void 0) {
|
|
14819
|
+
patch.server.backend_url = raw.serverBackendUrl;
|
|
14820
|
+
}
|
|
14821
|
+
}
|
|
14792
14822
|
const quotaSecret = raw.secretStdinFile?.startsWith("quota-access-token=") ?? false;
|
|
14793
14823
|
if (raw.quotaSource !== void 0 || raw.quotaAccessTokenRef !== void 0 || quotaSecret) {
|
|
14794
14824
|
if (raw.quotaSource !== "cursor-web") {
|
|
@@ -14863,7 +14893,8 @@ async function runProfileUpdate(deps, name, raw) {
|
|
|
14863
14893
|
if (Object.keys(patch).length === 0) throw usage("profile update requires at least one connection option.");
|
|
14864
14894
|
const sandbox = patch.sandbox ? { ...current?.sandbox ?? {}, ...patch.sandbox } : current?.sandbox;
|
|
14865
14895
|
const quota = patch.quota ? { ...current?.quota ?? {}, ...patch.quota } : current?.quota;
|
|
14866
|
-
const
|
|
14896
|
+
const server = patch.server ? { ...current?.server ?? {}, ...patch.server } : current?.server;
|
|
14897
|
+
const next = validateProfile({ version: 1, ...current ?? {}, ...patch, sandbox, quota, server });
|
|
14867
14898
|
await persistSecretInput(deps, secretInput);
|
|
14868
14899
|
await writeProfileFile(deps.configDir, name, next);
|
|
14869
14900
|
writeSuccess(deps.stdout, { name, updated: true });
|
|
@@ -14882,6 +14913,8 @@ async function runProfileCapabilities(deps, name, raw) {
|
|
|
14882
14913
|
const sandboxConfigured = Boolean(profile.sandbox?.access_token_ref);
|
|
14883
14914
|
const sandboxCapability = sandboxConfigured ? "provider-authorization-dependent" : false;
|
|
14884
14915
|
const quotaCapability = profile.quota?.access_token_ref ? "provider-authorization-dependent" : false;
|
|
14916
|
+
const serverCapability = profile.server?.access_token_ref ? "provider-authorization-dependent" : false;
|
|
14917
|
+
const routineCapability = serverCapability || (local || directGateway || daemon ? "runtime-host-dependent" : false);
|
|
14885
14918
|
writeSuccess(deps.stdout, {
|
|
14886
14919
|
profile: profile.name,
|
|
14887
14920
|
transport: profile.transport,
|
|
@@ -14908,11 +14941,671 @@ async function runProfileCapabilities(deps, name, raw) {
|
|
|
14908
14941
|
"sandbox.inspect": sandboxCapability,
|
|
14909
14942
|
"sandbox.wake": sandboxCapability,
|
|
14910
14943
|
"sandbox.keepalive": sandboxCapability,
|
|
14911
|
-
"quota.read": quotaCapability
|
|
14944
|
+
"quota.read": quotaCapability,
|
|
14945
|
+
"grok.routine.read": routineCapability,
|
|
14946
|
+
"grok.routine.webhook": routineCapability
|
|
14912
14947
|
}
|
|
14913
14948
|
});
|
|
14914
14949
|
}
|
|
14915
14950
|
|
|
14951
|
+
// src/box-routine-auth.ts
|
|
14952
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
14953
|
+
import { constants as constants5 } from "node:fs";
|
|
14954
|
+
import { open as open7, stat as stat6 } from "node:fs/promises";
|
|
14955
|
+
|
|
14956
|
+
// src/server-routines.ts
|
|
14957
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
14958
|
+
import { constants as constants4 } from "node:fs";
|
|
14959
|
+
import { mkdir as mkdir9, open as open6, lstat as lstat5, rename as rename6, unlink as unlink3 } from "node:fs/promises";
|
|
14960
|
+
import { join as join11 } from "node:path";
|
|
14961
|
+
var SERVER_ROUTINE_PATHS = {
|
|
14962
|
+
listAgents: "/aiserver.v1.GrokBotService/ListGrokBotAgents",
|
|
14963
|
+
listAutomations: "/aiserver.v1.GrokBotService/ListGrokBotAgentAutomations",
|
|
14964
|
+
mintWebhookKey: "/aiserver.v1.DashboardService/CreateAutomationWebhookApiKey"
|
|
14965
|
+
};
|
|
14966
|
+
var DEFAULT_SERVER_BACKEND_URL = "https://api2.cursor.sh";
|
|
14967
|
+
var MAX_RESPONSE_BYTES2 = 8 * 1024 * 1024;
|
|
14968
|
+
var MAX_RECORD_BYTES = 512 * 1024;
|
|
14969
|
+
var MAX_KEY_BYTES = 4096;
|
|
14970
|
+
var MAX_AGENTS = 5e3;
|
|
14971
|
+
var MAX_ROUTINES = 100;
|
|
14972
|
+
var NIL_AUTOMATION_ID = "00000000-0000-5000-8000-000000000000";
|
|
14973
|
+
function protocolFailure2(failureCode) {
|
|
14974
|
+
return new CliError(
|
|
14975
|
+
"capability_unavailable",
|
|
14976
|
+
"The Server Routine surface did not satisfy the supported bounded protocol.",
|
|
14977
|
+
{ failureCode }
|
|
14978
|
+
);
|
|
14979
|
+
}
|
|
14980
|
+
function authorizationFailure2(failureCode) {
|
|
14981
|
+
return new CliError(
|
|
14982
|
+
"credential_invalid",
|
|
14983
|
+
"The configured Server credential was not authorized for Routine access.",
|
|
14984
|
+
{ failureCode }
|
|
14985
|
+
);
|
|
14986
|
+
}
|
|
14987
|
+
function varint(value) {
|
|
14988
|
+
const bytes = [];
|
|
14989
|
+
let rest = value;
|
|
14990
|
+
while (rest > 127) {
|
|
14991
|
+
bytes.push(rest & 127 | 128);
|
|
14992
|
+
rest = Math.floor(rest / 128);
|
|
14993
|
+
}
|
|
14994
|
+
bytes.push(rest);
|
|
14995
|
+
return Buffer.from(bytes);
|
|
14996
|
+
}
|
|
14997
|
+
function lengthDelimited(field, payload) {
|
|
14998
|
+
return Buffer.concat([varint(field << 3 | 2), varint(payload.length), payload]);
|
|
14999
|
+
}
|
|
15000
|
+
function readVarint(buffer, offset) {
|
|
15001
|
+
let value = 0n;
|
|
15002
|
+
for (let index = 0; index < 10; index += 1) {
|
|
15003
|
+
const byte = buffer[offset + index];
|
|
15004
|
+
if (byte === void 0) throw protocolFailure2("truncated_varint");
|
|
15005
|
+
if (index === 9 && byte > 1) throw protocolFailure2("varint_overflow");
|
|
15006
|
+
value |= BigInt(byte & 127) << BigInt(index * 7);
|
|
15007
|
+
if ((byte & 128) === 0) return { value, next: offset + index + 1 };
|
|
15008
|
+
}
|
|
15009
|
+
throw protocolFailure2("varint_overflow");
|
|
15010
|
+
}
|
|
15011
|
+
function decodeFields(buffer) {
|
|
15012
|
+
const fields = [];
|
|
15013
|
+
let offset = 0;
|
|
15014
|
+
while (offset < buffer.length) {
|
|
15015
|
+
const key = readVarint(buffer, offset);
|
|
15016
|
+
offset = key.next;
|
|
15017
|
+
if (key.value < 8n || key.value > 0xffffffffn) throw protocolFailure2("invalid_tag");
|
|
15018
|
+
const number = Number(key.value >> 3n);
|
|
15019
|
+
const wire = Number(key.value & 7n);
|
|
15020
|
+
if (wire === 0) {
|
|
15021
|
+
const value = readVarint(buffer, offset);
|
|
15022
|
+
offset = value.next;
|
|
15023
|
+
fields.push({ number, wire, value: value.value });
|
|
15024
|
+
} else if (wire === 2) {
|
|
15025
|
+
const size = readVarint(buffer, offset);
|
|
15026
|
+
offset = size.next;
|
|
15027
|
+
if (size.value > BigInt(buffer.length - offset)) throw protocolFailure2("truncated_field");
|
|
15028
|
+
const length = Number(size.value);
|
|
15029
|
+
fields.push({ number, wire, value: buffer.subarray(offset, offset + length) });
|
|
15030
|
+
offset += length;
|
|
15031
|
+
} else if (wire === 5) {
|
|
15032
|
+
if (offset + 4 > buffer.length) throw protocolFailure2("truncated_field");
|
|
15033
|
+
fields.push({ number, wire, value: buffer.subarray(offset, offset + 4) });
|
|
15034
|
+
offset += 4;
|
|
15035
|
+
} else if (wire === 1) {
|
|
15036
|
+
if (offset + 8 > buffer.length) throw protocolFailure2("truncated_field");
|
|
15037
|
+
fields.push({ number, wire, value: buffer.subarray(offset, offset + 8) });
|
|
15038
|
+
offset += 8;
|
|
15039
|
+
} else {
|
|
15040
|
+
if (wire === 3) {
|
|
15041
|
+
offset = skipGroup(buffer, offset, number, 0);
|
|
15042
|
+
fields.push({ number, wire, value: 0n });
|
|
15043
|
+
continue;
|
|
15044
|
+
}
|
|
15045
|
+
throw protocolFailure2("unsupported_wire_type");
|
|
15046
|
+
}
|
|
15047
|
+
}
|
|
15048
|
+
return fields;
|
|
15049
|
+
}
|
|
15050
|
+
function skipGroup(buffer, start, expected, depth) {
|
|
15051
|
+
if (depth > 32) throw protocolFailure2("group_depth_exceeded");
|
|
15052
|
+
let offset = start;
|
|
15053
|
+
while (offset < buffer.length) {
|
|
15054
|
+
const tag = readVarint(buffer, offset);
|
|
15055
|
+
if (tag.value < 8n || tag.value > 0xffffffffn) throw protocolFailure2("invalid_tag");
|
|
15056
|
+
const number = Number(tag.value >> 3n);
|
|
15057
|
+
const wire = Number(tag.value & 7n);
|
|
15058
|
+
offset = tag.next;
|
|
15059
|
+
if (wire === 4) {
|
|
15060
|
+
if (number !== expected) throw protocolFailure2("group_mismatch");
|
|
15061
|
+
return offset;
|
|
15062
|
+
}
|
|
15063
|
+
if (wire === 3) offset = skipGroup(buffer, offset, number, depth + 1);
|
|
15064
|
+
else if (wire === 0) offset = readVarint(buffer, offset).next;
|
|
15065
|
+
else if (wire === 1 || wire === 5) offset += wire === 1 ? 8 : 4;
|
|
15066
|
+
else if (wire === 2) {
|
|
15067
|
+
const size = readVarint(buffer, offset);
|
|
15068
|
+
if (size.value > BigInt(buffer.length - size.next)) throw protocolFailure2("truncated_field");
|
|
15069
|
+
offset = size.next + Number(size.value);
|
|
15070
|
+
} else throw protocolFailure2("unsupported_wire_type");
|
|
15071
|
+
if (offset > buffer.length) throw protocolFailure2("truncated_field");
|
|
15072
|
+
}
|
|
15073
|
+
throw protocolFailure2("truncated_group");
|
|
15074
|
+
}
|
|
15075
|
+
function firstBytes(fields, number) {
|
|
15076
|
+
const hits = fields.filter((field) => field.number === number);
|
|
15077
|
+
if (hits.length > 1 || hits.length === 1 && hits[0].wire !== 2) {
|
|
15078
|
+
throw protocolFailure2("invalid_required_field");
|
|
15079
|
+
}
|
|
15080
|
+
const value = hits[0]?.value;
|
|
15081
|
+
return value instanceof Buffer ? value : void 0;
|
|
15082
|
+
}
|
|
15083
|
+
function text(buffer) {
|
|
15084
|
+
try {
|
|
15085
|
+
return buffer === void 0 ? "" : new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
15086
|
+
} catch {
|
|
15087
|
+
throw protocolFailure2("invalid_utf8");
|
|
15088
|
+
}
|
|
15089
|
+
}
|
|
15090
|
+
function webhookUrlFor(backendUrl, serverId) {
|
|
15091
|
+
return new URL(`/automations/webhook/${encodeURIComponent(serverId)}`, normalizedBackend(backendUrl)).toString();
|
|
15092
|
+
}
|
|
15093
|
+
function normalizedBackend(backendUrl) {
|
|
15094
|
+
let parsed;
|
|
15095
|
+
try {
|
|
15096
|
+
parsed = new URL(backendUrl);
|
|
15097
|
+
} catch {
|
|
15098
|
+
throw new CliError("profile_invalid", "Server backend URL is invalid.");
|
|
15099
|
+
}
|
|
15100
|
+
const loopback = parsed.hostname === "localhost" || parsed.hostname === "[::1]" || /^127(?:\.[0-9]{1,3}){3}$/.test(parsed.hostname);
|
|
15101
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopback) || parsed.username || parsed.password || backendUrl.includes("?") || backendUrl.includes("#") || parsed.pathname !== "/") {
|
|
15102
|
+
throw new CliError("profile_invalid", "Server backend must be an HTTPS origin (HTTP only on loopback), without credentials, query, or fragment.");
|
|
15103
|
+
}
|
|
15104
|
+
return parsed.toString();
|
|
15105
|
+
}
|
|
15106
|
+
async function readBounded2(response, limit) {
|
|
15107
|
+
const declared = response.headers.get("content-length");
|
|
15108
|
+
if (declared !== null) {
|
|
15109
|
+
const bytes = Number(declared);
|
|
15110
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > limit) {
|
|
15111
|
+
throw protocolFailure2("response_oversized");
|
|
15112
|
+
}
|
|
15113
|
+
}
|
|
15114
|
+
if (!response.body) return Buffer.alloc(0);
|
|
15115
|
+
const reader = response.body.getReader();
|
|
15116
|
+
const chunks = [];
|
|
15117
|
+
let total = 0;
|
|
15118
|
+
try {
|
|
15119
|
+
for (; ; ) {
|
|
15120
|
+
const { done, value } = await reader.read();
|
|
15121
|
+
if (done) break;
|
|
15122
|
+
total += value.byteLength;
|
|
15123
|
+
if (total > limit) {
|
|
15124
|
+
await reader.cancel().catch(() => void 0);
|
|
15125
|
+
throw protocolFailure2("response_oversized");
|
|
15126
|
+
}
|
|
15127
|
+
chunks.push(Buffer.from(value));
|
|
15128
|
+
}
|
|
15129
|
+
} finally {
|
|
15130
|
+
reader.releaseLock();
|
|
15131
|
+
}
|
|
15132
|
+
return Buffer.concat(chunks);
|
|
15133
|
+
}
|
|
15134
|
+
async function call(deps, backendUrl, token, path, body, timeoutMs) {
|
|
15135
|
+
const controller2 = new AbortController();
|
|
15136
|
+
const abort2 = () => controller2.abort();
|
|
15137
|
+
if (deps.signal?.aborted) controller2.abort();
|
|
15138
|
+
else deps.signal?.addEventListener("abort", abort2, { once: true });
|
|
15139
|
+
const timer = setTimeout(() => controller2.abort(), timeoutMs);
|
|
15140
|
+
const cleanup = () => {
|
|
15141
|
+
clearTimeout(timer);
|
|
15142
|
+
deps.signal?.removeEventListener("abort", abort2);
|
|
15143
|
+
};
|
|
15144
|
+
let response;
|
|
15145
|
+
try {
|
|
15146
|
+
if (controller2.signal.aborted) throw new Error("aborted");
|
|
15147
|
+
response = await deps.fetch(new URL(path, normalizedBackend(backendUrl)).toString(), {
|
|
15148
|
+
method: "POST",
|
|
15149
|
+
headers: {
|
|
15150
|
+
accept: "application/proto",
|
|
15151
|
+
"content-type": "application/proto",
|
|
15152
|
+
authorization: `Bearer ${token}`
|
|
15153
|
+
},
|
|
15154
|
+
body: new Uint8Array(body),
|
|
15155
|
+
cache: "no-store",
|
|
15156
|
+
redirect: "manual",
|
|
15157
|
+
signal: controller2.signal
|
|
15158
|
+
});
|
|
15159
|
+
} catch {
|
|
15160
|
+
cleanup();
|
|
15161
|
+
throw new CliError(
|
|
15162
|
+
"gateway_unreachable",
|
|
15163
|
+
controller2.signal.aborted ? "The Server Routine request timed out." : "The Server Routine endpoint is unreachable.",
|
|
15164
|
+
{ failureCode: controller2.signal.aborted ? "request_timeout" : "network_failure", retryable: true }
|
|
15165
|
+
);
|
|
15166
|
+
}
|
|
15167
|
+
try {
|
|
15168
|
+
if (controller2.signal.aborted) {
|
|
15169
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15170
|
+
throw protocolFailure2("request_cancelled");
|
|
15171
|
+
}
|
|
15172
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
15173
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15174
|
+
throw protocolFailure2("redirect_refused");
|
|
15175
|
+
}
|
|
15176
|
+
if (response.status === 401 || response.status === 403) {
|
|
15177
|
+
const detail = await readBounded2(response, MAX_RESPONSE_BYTES2).then((buffer) => buffer.toString("utf8")).catch(() => "");
|
|
15178
|
+
throw authorizationFailure2(authorizationFailureCode(detail));
|
|
15179
|
+
}
|
|
15180
|
+
if (response.status === 404) {
|
|
15181
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15182
|
+
throw new CliError("target_not_found", "The Server does not know that Routine.", {
|
|
15183
|
+
failureCode: "routine_not_found",
|
|
15184
|
+
httpStatus: 404
|
|
15185
|
+
});
|
|
15186
|
+
}
|
|
15187
|
+
if (response.status === 408 || response.status === 429 || response.status >= 500) {
|
|
15188
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15189
|
+
throw new CliError("gateway_unreachable", "The Server Routine endpoint refused the request.", {
|
|
15190
|
+
failureCode: "provider_refused",
|
|
15191
|
+
httpStatus: response.status,
|
|
15192
|
+
retryable: true
|
|
15193
|
+
});
|
|
15194
|
+
}
|
|
15195
|
+
if (response.status !== 200) {
|
|
15196
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15197
|
+
throw new CliError(
|
|
15198
|
+
"capability_unavailable",
|
|
15199
|
+
"The Server Routine method is unavailable for this credential.",
|
|
15200
|
+
{ failureCode: "unexpected_status", httpStatus: response.status }
|
|
15201
|
+
);
|
|
15202
|
+
}
|
|
15203
|
+
if (response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/proto") {
|
|
15204
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15205
|
+
throw protocolFailure2("response_content_type_invalid");
|
|
15206
|
+
}
|
|
15207
|
+
return await readBounded2(response, MAX_RESPONSE_BYTES2);
|
|
15208
|
+
} catch (error) {
|
|
15209
|
+
if (error instanceof CliError) throw error;
|
|
15210
|
+
throw new CliError(
|
|
15211
|
+
"gateway_unreachable",
|
|
15212
|
+
controller2.signal.aborted ? "The Server Routine request timed out." : "The Server Routine response could not be read.",
|
|
15213
|
+
{ failureCode: controller2.signal.aborted ? "request_timeout" : "response_unreadable", retryable: true }
|
|
15214
|
+
);
|
|
15215
|
+
} finally {
|
|
15216
|
+
cleanup();
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
function authorizationFailureCode(detail) {
|
|
15220
|
+
if (/Only the automation owner/i.test(detail)) return "not_routine_owner";
|
|
15221
|
+
if (/missing required scope/i.test(detail)) return "scope_mismatch";
|
|
15222
|
+
if (/Invalid API key/i.test(detail)) return "invalid_api_key";
|
|
15223
|
+
if (/ERROR_NOT_LOGGED_IN|not logged in/i.test(detail)) return "not_signed_in";
|
|
15224
|
+
return "credential_refused";
|
|
15225
|
+
}
|
|
15226
|
+
async function listServerAgents(deps, options2) {
|
|
15227
|
+
const body = await call(deps, options2.backendUrl, options2.token, SERVER_ROUTINE_PATHS.listAgents, Buffer.alloc(0), options2.timeoutMs);
|
|
15228
|
+
const agents = [];
|
|
15229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15230
|
+
const fields = decodeFields(body);
|
|
15231
|
+
const truncated = fields.filter((field) => field.number === 3);
|
|
15232
|
+
if (truncated.length > 1 || truncated[0] && truncated[0].wire !== 0) throw protocolFailure2("agent_truncation_invalid");
|
|
15233
|
+
if (truncated[0] && truncated[0].value !== 0n) throw protocolFailure2("agent_roster_truncated");
|
|
15234
|
+
for (const field of fields) {
|
|
15235
|
+
if (field.number !== 1) continue;
|
|
15236
|
+
if (!(field.value instanceof Buffer) || field.wire !== 2) throw protocolFailure2("agent_envelope_invalid");
|
|
15237
|
+
const inner = decodeFields(field.value);
|
|
15238
|
+
const id = text(firstBytes(inner, 12));
|
|
15239
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id) || seen.has(id)) {
|
|
15240
|
+
throw protocolFailure2("agent_id_invalid");
|
|
15241
|
+
}
|
|
15242
|
+
seen.add(id);
|
|
15243
|
+
const kinds = inner.filter((field2) => field2.number === 19);
|
|
15244
|
+
if (kinds.length > 1 || kinds[0] && kinds[0].wire !== 0) throw protocolFailure2("agent_kind_invalid");
|
|
15245
|
+
const kind = kinds[0]?.value ?? 0n;
|
|
15246
|
+
if (kind !== 0n && kind !== 1n && kind !== 2n) throw protocolFailure2("agent_kind_unknown");
|
|
15247
|
+
const harness = text(firstBytes(inner, 13));
|
|
15248
|
+
const name = text(firstBytes(inner, 3));
|
|
15249
|
+
if (name.length > 512 || /[\x00-\x1f\x7f]/.test(name)) throw protocolFailure2("agent_name_invalid");
|
|
15250
|
+
agents.push({
|
|
15251
|
+
id,
|
|
15252
|
+
name,
|
|
15253
|
+
harness: harness === "temporal" || harness === "box" ? harness : null,
|
|
15254
|
+
isGroup: kind === 2n
|
|
15255
|
+
});
|
|
15256
|
+
if (agents.length > MAX_AGENTS) throw protocolFailure2("too_many_agents");
|
|
15257
|
+
}
|
|
15258
|
+
return agents;
|
|
15259
|
+
}
|
|
15260
|
+
async function listServerRoutines(deps, options2) {
|
|
15261
|
+
const body = await call(
|
|
15262
|
+
deps,
|
|
15263
|
+
options2.backendUrl,
|
|
15264
|
+
options2.token,
|
|
15265
|
+
SERVER_ROUTINE_PATHS.listAutomations,
|
|
15266
|
+
lengthDelimited(1, Buffer.from(options2.agentId, "utf8")),
|
|
15267
|
+
options2.timeoutMs
|
|
15268
|
+
);
|
|
15269
|
+
const records = [];
|
|
15270
|
+
const serverIds = [];
|
|
15271
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15272
|
+
for (const field of decodeFields(body)) {
|
|
15273
|
+
if (field.number !== 1) continue;
|
|
15274
|
+
if (!(field.value instanceof Buffer) || field.wire !== 2) throw protocolFailure2("routine_envelope_invalid");
|
|
15275
|
+
const inner = decodeFields(field.value);
|
|
15276
|
+
const serverId = text(firstBytes(inner, 1));
|
|
15277
|
+
const recordBytes = firstBytes(inner, 2);
|
|
15278
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,255}$/.test(serverId) || seen.has(serverId)) {
|
|
15279
|
+
throw protocolFailure2("server_id_invalid");
|
|
15280
|
+
}
|
|
15281
|
+
if (recordBytes === void 0) throw protocolFailure2("record_missing");
|
|
15282
|
+
if (recordBytes.length > MAX_RECORD_BYTES) throw protocolFailure2("record_oversized");
|
|
15283
|
+
const json = text(recordBytes);
|
|
15284
|
+
let record3;
|
|
15285
|
+
try {
|
|
15286
|
+
record3 = JSON.parse(json);
|
|
15287
|
+
} catch {
|
|
15288
|
+
throw protocolFailure2("record_not_json");
|
|
15289
|
+
}
|
|
15290
|
+
if (!isRecord(record3)) throw protocolFailure2("record_not_object");
|
|
15291
|
+
seen.add(serverId);
|
|
15292
|
+
records.push(record3);
|
|
15293
|
+
serverIds.push(serverId);
|
|
15294
|
+
if (records.length > MAX_ROUTINES) throw protocolFailure2("too_many_routines");
|
|
15295
|
+
}
|
|
15296
|
+
try {
|
|
15297
|
+
return projectNativeRoutines(options2.agentId, records).catalog.routines.map((routine, index) => ({
|
|
15298
|
+
...routine,
|
|
15299
|
+
mutable: false,
|
|
15300
|
+
serverId: serverIds[index],
|
|
15301
|
+
webhookUrl: routine.trigger.type === "webhook" ? webhookUrlFor(options2.backendUrl, serverIds[index]) : null
|
|
15302
|
+
}));
|
|
15303
|
+
} catch (error) {
|
|
15304
|
+
if (error instanceof CliError) throw error;
|
|
15305
|
+
throw protocolFailure2("routine_record_invalid");
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15308
|
+
function assertWebhookKey(value) {
|
|
15309
|
+
if (value.length < 1 || Buffer.byteLength(value, "utf8") > MAX_KEY_BYTES || /[\x00-\x20\x7f]/.test(value)) {
|
|
15310
|
+
throw protocolFailure2("key_invalid");
|
|
15311
|
+
}
|
|
15312
|
+
return value;
|
|
15313
|
+
}
|
|
15314
|
+
async function mintWebhookKey(deps, options2) {
|
|
15315
|
+
const body = await call(
|
|
15316
|
+
deps,
|
|
15317
|
+
options2.backendUrl,
|
|
15318
|
+
options2.token,
|
|
15319
|
+
SERVER_ROUTINE_PATHS.mintWebhookKey,
|
|
15320
|
+
lengthDelimited(1, Buffer.from(options2.serverId, "utf8")),
|
|
15321
|
+
options2.timeoutMs
|
|
15322
|
+
);
|
|
15323
|
+
const key = text(firstBytes(decodeFields(body), 1));
|
|
15324
|
+
if (key.length === 0) throw protocolFailure2("key_missing");
|
|
15325
|
+
return assertWebhookKey(key);
|
|
15326
|
+
}
|
|
15327
|
+
async function probeWebhookKey(deps, options2) {
|
|
15328
|
+
if (options2.serverId === NIL_AUTOMATION_ID) throw protocolFailure2("unsafe_probe_target");
|
|
15329
|
+
const controller2 = new AbortController();
|
|
15330
|
+
const abort2 = () => controller2.abort();
|
|
15331
|
+
if (deps.signal?.aborted) controller2.abort();
|
|
15332
|
+
else deps.signal?.addEventListener("abort", abort2, { once: true });
|
|
15333
|
+
const timer = setTimeout(abort2, options2.timeoutMs);
|
|
15334
|
+
try {
|
|
15335
|
+
if (controller2.signal.aborted) throw protocolFailure2("request_cancelled");
|
|
15336
|
+
const response = await deps.fetch(webhookUrlFor(options2.backendUrl, NIL_AUTOMATION_ID), {
|
|
15337
|
+
method: "POST",
|
|
15338
|
+
headers: { authorization: `Bearer ${options2.key}`, "content-type": "application/json" },
|
|
15339
|
+
body: "{}",
|
|
15340
|
+
cache: "no-store",
|
|
15341
|
+
redirect: "manual",
|
|
15342
|
+
signal: controller2.signal
|
|
15343
|
+
});
|
|
15344
|
+
const detail = JSON.parse(text(await readBounded2(response, 64 * 1024)));
|
|
15345
|
+
if (controller2.signal.aborted) throw protocolFailure2("request_cancelled");
|
|
15346
|
+
if (!response.redirected && response.status === 401 && isRecord(detail) && detail.code === "error") {
|
|
15347
|
+
if (detail.message === `User API key missing required scope: automation:${NIL_AUTOMATION_ID}`) return true;
|
|
15348
|
+
if (detail.message === "Invalid API key") return false;
|
|
15349
|
+
}
|
|
15350
|
+
throw protocolFailure2("verification_unknown");
|
|
15351
|
+
} catch (error) {
|
|
15352
|
+
if (error instanceof CliError) throw error;
|
|
15353
|
+
throw protocolFailure2(controller2.signal.aborted ? "request_cancelled" : "verification_unknown");
|
|
15354
|
+
} finally {
|
|
15355
|
+
clearTimeout(timer);
|
|
15356
|
+
deps.signal?.removeEventListener("abort", abort2);
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
async function cachedWebhookKey(deps, options2) {
|
|
15360
|
+
if (options2.routine.createdAtMs === null) throw protocolFailure2("routine_incarnation_missing");
|
|
15361
|
+
const scope = createHash8("sha256").update(JSON.stringify([
|
|
15362
|
+
normalizedBackend(options2.backendUrl),
|
|
15363
|
+
options2.credentialScope ?? createHash8("sha256").update(options2.token).digest("hex"),
|
|
15364
|
+
options2.agentId,
|
|
15365
|
+
options2.routine.serverId,
|
|
15366
|
+
options2.routine.createdAtMs
|
|
15367
|
+
])).digest("hex");
|
|
15368
|
+
const directory = join11(options2.configDir, "server-webhook-keys");
|
|
15369
|
+
const path = join11(directory, `${scope}.json`);
|
|
15370
|
+
const lockPath = join11(directory, `${scope}.lock`);
|
|
15371
|
+
let lock;
|
|
15372
|
+
let temporary;
|
|
15373
|
+
let preserveLock = false;
|
|
15374
|
+
try {
|
|
15375
|
+
await mkdir9(directory, { recursive: true, mode: 448 });
|
|
15376
|
+
const dir = await lstat5(directory);
|
|
15377
|
+
if (!dir.isDirectory() || (dir.mode & 63) !== 0 || process.getuid && dir.uid !== process.getuid()) throw protocolFailure2("key_store_permissions");
|
|
15378
|
+
lock = await open6(lockPath, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY | constants4.O_NOFOLLOW, 384);
|
|
15379
|
+
let cached;
|
|
15380
|
+
try {
|
|
15381
|
+
cached = await open6(path, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
15382
|
+
} catch (error) {
|
|
15383
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
15384
|
+
}
|
|
15385
|
+
if (cached) {
|
|
15386
|
+
try {
|
|
15387
|
+
const stat7 = await cached.stat();
|
|
15388
|
+
if (!stat7.isFile() || (stat7.mode & 63) !== 0 || stat7.nlink !== 1 || stat7.size > 8192 || process.getuid && stat7.uid !== process.getuid()) throw protocolFailure2("key_store_permissions");
|
|
15389
|
+
const bytes = Buffer.alloc(8193);
|
|
15390
|
+
const { bytesRead } = await cached.read(bytes, 0, bytes.length, 0);
|
|
15391
|
+
if (bytesRead > 8192) throw protocolFailure2("key_store_corrupt");
|
|
15392
|
+
const record3 = JSON.parse(text(bytes.subarray(0, bytesRead)));
|
|
15393
|
+
if (!isRecord(record3) || record3.version !== 1 || record3.scope !== scope || typeof record3.key !== "string" || Object.keys(record3).length !== 3) throw protocolFailure2("key_store_corrupt");
|
|
15394
|
+
return assertWebhookKey(record3.key);
|
|
15395
|
+
} finally {
|
|
15396
|
+
await cached.close();
|
|
15397
|
+
}
|
|
15398
|
+
}
|
|
15399
|
+
if (deps.signal?.aborted) throw protocolFailure2("request_cancelled");
|
|
15400
|
+
temporary = join11(directory, `${scope}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
15401
|
+
const output = await (deps.openWebhookKeyStore ?? open6)(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY | constants4.O_NOFOLLOW, 384);
|
|
15402
|
+
try {
|
|
15403
|
+
if (deps.signal?.aborted) throw protocolFailure2("request_cancelled");
|
|
15404
|
+
preserveLock = true;
|
|
15405
|
+
const key = await mintWebhookKey(deps, { ...options2, serverId: options2.routine.serverId });
|
|
15406
|
+
await output.writeFile(JSON.stringify({ version: 1, scope, key }), "utf8");
|
|
15407
|
+
await output.sync();
|
|
15408
|
+
await output.close();
|
|
15409
|
+
await rename6(temporary, path);
|
|
15410
|
+
temporary = void 0;
|
|
15411
|
+
preserveLock = false;
|
|
15412
|
+
return key;
|
|
15413
|
+
} finally {
|
|
15414
|
+
await output.close().catch(() => void 0);
|
|
15415
|
+
}
|
|
15416
|
+
} catch (error) {
|
|
15417
|
+
if (error instanceof CliError && (error.code === "credential_invalid" || error.code === "target_not_found" || error.httpStatus !== void 0 && error.httpStatus >= 400 && error.httpStatus < 500 && error.httpStatus !== 408)) {
|
|
15418
|
+
preserveLock = false;
|
|
15419
|
+
}
|
|
15420
|
+
if (preserveLock) {
|
|
15421
|
+
throw new CliError("operation_outcome_unknown", "Webhook key mint outcome is unknown; no automatic retry is safe. Inspect the protected store lock before another attempt.", {
|
|
15422
|
+
failureCode: "mint_outcome_unknown",
|
|
15423
|
+
retryable: false
|
|
15424
|
+
});
|
|
15425
|
+
}
|
|
15426
|
+
if (error instanceof CliError) throw error;
|
|
15427
|
+
throw protocolFailure2("key_store_unavailable");
|
|
15428
|
+
} finally {
|
|
15429
|
+
if (temporary) await unlink3(temporary).catch(() => void 0);
|
|
15430
|
+
if (lock) {
|
|
15431
|
+
await lock.close();
|
|
15432
|
+
if (!preserveLock) await unlink3(lockPath).catch(() => void 0);
|
|
15433
|
+
}
|
|
15434
|
+
}
|
|
15435
|
+
}
|
|
15436
|
+
|
|
15437
|
+
// src/box-routine-auth.ts
|
|
15438
|
+
var ENV_LIMIT = 256 * 1024;
|
|
15439
|
+
var RESPONSE_LIMIT = 32 * 1024;
|
|
15440
|
+
async function boundedFile(path, limit) {
|
|
15441
|
+
const handle = await open7(path, constants5.O_RDONLY | constants5.O_NOFOLLOW);
|
|
15442
|
+
try {
|
|
15443
|
+
const buffer = Buffer.alloc(limit + 1);
|
|
15444
|
+
let length = 0;
|
|
15445
|
+
while (length < buffer.length) {
|
|
15446
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
|
|
15447
|
+
if (!bytesRead) break;
|
|
15448
|
+
length += bytesRead;
|
|
15449
|
+
}
|
|
15450
|
+
if (length > limit) throw new Error("bounded process read");
|
|
15451
|
+
return buffer.subarray(0, length);
|
|
15452
|
+
} finally {
|
|
15453
|
+
await handle.close();
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
var productionIO = {
|
|
15457
|
+
platform: process.platform,
|
|
15458
|
+
uid: process.getuid?.() ?? -1,
|
|
15459
|
+
async process(pid) {
|
|
15460
|
+
const root = `/proc/${pid}`;
|
|
15461
|
+
const metadata = await stat6(root);
|
|
15462
|
+
const status = (await boundedFile(`${root}/status`, 32 * 1024)).toString("utf8");
|
|
15463
|
+
const uids = /^Uid:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/m.exec(status);
|
|
15464
|
+
if (!uids || uids.slice(1).some((uid) => Number(uid) !== metadata.uid)) throw new Error("process owner");
|
|
15465
|
+
const raw = (await boundedFile(`${root}/stat`, 8192)).toString("utf8");
|
|
15466
|
+
const start = raw.slice(raw.lastIndexOf(")") + 2).split(" ")[19];
|
|
15467
|
+
if (!start || !/^\d+$/.test(start)) throw new Error("process start");
|
|
15468
|
+
return { uid: metadata.uid, start, cmdline: (await boundedFile(`${root}/cmdline`, 8192)).toString("utf8") };
|
|
15469
|
+
},
|
|
15470
|
+
environment: async (pid) => await boundedFile(`/proc/${pid}/environ`, ENV_LIMIT)
|
|
15471
|
+
};
|
|
15472
|
+
function nativeRoutineAuthEligible(deps) {
|
|
15473
|
+
return (deps.boxRoutineAuthIO ?? productionIO).platform === "linux" && (deps.transport === "auto" || deps.transport === "local") && !deps.gatewayServerUrl && !deps.daemonServerUrl && !deps.sshHost;
|
|
15474
|
+
}
|
|
15475
|
+
function validHost(value, uid) {
|
|
15476
|
+
const args = value.cmdline.split("\0");
|
|
15477
|
+
if (args.at(-1) === "") args.pop();
|
|
15478
|
+
return uid >= 0 && value.uid === uid && /^\d+$/.test(value.start) && args[0] === "/exec-daemon/node" && args.at(-1) === "/opt/sand/sand-host/host-main.cjs" && args.slice(1, -1).every((arg) => arg === "--disable-warning=ExperimentalWarning");
|
|
15479
|
+
}
|
|
15480
|
+
function sameDiscovery(a, b) {
|
|
15481
|
+
return a.pid === b.pid && a.startedAt === b.startedAt && a.baseUrl === b.baseUrl && a.token === b.token;
|
|
15482
|
+
}
|
|
15483
|
+
function failure(code) {
|
|
15484
|
+
return new CliError("credential_invalid", "Current-box Routine authentication could not be established safely.", { failureCode: code });
|
|
15485
|
+
}
|
|
15486
|
+
async function resolveBoxRoutineAuth(deps, timeoutMs) {
|
|
15487
|
+
if (!nativeRoutineAuthEligible(deps)) return null;
|
|
15488
|
+
if (deps.signal?.aborted) throw failure("request_cancelled");
|
|
15489
|
+
const io = deps.boxRoutineAuthIO ?? productionIO;
|
|
15490
|
+
let discovery;
|
|
15491
|
+
let environment;
|
|
15492
|
+
try {
|
|
15493
|
+
discovery = await readDiscovery(deps);
|
|
15494
|
+
if (!Number.isSafeInteger(discovery.pid) || discovery.pid <= 0 || !Number.isSafeInteger(discovery.startedAt) || discovery.startedAt <= 0) return null;
|
|
15495
|
+
const before = await io.process(discovery.pid);
|
|
15496
|
+
if (!validHost(before, io.uid)) return null;
|
|
15497
|
+
const healthy = await new GatewayClient({ ...deps, transport: "local" }).health(timeoutMs);
|
|
15498
|
+
if (!sameDiscovery(discovery, healthy.discovery) || healthy.health.pid !== discovery.pid || healthy.health.startedAt !== discovery.startedAt) return null;
|
|
15499
|
+
environment = await io.environment(discovery.pid);
|
|
15500
|
+
const after = await io.process(discovery.pid);
|
|
15501
|
+
if (!validHost(after, io.uid) || before.start !== after.start || before.cmdline !== after.cmdline || !sameDiscovery(discovery, await readDiscovery(deps))) return null;
|
|
15502
|
+
} catch {
|
|
15503
|
+
if (deps.signal?.aborted) throw failure("request_cancelled");
|
|
15504
|
+
return null;
|
|
15505
|
+
}
|
|
15506
|
+
if (environment.length > ENV_LIMIT) throw failure("renewal_credential_oversized");
|
|
15507
|
+
const entries = environment.toString("utf8").split("\0").filter((entry) => entry.startsWith("SAND_INFERENCE_RENEWAL_CREDENTIAL="));
|
|
15508
|
+
if (entries.length === 0) return null;
|
|
15509
|
+
const credential = entries[0].slice("SAND_INFERENCE_RENEWAL_CREDENTIAL=".length).trim();
|
|
15510
|
+
if (entries.length !== 1 || !credential || Buffer.byteLength(credential) > 16 * 1024 || /[\r\n]/.test(credential)) {
|
|
15511
|
+
throw failure("renewal_credential_invalid");
|
|
15512
|
+
}
|
|
15513
|
+
const metadata = {};
|
|
15514
|
+
for (const entry of environment.toString("utf8").split("\0")) {
|
|
15515
|
+
const separator = entry.indexOf("=");
|
|
15516
|
+
const key = entry.slice(0, separator);
|
|
15517
|
+
if (!["SAND_CLIENT_APP_VERSION", "SAND_BOX_OWNER_NAMESPACE", "SAND_BACKEND_URL", "CURSOR_API_BASE_URL", "SAND_PACKAGED", "SAND_LAB"].includes(key)) continue;
|
|
15518
|
+
const value = entry.slice(separator + 1);
|
|
15519
|
+
if (metadata[key] !== void 0 || value.length > 1024 || /[^\x20-\x7e]/.test(value)) throw failure("renewal_metadata_invalid");
|
|
15520
|
+
metadata[key] = value;
|
|
15521
|
+
}
|
|
15522
|
+
for (const key of ["SAND_BACKEND_URL", "CURSOR_API_BASE_URL"]) {
|
|
15523
|
+
const origin = metadata[key];
|
|
15524
|
+
if (origin && origin !== DEFAULT_SERVER_BACKEND_URL && origin !== `${DEFAULT_SERVER_BACKEND_URL}/`) throw failure("renewal_backend_unsupported");
|
|
15525
|
+
}
|
|
15526
|
+
const ownerNamespace = metadata.SAND_BOX_OWNER_NAMESPACE?.trim();
|
|
15527
|
+
if (metadata.SAND_PACKAGED !== "1" || metadata.SAND_LAB === "1" || ownerNamespace === "dev" || ownerNamespace === "lab") {
|
|
15528
|
+
throw failure("renewal_namespace_unsupported");
|
|
15529
|
+
}
|
|
15530
|
+
const namespace = "prod";
|
|
15531
|
+
const clientVersion = /^(\d+\.\d+\.\d+)(?:-.+)?$/.exec(metadata.SAND_CLIENT_APP_VERSION?.trim() ?? "")?.[1] ?? "0.1.0";
|
|
15532
|
+
const credentialScope = createHash9("sha256").update(JSON.stringify([
|
|
15533
|
+
"box-routine-auth-v1",
|
|
15534
|
+
DEFAULT_SERVER_BACKEND_URL,
|
|
15535
|
+
namespace,
|
|
15536
|
+
credential
|
|
15537
|
+
])).digest("hex");
|
|
15538
|
+
const controller2 = new AbortController();
|
|
15539
|
+
const abort2 = () => controller2.abort();
|
|
15540
|
+
deps.signal?.addEventListener("abort", abort2, { once: true });
|
|
15541
|
+
const timer = setTimeout(abort2, timeoutMs);
|
|
15542
|
+
try {
|
|
15543
|
+
if (deps.signal?.aborted) controller2.abort();
|
|
15544
|
+
if (controller2.signal.aborted) throw failure("request_cancelled");
|
|
15545
|
+
const response = await deps.fetch(`${DEFAULT_SERVER_BACKEND_URL}/sand-box/inference-credential`, {
|
|
15546
|
+
method: "POST",
|
|
15547
|
+
headers: {
|
|
15548
|
+
"content-type": "application/json",
|
|
15549
|
+
"x-cursor-client-type": "sand",
|
|
15550
|
+
"x-cursor-client-source": "sand-desktop",
|
|
15551
|
+
"x-cursor-client-version": clientVersion,
|
|
15552
|
+
"x-sand-box-namespace": namespace
|
|
15553
|
+
},
|
|
15554
|
+
body: JSON.stringify({ credential }),
|
|
15555
|
+
redirect: "manual",
|
|
15556
|
+
cache: "no-store",
|
|
15557
|
+
signal: controller2.signal
|
|
15558
|
+
});
|
|
15559
|
+
if (response.redirected || !response.ok) {
|
|
15560
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15561
|
+
throw failure(response.status >= 300 && response.status < 400 ? "renewal_redirect_refused" : response.status === 401 || response.status === 403 ? "renewal_unauthorized" : "renewal_unavailable");
|
|
15562
|
+
}
|
|
15563
|
+
const declared = response.headers.get("content-length");
|
|
15564
|
+
if (declared !== null && (!Number.isSafeInteger(Number(declared)) || Number(declared) < 0 || Number(declared) > RESPONSE_LIMIT)) {
|
|
15565
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15566
|
+
throw failure("renewal_response_oversized");
|
|
15567
|
+
}
|
|
15568
|
+
const reader = response.body?.getReader();
|
|
15569
|
+
const chunks = [];
|
|
15570
|
+
let size = 0;
|
|
15571
|
+
try {
|
|
15572
|
+
if (reader) for (; ; ) {
|
|
15573
|
+
const next = await reader.read();
|
|
15574
|
+
if (next.done) break;
|
|
15575
|
+
size += next.value.byteLength;
|
|
15576
|
+
if (size > RESPONSE_LIMIT) {
|
|
15577
|
+
await reader.cancel().catch(() => void 0);
|
|
15578
|
+
throw failure("renewal_response_oversized");
|
|
15579
|
+
}
|
|
15580
|
+
chunks.push(Buffer.from(next.value));
|
|
15581
|
+
}
|
|
15582
|
+
} finally {
|
|
15583
|
+
reader?.releaseLock();
|
|
15584
|
+
}
|
|
15585
|
+
if (controller2.signal.aborted) throw failure("request_cancelled");
|
|
15586
|
+
const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
15587
|
+
let expiresAtMs = isRecord(result) ? result.expiresAtMs : void 0;
|
|
15588
|
+
if (expiresAtMs === void 0 && isRecord(result) && typeof result.accessToken === "string") {
|
|
15589
|
+
try {
|
|
15590
|
+
const payload = JSON.parse(Buffer.from(result.accessToken.split(".")[1] ?? "", "base64url").toString("utf8"));
|
|
15591
|
+
if (isRecord(payload) && typeof payload.exp === "number") expiresAtMs = payload.exp * 1e3;
|
|
15592
|
+
} catch {
|
|
15593
|
+
}
|
|
15594
|
+
}
|
|
15595
|
+
if (!isRecord(result) || typeof result.accessToken !== "string" || !result.accessToken || Buffer.byteLength(result.accessToken) > 16 * 1024 || /[\s\0]/.test(result.accessToken) || typeof expiresAtMs !== "number" || !Number.isSafeInteger(expiresAtMs) || expiresAtMs <= deps.now() + 3e4) {
|
|
15596
|
+
throw failure("renewal_response_invalid");
|
|
15597
|
+
}
|
|
15598
|
+
return { backendUrl: DEFAULT_SERVER_BACKEND_URL, token: result.accessToken, credentialScope };
|
|
15599
|
+
} catch (error) {
|
|
15600
|
+
if (controller2.signal.aborted) throw failure("request_cancelled");
|
|
15601
|
+
if (error instanceof CliError) throw error;
|
|
15602
|
+
throw failure("renewal_response_invalid");
|
|
15603
|
+
} finally {
|
|
15604
|
+
clearTimeout(timer);
|
|
15605
|
+
deps.signal?.removeEventListener("abort", abort2);
|
|
15606
|
+
}
|
|
15607
|
+
}
|
|
15608
|
+
|
|
14916
15609
|
// src/commands/routines.ts
|
|
14917
15610
|
function routineCliError(error) {
|
|
14918
15611
|
if (error instanceof CliError) return error;
|
|
@@ -14984,7 +15677,8 @@ async function resolveRoutineAuthority(client2, agent, timeoutMs) {
|
|
|
14984
15677
|
if (error instanceof CliError && ["gateway_not_found", "gateway_bad_request", "capability_unavailable"].includes(error.code)) {
|
|
14985
15678
|
throw new CliError(
|
|
14986
15679
|
"capability_unavailable",
|
|
14987
|
-
"This Temporal Bot requires the Grokbox Temporal Routine bridge before Routine state can be read safely."
|
|
15680
|
+
"This Temporal Bot requires the Grokbox Temporal Routine bridge, or an explicit server.access_token_ref in the selected Profile, before Routine state can be read safely.",
|
|
15681
|
+
{ failureCode: "routine_bridge_missing" }
|
|
14988
15682
|
);
|
|
14989
15683
|
}
|
|
14990
15684
|
throw error;
|
|
@@ -15034,16 +15728,21 @@ function nativePrompts(value, catalog) {
|
|
|
15034
15728
|
}
|
|
15035
15729
|
return prompts;
|
|
15036
15730
|
}
|
|
15037
|
-
function findRoutine(snapshot,
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15731
|
+
function findRoutine(snapshot, target, allowName = false) {
|
|
15732
|
+
const exact = snapshot.catalog.routines.find((routine) => routine.id === target);
|
|
15733
|
+
if (exact) return exact;
|
|
15734
|
+
if (!allowName) {
|
|
15735
|
+
try {
|
|
15736
|
+
routineId(target);
|
|
15737
|
+
} catch (error) {
|
|
15738
|
+
throw routineCliError(error);
|
|
15739
|
+
}
|
|
15740
|
+
throw routineCliError(new RoutineError("not_found_in_window"));
|
|
15043
15741
|
}
|
|
15044
|
-
const
|
|
15045
|
-
if (
|
|
15046
|
-
|
|
15742
|
+
const named = snapshot.catalog.routines.filter((routine) => routine.name.toLocaleLowerCase() === target.toLocaleLowerCase());
|
|
15743
|
+
if (named.length > 1) throw new CliError("target_ambiguous", "Routine name matched more than one Routine.");
|
|
15744
|
+
if (!named[0]) throw routineCliError(new RoutineError("not_found_in_window"));
|
|
15745
|
+
return named[0];
|
|
15047
15746
|
}
|
|
15048
15747
|
function mutableRoutine(snapshot, id) {
|
|
15049
15748
|
const row = findRoutine(snapshot, id);
|
|
@@ -15104,7 +15803,7 @@ async function listOrShow(deps, client2, action, agentTarget, routineTarget, raw
|
|
|
15104
15803
|
writeSuccess(deps.stdout, { ...snapshot.catalog, authority: snapshot.authority }, snapshot.discovery);
|
|
15105
15804
|
return;
|
|
15106
15805
|
}
|
|
15107
|
-
const routine = findRoutine(snapshot, routineTarget ?? "");
|
|
15806
|
+
const routine = findRoutine(snapshot, routineTarget ?? "", true);
|
|
15108
15807
|
writeSuccess(
|
|
15109
15808
|
deps.stdout,
|
|
15110
15809
|
{ schemaVersion: 1, agentId: agent.id, authority: snapshot.authority, routine, coverage: snapshot.catalog.coverage },
|
|
@@ -15297,10 +15996,13 @@ async function setRoutineEnabled(deps, client2, agentTarget, routineTarget, acti
|
|
|
15297
15996
|
}
|
|
15298
15997
|
async function showWebhookCredential(deps, client2, agentTarget, routineTarget, raw) {
|
|
15299
15998
|
requireConfirmed(raw);
|
|
15999
|
+
if (raw.verify === true) {
|
|
16000
|
+
throw new CliError("capability_unavailable", "Webhook key verification requires an explicit cloud credential in the selected Profile; Host-provided credentials are not sent to additional endpoints.");
|
|
16001
|
+
}
|
|
15300
16002
|
const io = ioFromOpts(raw);
|
|
15301
16003
|
const agent = await resolveAgent(client2, agentTarget, io.timeoutMs);
|
|
15302
16004
|
const snapshot = await readSnapshot(client2, agent, io.timeoutMs);
|
|
15303
|
-
const routine = findRoutine(snapshot, routineTarget);
|
|
16005
|
+
const routine = findRoutine(snapshot, routineTarget, true);
|
|
15304
16006
|
if (routine.trigger.type !== "webhook") {
|
|
15305
16007
|
throw new CliError("capability_unavailable", "Routine is not webhook-triggered.");
|
|
15306
16008
|
}
|
|
@@ -15380,7 +16082,66 @@ async function deleteRoutine(deps, client2, agentTarget, routineTarget, raw) {
|
|
|
15380
16082
|
})
|
|
15381
16083
|
}, after.discovery);
|
|
15382
16084
|
}
|
|
16085
|
+
async function cloudRoutines(deps, action, agentTarget, routineTarget, raw, nativeAuth) {
|
|
16086
|
+
if (action === "webhook-credential") requireConfirmed(raw);
|
|
16087
|
+
const io = ioFromOpts(raw);
|
|
16088
|
+
const context = nativeAuth ? { ...nativeAuth, timeoutMs: io.timeoutMs } : {
|
|
16089
|
+
backendUrl: normalizedBackend(deps.serverBackendUrl ?? DEFAULT_SERVER_BACKEND_URL),
|
|
16090
|
+
token: await resolveSecretRef(deps, deps.serverAccessTokenRef),
|
|
16091
|
+
timeoutMs: io.timeoutMs
|
|
16092
|
+
};
|
|
16093
|
+
const agents = await listServerAgents(deps, context);
|
|
16094
|
+
const agent = findRosterRow(agents, agentTarget, ["agent"]);
|
|
16095
|
+
const agentId = asString(agent.id);
|
|
16096
|
+
const routines = await listServerRoutines(deps, { ...context, agentId });
|
|
16097
|
+
const coverage = { kind: "native_returned_window", limit: 100, atLimit: routines.length === 100, complete: false };
|
|
16098
|
+
const catalog = { schemaVersion: 1, agentId, routines, coverage };
|
|
16099
|
+
if (action === "list") {
|
|
16100
|
+
if (io.table) deps.stdout.write(listTable(catalog));
|
|
16101
|
+
else writeSuccess(deps.stdout, { ...catalog, authority: "server-temporal" });
|
|
16102
|
+
return;
|
|
16103
|
+
}
|
|
16104
|
+
let selected = routines.filter((routine2) => routine2.id === routineTarget || routine2.serverId === routineTarget);
|
|
16105
|
+
if (selected.length === 0) {
|
|
16106
|
+
selected = routines.filter((routine2) => routine2.name.toLocaleLowerCase() === (routineTarget ?? "").toLocaleLowerCase());
|
|
16107
|
+
}
|
|
16108
|
+
if (selected.length > 1) throw new CliError("target_ambiguous", "Routine target matched more than one Routine.");
|
|
16109
|
+
const routine = selected[0];
|
|
16110
|
+
if (!routine) throw new CliError("target_not_found", "No Routine matched that ID or name in the configured cloud account; no other identity was searched.");
|
|
16111
|
+
if (action === "show") {
|
|
16112
|
+
writeSuccess(deps.stdout, { schemaVersion: 1, agentId, authority: "server-temporal", routine, coverage });
|
|
16113
|
+
return;
|
|
16114
|
+
}
|
|
16115
|
+
if (routine.trigger.type !== "webhook") throw new CliError("capability_unavailable", "Routine is not webhook-triggered.");
|
|
16116
|
+
const key = await cachedWebhookKey(deps, { ...context, configDir: deps.configDir, agentId, routine });
|
|
16117
|
+
const keyLive = raw.verify === true ? await probeWebhookKey(deps, { ...context, key, serverId: routine.serverId }) : void 0;
|
|
16118
|
+
writeSuccess(deps.stdout, {
|
|
16119
|
+
schemaVersion: 1,
|
|
16120
|
+
agentId,
|
|
16121
|
+
routineId: routine.id,
|
|
16122
|
+
serverId: routine.serverId,
|
|
16123
|
+
authority: "server-temporal",
|
|
16124
|
+
url: routine.webhookUrl,
|
|
16125
|
+
keyPresent: true,
|
|
16126
|
+
keyIncluded: raw.revealKey === true,
|
|
16127
|
+
promptIncluded: false,
|
|
16128
|
+
...keyLive === void 0 ? {} : { keyLive },
|
|
16129
|
+
...raw.revealKey === true ? { key } : {}
|
|
16130
|
+
});
|
|
16131
|
+
}
|
|
15383
16132
|
async function runRoutines(deps, action, agentTarget, routineTarget, raw) {
|
|
16133
|
+
if (deps.serverAccessTokenRef !== void 0 && (action === "list" || action === "show" || action === "webhook-credential")) {
|
|
16134
|
+
await cloudRoutines(deps, action, agentTarget, routineTarget, raw);
|
|
16135
|
+
return;
|
|
16136
|
+
}
|
|
16137
|
+
if (action === "webhook-credential") requireConfirmed(raw);
|
|
16138
|
+
if (action === "list" || action === "show" || action === "webhook-credential") {
|
|
16139
|
+
const nativeAuth = await resolveBoxRoutineAuth(deps, ioFromOpts(raw).timeoutMs);
|
|
16140
|
+
if (nativeAuth) {
|
|
16141
|
+
await cloudRoutines(deps, action, agentTarget, routineTarget, raw, nativeAuth);
|
|
16142
|
+
return;
|
|
16143
|
+
}
|
|
16144
|
+
}
|
|
15384
16145
|
const client2 = new GatewayClient(deps);
|
|
15385
16146
|
if (action === "list" || action === "show") {
|
|
15386
16147
|
await listOrShow(deps, client2, action, agentTarget, routineTarget, raw);
|
|
@@ -15450,11 +16211,11 @@ async function runSend(deps, target, raw) {
|
|
|
15450
16211
|
}
|
|
15451
16212
|
|
|
15452
16213
|
// src/skills.ts
|
|
15453
|
-
import { join as
|
|
16214
|
+
import { join as join12 } from "node:path";
|
|
15454
16215
|
var CORE_SKILL_NAME = "core";
|
|
15455
16216
|
var CORE_SKILL_SUMMARY = "Core grokbox usage guide.";
|
|
15456
16217
|
async function loadCoreMarkdown(deps, full) {
|
|
15457
|
-
const overview = await deps.readFile(
|
|
16218
|
+
const overview = await deps.readFile(join12(deps.skillsDir, "core.md"));
|
|
15458
16219
|
if (!full) return overview.trimEnd() + "\n";
|
|
15459
16220
|
return `${overview.trimEnd()}
|
|
15460
16221
|
|
|
@@ -15517,9 +16278,9 @@ var FAMILY_DESCRIPTIONS = {
|
|
|
15517
16278
|
is: "Read state projections"
|
|
15518
16279
|
};
|
|
15519
16280
|
function publicCommanderMessage(error) {
|
|
15520
|
-
const
|
|
15521
|
-
if (/bearer|authorization|token/i.test(
|
|
15522
|
-
return
|
|
16281
|
+
const text2 = error.message.replace(/^error: /i, "").trim();
|
|
16282
|
+
if (/bearer|authorization|token/i.test(text2)) return "Invalid usage.";
|
|
16283
|
+
return text2.length > 0 ? text2 : "Invalid usage.";
|
|
15523
16284
|
}
|
|
15524
16285
|
function unexpectedMessage(error) {
|
|
15525
16286
|
if (error instanceof Error) {
|
|
@@ -15658,6 +16419,8 @@ function addLeaf(deps, program2, parents, leaf, action) {
|
|
|
15658
16419
|
sandboxKeepaliveIntervalMs: profile.sandbox?.keepalive_interval_ms,
|
|
15659
16420
|
quotaSource: profile.quota?.source,
|
|
15660
16421
|
quotaAccessTokenRef: profile.quota?.access_token_ref,
|
|
16422
|
+
serverAccessTokenRef: profile.server?.access_token_ref,
|
|
16423
|
+
serverBackendUrl: profile.server?.backend_url,
|
|
15661
16424
|
gatewayServerUrl: profile.gateway_url,
|
|
15662
16425
|
gatewayTokenRef: profile.gateway_token_ref,
|
|
15663
16426
|
gatewayHeadersRef: profile.gateway_headers_ref,
|