grokbox 0.1.0-alpha.9 → 0.1.0
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 +5 -5
- package/README.zh-CN.md +3 -3
- package/dist/index.js +660 -83
- package/package.json +1 -1
- package/skills/core.md +6 -1
package/README.md
CHANGED
|
@@ -10,9 +10,9 @@ This project is not affiliated with or endorsed by Anysphere, Cursor, xAI, or
|
|
|
10
10
|
Grok Bot. Grok Bot, Cursor, and related names identify compatible products and
|
|
11
11
|
remain the property of their respective owners.
|
|
12
12
|
|
|
13
|
-
> **
|
|
14
|
-
> `
|
|
15
|
-
>
|
|
13
|
+
> **Release:** current source is `0.1.0`. Stable versions publish to npm
|
|
14
|
+
> `latest`; prereleases use `next`. Upstream-private compatibility adapters
|
|
15
|
+
> remain experimental and subject to the documented acceptance limits.
|
|
16
16
|
|
|
17
17
|
## What it does
|
|
18
18
|
|
|
@@ -82,7 +82,7 @@ configured Profile is available, commands fail closed.
|
|
|
82
82
|
## Install
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
npm install --global grokbox@
|
|
85
|
+
npm install --global grokbox@latest
|
|
86
86
|
grokbox --version
|
|
87
87
|
gbox --help
|
|
88
88
|
```
|
|
@@ -147,7 +147,7 @@ grokbox jobs list --table
|
|
|
147
147
|
grokbox desktop status --table
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
Routine reads expose bounded metadata only: prompt bodies, webhook credentials, native paths, and unknown provider fields stay private.
|
|
150
|
+
Routine reads expose bounded metadata only: prompt bodies, webhook credentials, native paths, and unknown provider fields stay private. The same `agents routines list/show` and `agents routines webhook credential <agent> <routine-id> --confirm` commands use cloud SSOT when the selected Profile has `server.access_token_ref` (configure with `profile update <name> --server-access-token-ref file:/absolute/path/to/cloud-token`); otherwise they retain Gateway/Host behavior. Cloud keys are minted only after confirmation and reused from a scoped local cache; `--reveal-key` explicitly reveals the key, never a cloud prompt, and `--verify` never invokes the real Routine. Cloud reads are read-only; mutations retain the Host contract. Box-local create writes a disabled definition; later mutations require confirmation and a fresh revision. Temporal create/update remain unsupported. See the [Routine contract](docs/product-contract.md#711-native-routines) for setup, verification, and failure guidance.
|
|
151
151
|
|
|
152
152
|
Mutating commands require explicit capability and, where destructive, explicit
|
|
153
153
|
confirmation. `desktop prune run` defaults to dry-run; `--yes` invokes the
|
package/README.zh-CN.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
本项目与 Anysphere、Cursor、xAI 或 Grok Bot 没有隶属或背书关系。Grok Bot、Cursor 及相关名称仅用于标识兼容产品,其权利归各自所有者。
|
|
8
8
|
|
|
9
|
-
>
|
|
9
|
+
> **发布:** 当前源码版本是 `0.1.0`。正式版发布到 npm `latest`,预发布版使用 `next`。上游私有接口的兼容适配器仍为实验性,遵守文档中的验收范围与限制。
|
|
10
10
|
|
|
11
11
|
## 它能做什么
|
|
12
12
|
|
|
@@ -70,7 +70,7 @@ bun run grokbox -- doctor
|
|
|
70
70
|
## 安装
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
|
-
npm install --global grokbox@
|
|
73
|
+
npm install --global grokbox@latest
|
|
74
74
|
grokbox --version
|
|
75
75
|
gbox --help
|
|
76
76
|
```
|
|
@@ -128,7 +128,7 @@ grokbox jobs list --table
|
|
|
128
128
|
grokbox desktop status --table
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
Routine 读取只输出有界元数据,不暴露 prompt 正文、Webhook credential、原生路径或未知 provider
|
|
131
|
+
Routine 读取只输出有界元数据,不暴露 prompt 正文、Webhook credential、原生路径或未知 provider 字段。同一组 `agents routines list/show` 与 `agents routines webhook credential <agent> <routine-id> --confirm` 命令,在 selected Profile 配置 `server.access_token_ref` 时使用 cloud SSOT(通过 `profile update <name> --server-access-token-ref file:/absolute/path/to/cloud-token` 配置);未配置时保留 Gateway/Host 行为。Cloud key 仅在确认后铸造,并从按范围隔离的本地缓存复用;`--reveal-key` 显式输出 key,但不输出 cloud prompt;`--verify` 从不调用真实 Routine。Cloud 读取为只读,mutation 保留现有 Host 合同。Box-local create 创建 disabled definition,后续 mutation 需要确认与 fresh revision;Temporal create/update 仍不支持。配置、验证与失败处理见 [Routine 合同](docs/product-contract.md#711-native-routines)。
|
|
132
132
|
|
|
133
133
|
变更命令需要显式 capability;破坏性操作还需要显式确认。`desktop prune run` 默认 dry-run;传入 `--yes` 后会调用上游 stop-window 路径,并删除该 fork 的 Chrome profile。
|
|
134
134
|
|
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.0
|
|
3477
|
+
version: "0.1.0",
|
|
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 {
|
|
@@ -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,7 @@ 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;
|
|
14885
14917
|
writeSuccess(deps.stdout, {
|
|
14886
14918
|
profile: profile.name,
|
|
14887
14919
|
transport: profile.transport,
|
|
@@ -14908,11 +14940,494 @@ async function runProfileCapabilities(deps, name, raw) {
|
|
|
14908
14940
|
"sandbox.inspect": sandboxCapability,
|
|
14909
14941
|
"sandbox.wake": sandboxCapability,
|
|
14910
14942
|
"sandbox.keepalive": sandboxCapability,
|
|
14911
|
-
"quota.read": quotaCapability
|
|
14943
|
+
"quota.read": quotaCapability,
|
|
14944
|
+
"grok.routine.read": serverCapability || local || directGateway || daemon,
|
|
14945
|
+
"grok.routine.webhook": serverCapability || local || directGateway || daemon
|
|
14912
14946
|
}
|
|
14913
14947
|
});
|
|
14914
14948
|
}
|
|
14915
14949
|
|
|
14950
|
+
// src/server-routines.ts
|
|
14951
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
14952
|
+
import { constants as constants4 } from "node:fs";
|
|
14953
|
+
import { mkdir as mkdir9, open as open6, lstat as lstat5, rename as rename6, unlink as unlink3 } from "node:fs/promises";
|
|
14954
|
+
import { join as join11 } from "node:path";
|
|
14955
|
+
var SERVER_ROUTINE_PATHS = {
|
|
14956
|
+
listAgents: "/aiserver.v1.GrokBotService/ListGrokBotAgents",
|
|
14957
|
+
listAutomations: "/aiserver.v1.GrokBotService/ListGrokBotAgentAutomations",
|
|
14958
|
+
mintWebhookKey: "/aiserver.v1.DashboardService/CreateAutomationWebhookApiKey"
|
|
14959
|
+
};
|
|
14960
|
+
var DEFAULT_SERVER_BACKEND_URL = "https://api2.cursor.sh";
|
|
14961
|
+
var MAX_RESPONSE_BYTES2 = 8 * 1024 * 1024;
|
|
14962
|
+
var MAX_RECORD_BYTES = 512 * 1024;
|
|
14963
|
+
var MAX_KEY_BYTES = 4096;
|
|
14964
|
+
var MAX_AGENTS = 5e3;
|
|
14965
|
+
var MAX_ROUTINES = 100;
|
|
14966
|
+
var NIL_AUTOMATION_ID = "00000000-0000-5000-8000-000000000000";
|
|
14967
|
+
function protocolFailure2(failureCode) {
|
|
14968
|
+
return new CliError(
|
|
14969
|
+
"capability_unavailable",
|
|
14970
|
+
"The Server Routine surface did not satisfy the supported bounded protocol.",
|
|
14971
|
+
{ failureCode }
|
|
14972
|
+
);
|
|
14973
|
+
}
|
|
14974
|
+
function authorizationFailure2(failureCode) {
|
|
14975
|
+
return new CliError(
|
|
14976
|
+
"credential_invalid",
|
|
14977
|
+
"The configured Server credential was not authorized for Routine access.",
|
|
14978
|
+
{ failureCode }
|
|
14979
|
+
);
|
|
14980
|
+
}
|
|
14981
|
+
function varint(value) {
|
|
14982
|
+
const bytes = [];
|
|
14983
|
+
let rest = value;
|
|
14984
|
+
while (rest > 127) {
|
|
14985
|
+
bytes.push(rest & 127 | 128);
|
|
14986
|
+
rest = Math.floor(rest / 128);
|
|
14987
|
+
}
|
|
14988
|
+
bytes.push(rest);
|
|
14989
|
+
return Buffer.from(bytes);
|
|
14990
|
+
}
|
|
14991
|
+
function lengthDelimited(field, payload) {
|
|
14992
|
+
return Buffer.concat([varint(field << 3 | 2), varint(payload.length), payload]);
|
|
14993
|
+
}
|
|
14994
|
+
function readVarint(buffer, offset) {
|
|
14995
|
+
let value = 0n;
|
|
14996
|
+
for (let index = 0; index < 10; index += 1) {
|
|
14997
|
+
const byte = buffer[offset + index];
|
|
14998
|
+
if (byte === void 0) throw protocolFailure2("truncated_varint");
|
|
14999
|
+
if (index === 9 && byte > 1) throw protocolFailure2("varint_overflow");
|
|
15000
|
+
value |= BigInt(byte & 127) << BigInt(index * 7);
|
|
15001
|
+
if ((byte & 128) === 0) return { value, next: offset + index + 1 };
|
|
15002
|
+
}
|
|
15003
|
+
throw protocolFailure2("varint_overflow");
|
|
15004
|
+
}
|
|
15005
|
+
function decodeFields(buffer) {
|
|
15006
|
+
const fields = [];
|
|
15007
|
+
let offset = 0;
|
|
15008
|
+
while (offset < buffer.length) {
|
|
15009
|
+
const key = readVarint(buffer, offset);
|
|
15010
|
+
offset = key.next;
|
|
15011
|
+
if (key.value < 8n || key.value > 0xffffffffn) throw protocolFailure2("invalid_tag");
|
|
15012
|
+
const number = Number(key.value >> 3n);
|
|
15013
|
+
const wire = Number(key.value & 7n);
|
|
15014
|
+
if (wire === 0) {
|
|
15015
|
+
const value = readVarint(buffer, offset);
|
|
15016
|
+
offset = value.next;
|
|
15017
|
+
fields.push({ number, wire, value: value.value });
|
|
15018
|
+
} else if (wire === 2) {
|
|
15019
|
+
const size = readVarint(buffer, offset);
|
|
15020
|
+
offset = size.next;
|
|
15021
|
+
if (size.value > BigInt(buffer.length - offset)) throw protocolFailure2("truncated_field");
|
|
15022
|
+
const length = Number(size.value);
|
|
15023
|
+
fields.push({ number, wire, value: buffer.subarray(offset, offset + length) });
|
|
15024
|
+
offset += length;
|
|
15025
|
+
} else if (wire === 5) {
|
|
15026
|
+
if (offset + 4 > buffer.length) throw protocolFailure2("truncated_field");
|
|
15027
|
+
fields.push({ number, wire, value: buffer.subarray(offset, offset + 4) });
|
|
15028
|
+
offset += 4;
|
|
15029
|
+
} else if (wire === 1) {
|
|
15030
|
+
if (offset + 8 > buffer.length) throw protocolFailure2("truncated_field");
|
|
15031
|
+
fields.push({ number, wire, value: buffer.subarray(offset, offset + 8) });
|
|
15032
|
+
offset += 8;
|
|
15033
|
+
} else {
|
|
15034
|
+
if (wire === 3) {
|
|
15035
|
+
offset = skipGroup(buffer, offset, number, 0);
|
|
15036
|
+
fields.push({ number, wire, value: 0n });
|
|
15037
|
+
continue;
|
|
15038
|
+
}
|
|
15039
|
+
throw protocolFailure2("unsupported_wire_type");
|
|
15040
|
+
}
|
|
15041
|
+
}
|
|
15042
|
+
return fields;
|
|
15043
|
+
}
|
|
15044
|
+
function skipGroup(buffer, start, expected, depth) {
|
|
15045
|
+
if (depth > 32) throw protocolFailure2("group_depth_exceeded");
|
|
15046
|
+
let offset = start;
|
|
15047
|
+
while (offset < buffer.length) {
|
|
15048
|
+
const tag = readVarint(buffer, offset);
|
|
15049
|
+
if (tag.value < 8n || tag.value > 0xffffffffn) throw protocolFailure2("invalid_tag");
|
|
15050
|
+
const number = Number(tag.value >> 3n);
|
|
15051
|
+
const wire = Number(tag.value & 7n);
|
|
15052
|
+
offset = tag.next;
|
|
15053
|
+
if (wire === 4) {
|
|
15054
|
+
if (number !== expected) throw protocolFailure2("group_mismatch");
|
|
15055
|
+
return offset;
|
|
15056
|
+
}
|
|
15057
|
+
if (wire === 3) offset = skipGroup(buffer, offset, number, depth + 1);
|
|
15058
|
+
else if (wire === 0) offset = readVarint(buffer, offset).next;
|
|
15059
|
+
else if (wire === 1 || wire === 5) offset += wire === 1 ? 8 : 4;
|
|
15060
|
+
else if (wire === 2) {
|
|
15061
|
+
const size = readVarint(buffer, offset);
|
|
15062
|
+
if (size.value > BigInt(buffer.length - size.next)) throw protocolFailure2("truncated_field");
|
|
15063
|
+
offset = size.next + Number(size.value);
|
|
15064
|
+
} else throw protocolFailure2("unsupported_wire_type");
|
|
15065
|
+
if (offset > buffer.length) throw protocolFailure2("truncated_field");
|
|
15066
|
+
}
|
|
15067
|
+
throw protocolFailure2("truncated_group");
|
|
15068
|
+
}
|
|
15069
|
+
function firstBytes(fields, number) {
|
|
15070
|
+
const hits = fields.filter((field) => field.number === number);
|
|
15071
|
+
if (hits.length > 1 || hits.length === 1 && hits[0].wire !== 2) {
|
|
15072
|
+
throw protocolFailure2("invalid_required_field");
|
|
15073
|
+
}
|
|
15074
|
+
const value = hits[0]?.value;
|
|
15075
|
+
return value instanceof Buffer ? value : void 0;
|
|
15076
|
+
}
|
|
15077
|
+
function text(buffer) {
|
|
15078
|
+
try {
|
|
15079
|
+
return buffer === void 0 ? "" : new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
15080
|
+
} catch {
|
|
15081
|
+
throw protocolFailure2("invalid_utf8");
|
|
15082
|
+
}
|
|
15083
|
+
}
|
|
15084
|
+
function webhookUrlFor(backendUrl, serverId) {
|
|
15085
|
+
return new URL(`/automations/webhook/${encodeURIComponent(serverId)}`, normalizedBackend(backendUrl)).toString();
|
|
15086
|
+
}
|
|
15087
|
+
function normalizedBackend(backendUrl) {
|
|
15088
|
+
let parsed;
|
|
15089
|
+
try {
|
|
15090
|
+
parsed = new URL(backendUrl);
|
|
15091
|
+
} catch {
|
|
15092
|
+
throw new CliError("profile_invalid", "Server backend URL is invalid.");
|
|
15093
|
+
}
|
|
15094
|
+
const loopback = parsed.hostname === "localhost" || parsed.hostname === "[::1]" || /^127(?:\.[0-9]{1,3}){3}$/.test(parsed.hostname);
|
|
15095
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopback) || parsed.username || parsed.password || backendUrl.includes("?") || backendUrl.includes("#") || parsed.pathname !== "/") {
|
|
15096
|
+
throw new CliError("profile_invalid", "Server backend must be an HTTPS origin (HTTP only on loopback), without credentials, query, or fragment.");
|
|
15097
|
+
}
|
|
15098
|
+
return parsed.toString();
|
|
15099
|
+
}
|
|
15100
|
+
async function readBounded2(response, limit) {
|
|
15101
|
+
const declared = response.headers.get("content-length");
|
|
15102
|
+
if (declared !== null) {
|
|
15103
|
+
const bytes = Number(declared);
|
|
15104
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > limit) {
|
|
15105
|
+
throw protocolFailure2("response_oversized");
|
|
15106
|
+
}
|
|
15107
|
+
}
|
|
15108
|
+
if (!response.body) return Buffer.alloc(0);
|
|
15109
|
+
const reader = response.body.getReader();
|
|
15110
|
+
const chunks = [];
|
|
15111
|
+
let total = 0;
|
|
15112
|
+
try {
|
|
15113
|
+
for (; ; ) {
|
|
15114
|
+
const { done, value } = await reader.read();
|
|
15115
|
+
if (done) break;
|
|
15116
|
+
total += value.byteLength;
|
|
15117
|
+
if (total > limit) {
|
|
15118
|
+
await reader.cancel().catch(() => void 0);
|
|
15119
|
+
throw protocolFailure2("response_oversized");
|
|
15120
|
+
}
|
|
15121
|
+
chunks.push(Buffer.from(value));
|
|
15122
|
+
}
|
|
15123
|
+
} finally {
|
|
15124
|
+
reader.releaseLock();
|
|
15125
|
+
}
|
|
15126
|
+
return Buffer.concat(chunks);
|
|
15127
|
+
}
|
|
15128
|
+
async function call(deps, backendUrl, token, path, body, timeoutMs) {
|
|
15129
|
+
const controller2 = new AbortController();
|
|
15130
|
+
const abort2 = () => controller2.abort();
|
|
15131
|
+
if (deps.signal?.aborted) controller2.abort();
|
|
15132
|
+
else deps.signal?.addEventListener("abort", abort2, { once: true });
|
|
15133
|
+
const timer = setTimeout(() => controller2.abort(), timeoutMs);
|
|
15134
|
+
const cleanup = () => {
|
|
15135
|
+
clearTimeout(timer);
|
|
15136
|
+
deps.signal?.removeEventListener("abort", abort2);
|
|
15137
|
+
};
|
|
15138
|
+
let response;
|
|
15139
|
+
try {
|
|
15140
|
+
if (controller2.signal.aborted) throw new Error("aborted");
|
|
15141
|
+
response = await deps.fetch(new URL(path, normalizedBackend(backendUrl)).toString(), {
|
|
15142
|
+
method: "POST",
|
|
15143
|
+
headers: {
|
|
15144
|
+
accept: "application/proto",
|
|
15145
|
+
"content-type": "application/proto",
|
|
15146
|
+
authorization: `Bearer ${token}`
|
|
15147
|
+
},
|
|
15148
|
+
body: new Uint8Array(body),
|
|
15149
|
+
cache: "no-store",
|
|
15150
|
+
redirect: "manual",
|
|
15151
|
+
signal: controller2.signal
|
|
15152
|
+
});
|
|
15153
|
+
} catch {
|
|
15154
|
+
cleanup();
|
|
15155
|
+
throw new CliError(
|
|
15156
|
+
"gateway_unreachable",
|
|
15157
|
+
controller2.signal.aborted ? "The Server Routine request timed out." : "The Server Routine endpoint is unreachable.",
|
|
15158
|
+
{ failureCode: controller2.signal.aborted ? "request_timeout" : "network_failure", retryable: true }
|
|
15159
|
+
);
|
|
15160
|
+
}
|
|
15161
|
+
try {
|
|
15162
|
+
if (controller2.signal.aborted) {
|
|
15163
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15164
|
+
throw protocolFailure2("request_cancelled");
|
|
15165
|
+
}
|
|
15166
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
15167
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15168
|
+
throw protocolFailure2("redirect_refused");
|
|
15169
|
+
}
|
|
15170
|
+
if (response.status === 401 || response.status === 403) {
|
|
15171
|
+
const detail = await readBounded2(response, MAX_RESPONSE_BYTES2).then((buffer) => buffer.toString("utf8")).catch(() => "");
|
|
15172
|
+
throw authorizationFailure2(authorizationFailureCode(detail));
|
|
15173
|
+
}
|
|
15174
|
+
if (response.status === 404) {
|
|
15175
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15176
|
+
throw new CliError("target_not_found", "The Server does not know that Routine.", {
|
|
15177
|
+
failureCode: "routine_not_found",
|
|
15178
|
+
httpStatus: 404
|
|
15179
|
+
});
|
|
15180
|
+
}
|
|
15181
|
+
if (response.status === 408 || response.status === 429 || response.status >= 500) {
|
|
15182
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15183
|
+
throw new CliError("gateway_unreachable", "The Server Routine endpoint refused the request.", {
|
|
15184
|
+
failureCode: "provider_refused",
|
|
15185
|
+
httpStatus: response.status,
|
|
15186
|
+
retryable: true
|
|
15187
|
+
});
|
|
15188
|
+
}
|
|
15189
|
+
if (response.status !== 200) {
|
|
15190
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15191
|
+
throw new CliError(
|
|
15192
|
+
"capability_unavailable",
|
|
15193
|
+
"The Server Routine method is unavailable for this credential.",
|
|
15194
|
+
{ failureCode: "unexpected_status", httpStatus: response.status }
|
|
15195
|
+
);
|
|
15196
|
+
}
|
|
15197
|
+
if (response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/proto") {
|
|
15198
|
+
await response.body?.cancel().catch(() => void 0);
|
|
15199
|
+
throw protocolFailure2("response_content_type_invalid");
|
|
15200
|
+
}
|
|
15201
|
+
return await readBounded2(response, MAX_RESPONSE_BYTES2);
|
|
15202
|
+
} catch (error) {
|
|
15203
|
+
if (error instanceof CliError) throw error;
|
|
15204
|
+
throw new CliError(
|
|
15205
|
+
"gateway_unreachable",
|
|
15206
|
+
controller2.signal.aborted ? "The Server Routine request timed out." : "The Server Routine response could not be read.",
|
|
15207
|
+
{ failureCode: controller2.signal.aborted ? "request_timeout" : "response_unreadable", retryable: true }
|
|
15208
|
+
);
|
|
15209
|
+
} finally {
|
|
15210
|
+
cleanup();
|
|
15211
|
+
}
|
|
15212
|
+
}
|
|
15213
|
+
function authorizationFailureCode(detail) {
|
|
15214
|
+
if (/Only the automation owner/i.test(detail)) return "not_routine_owner";
|
|
15215
|
+
if (/missing required scope/i.test(detail)) return "scope_mismatch";
|
|
15216
|
+
if (/Invalid API key/i.test(detail)) return "invalid_api_key";
|
|
15217
|
+
if (/ERROR_NOT_LOGGED_IN|not logged in/i.test(detail)) return "not_signed_in";
|
|
15218
|
+
return "credential_refused";
|
|
15219
|
+
}
|
|
15220
|
+
async function listServerAgents(deps, options2) {
|
|
15221
|
+
const body = await call(deps, options2.backendUrl, options2.token, SERVER_ROUTINE_PATHS.listAgents, Buffer.alloc(0), options2.timeoutMs);
|
|
15222
|
+
const agents = [];
|
|
15223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15224
|
+
const fields = decodeFields(body);
|
|
15225
|
+
const truncated = fields.filter((field) => field.number === 3);
|
|
15226
|
+
if (truncated.length > 1 || truncated[0] && truncated[0].wire !== 0) throw protocolFailure2("agent_truncation_invalid");
|
|
15227
|
+
if (truncated[0] && truncated[0].value !== 0n) throw protocolFailure2("agent_roster_truncated");
|
|
15228
|
+
for (const field of fields) {
|
|
15229
|
+
if (field.number !== 1) continue;
|
|
15230
|
+
if (!(field.value instanceof Buffer) || field.wire !== 2) throw protocolFailure2("agent_envelope_invalid");
|
|
15231
|
+
const inner = decodeFields(field.value);
|
|
15232
|
+
const id = text(firstBytes(inner, 12));
|
|
15233
|
+
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)) {
|
|
15234
|
+
throw protocolFailure2("agent_id_invalid");
|
|
15235
|
+
}
|
|
15236
|
+
seen.add(id);
|
|
15237
|
+
const kinds = inner.filter((field2) => field2.number === 19);
|
|
15238
|
+
if (kinds.length > 1 || kinds[0] && kinds[0].wire !== 0) throw protocolFailure2("agent_kind_invalid");
|
|
15239
|
+
const kind = kinds[0]?.value ?? 0n;
|
|
15240
|
+
if (kind !== 0n && kind !== 1n && kind !== 2n) throw protocolFailure2("agent_kind_unknown");
|
|
15241
|
+
const harness = text(firstBytes(inner, 13));
|
|
15242
|
+
const name = text(firstBytes(inner, 3));
|
|
15243
|
+
if (name.length > 512 || /[\x00-\x1f\x7f]/.test(name)) throw protocolFailure2("agent_name_invalid");
|
|
15244
|
+
agents.push({
|
|
15245
|
+
id,
|
|
15246
|
+
name,
|
|
15247
|
+
harness: harness === "temporal" || harness === "box" ? harness : null,
|
|
15248
|
+
isGroup: kind === 2n
|
|
15249
|
+
});
|
|
15250
|
+
if (agents.length > MAX_AGENTS) throw protocolFailure2("too_many_agents");
|
|
15251
|
+
}
|
|
15252
|
+
return agents;
|
|
15253
|
+
}
|
|
15254
|
+
async function listServerRoutines(deps, options2) {
|
|
15255
|
+
const body = await call(
|
|
15256
|
+
deps,
|
|
15257
|
+
options2.backendUrl,
|
|
15258
|
+
options2.token,
|
|
15259
|
+
SERVER_ROUTINE_PATHS.listAutomations,
|
|
15260
|
+
lengthDelimited(1, Buffer.from(options2.agentId, "utf8")),
|
|
15261
|
+
options2.timeoutMs
|
|
15262
|
+
);
|
|
15263
|
+
const records = [];
|
|
15264
|
+
const serverIds = [];
|
|
15265
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15266
|
+
for (const field of decodeFields(body)) {
|
|
15267
|
+
if (field.number !== 1) continue;
|
|
15268
|
+
if (!(field.value instanceof Buffer) || field.wire !== 2) throw protocolFailure2("routine_envelope_invalid");
|
|
15269
|
+
const inner = decodeFields(field.value);
|
|
15270
|
+
const serverId = text(firstBytes(inner, 1));
|
|
15271
|
+
const recordBytes = firstBytes(inner, 2);
|
|
15272
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,255}$/.test(serverId) || seen.has(serverId)) {
|
|
15273
|
+
throw protocolFailure2("server_id_invalid");
|
|
15274
|
+
}
|
|
15275
|
+
if (recordBytes === void 0) throw protocolFailure2("record_missing");
|
|
15276
|
+
if (recordBytes.length > MAX_RECORD_BYTES) throw protocolFailure2("record_oversized");
|
|
15277
|
+
const json = text(recordBytes);
|
|
15278
|
+
let record3;
|
|
15279
|
+
try {
|
|
15280
|
+
record3 = JSON.parse(json);
|
|
15281
|
+
} catch {
|
|
15282
|
+
throw protocolFailure2("record_not_json");
|
|
15283
|
+
}
|
|
15284
|
+
if (!isRecord(record3)) throw protocolFailure2("record_not_object");
|
|
15285
|
+
seen.add(serverId);
|
|
15286
|
+
records.push(record3);
|
|
15287
|
+
serverIds.push(serverId);
|
|
15288
|
+
if (records.length > MAX_ROUTINES) throw protocolFailure2("too_many_routines");
|
|
15289
|
+
}
|
|
15290
|
+
try {
|
|
15291
|
+
return projectNativeRoutines(options2.agentId, records).catalog.routines.map((routine, index) => ({
|
|
15292
|
+
...routine,
|
|
15293
|
+
mutable: false,
|
|
15294
|
+
serverId: serverIds[index],
|
|
15295
|
+
webhookUrl: routine.trigger.type === "webhook" ? webhookUrlFor(options2.backendUrl, serverIds[index]) : null
|
|
15296
|
+
}));
|
|
15297
|
+
} catch (error) {
|
|
15298
|
+
if (error instanceof CliError) throw error;
|
|
15299
|
+
throw protocolFailure2("routine_record_invalid");
|
|
15300
|
+
}
|
|
15301
|
+
}
|
|
15302
|
+
function assertWebhookKey(value) {
|
|
15303
|
+
if (value.length < 1 || Buffer.byteLength(value, "utf8") > MAX_KEY_BYTES || /[\x00-\x20\x7f]/.test(value)) {
|
|
15304
|
+
throw protocolFailure2("key_invalid");
|
|
15305
|
+
}
|
|
15306
|
+
return value;
|
|
15307
|
+
}
|
|
15308
|
+
async function mintWebhookKey(deps, options2) {
|
|
15309
|
+
const body = await call(
|
|
15310
|
+
deps,
|
|
15311
|
+
options2.backendUrl,
|
|
15312
|
+
options2.token,
|
|
15313
|
+
SERVER_ROUTINE_PATHS.mintWebhookKey,
|
|
15314
|
+
lengthDelimited(1, Buffer.from(options2.serverId, "utf8")),
|
|
15315
|
+
options2.timeoutMs
|
|
15316
|
+
);
|
|
15317
|
+
const key = text(firstBytes(decodeFields(body), 1));
|
|
15318
|
+
if (key.length === 0) throw protocolFailure2("key_missing");
|
|
15319
|
+
return assertWebhookKey(key);
|
|
15320
|
+
}
|
|
15321
|
+
async function probeWebhookKey(deps, options2) {
|
|
15322
|
+
if (options2.serverId === NIL_AUTOMATION_ID) throw protocolFailure2("unsafe_probe_target");
|
|
15323
|
+
const controller2 = new AbortController();
|
|
15324
|
+
const abort2 = () => controller2.abort();
|
|
15325
|
+
if (deps.signal?.aborted) controller2.abort();
|
|
15326
|
+
else deps.signal?.addEventListener("abort", abort2, { once: true });
|
|
15327
|
+
const timer = setTimeout(abort2, options2.timeoutMs);
|
|
15328
|
+
try {
|
|
15329
|
+
if (controller2.signal.aborted) throw protocolFailure2("request_cancelled");
|
|
15330
|
+
const response = await deps.fetch(webhookUrlFor(options2.backendUrl, NIL_AUTOMATION_ID), {
|
|
15331
|
+
method: "POST",
|
|
15332
|
+
headers: { authorization: `Bearer ${options2.key}`, "content-type": "application/json" },
|
|
15333
|
+
body: "{}",
|
|
15334
|
+
cache: "no-store",
|
|
15335
|
+
redirect: "manual",
|
|
15336
|
+
signal: controller2.signal
|
|
15337
|
+
});
|
|
15338
|
+
const detail = JSON.parse(text(await readBounded2(response, 64 * 1024)));
|
|
15339
|
+
if (controller2.signal.aborted) throw protocolFailure2("request_cancelled");
|
|
15340
|
+
if (!response.redirected && response.status === 401 && isRecord(detail) && detail.code === "error") {
|
|
15341
|
+
if (detail.message === `User API key missing required scope: automation:${NIL_AUTOMATION_ID}`) return true;
|
|
15342
|
+
if (detail.message === "Invalid API key") return false;
|
|
15343
|
+
}
|
|
15344
|
+
throw protocolFailure2("verification_unknown");
|
|
15345
|
+
} catch (error) {
|
|
15346
|
+
if (error instanceof CliError) throw error;
|
|
15347
|
+
throw protocolFailure2(controller2.signal.aborted ? "request_cancelled" : "verification_unknown");
|
|
15348
|
+
} finally {
|
|
15349
|
+
clearTimeout(timer);
|
|
15350
|
+
deps.signal?.removeEventListener("abort", abort2);
|
|
15351
|
+
}
|
|
15352
|
+
}
|
|
15353
|
+
async function cachedWebhookKey(deps, options2) {
|
|
15354
|
+
if (options2.routine.createdAtMs === null) throw protocolFailure2("routine_incarnation_missing");
|
|
15355
|
+
const scope = createHash8("sha256").update(JSON.stringify([
|
|
15356
|
+
normalizedBackend(options2.backendUrl),
|
|
15357
|
+
createHash8("sha256").update(options2.token).digest("hex"),
|
|
15358
|
+
options2.agentId,
|
|
15359
|
+
options2.routine.serverId,
|
|
15360
|
+
options2.routine.createdAtMs
|
|
15361
|
+
])).digest("hex");
|
|
15362
|
+
const directory = join11(options2.configDir, "server-webhook-keys");
|
|
15363
|
+
const path = join11(directory, `${scope}.json`);
|
|
15364
|
+
const lockPath = join11(directory, `${scope}.lock`);
|
|
15365
|
+
let lock;
|
|
15366
|
+
let temporary;
|
|
15367
|
+
let preserveLock = false;
|
|
15368
|
+
try {
|
|
15369
|
+
await mkdir9(directory, { recursive: true, mode: 448 });
|
|
15370
|
+
const dir = await lstat5(directory);
|
|
15371
|
+
if (!dir.isDirectory() || (dir.mode & 63) !== 0 || process.getuid && dir.uid !== process.getuid()) throw protocolFailure2("key_store_permissions");
|
|
15372
|
+
lock = await open6(lockPath, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY | constants4.O_NOFOLLOW, 384);
|
|
15373
|
+
let cached;
|
|
15374
|
+
try {
|
|
15375
|
+
cached = await open6(path, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
15376
|
+
} catch (error) {
|
|
15377
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
15378
|
+
}
|
|
15379
|
+
if (cached) {
|
|
15380
|
+
try {
|
|
15381
|
+
const stat6 = await cached.stat();
|
|
15382
|
+
if (!stat6.isFile() || (stat6.mode & 63) !== 0 || stat6.nlink !== 1 || stat6.size > 8192 || process.getuid && stat6.uid !== process.getuid()) throw protocolFailure2("key_store_permissions");
|
|
15383
|
+
const bytes = Buffer.alloc(8193);
|
|
15384
|
+
const { bytesRead } = await cached.read(bytes, 0, bytes.length, 0);
|
|
15385
|
+
if (bytesRead > 8192) throw protocolFailure2("key_store_corrupt");
|
|
15386
|
+
const record3 = JSON.parse(text(bytes.subarray(0, bytesRead)));
|
|
15387
|
+
if (!isRecord(record3) || record3.version !== 1 || record3.scope !== scope || typeof record3.key !== "string" || Object.keys(record3).length !== 3) throw protocolFailure2("key_store_corrupt");
|
|
15388
|
+
return assertWebhookKey(record3.key);
|
|
15389
|
+
} finally {
|
|
15390
|
+
await cached.close();
|
|
15391
|
+
}
|
|
15392
|
+
}
|
|
15393
|
+
if (deps.signal?.aborted) throw protocolFailure2("request_cancelled");
|
|
15394
|
+
temporary = join11(directory, `${scope}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
15395
|
+
const output = await (deps.openWebhookKeyStore ?? open6)(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY | constants4.O_NOFOLLOW, 384);
|
|
15396
|
+
try {
|
|
15397
|
+
if (deps.signal?.aborted) throw protocolFailure2("request_cancelled");
|
|
15398
|
+
preserveLock = true;
|
|
15399
|
+
const key = await mintWebhookKey(deps, { ...options2, serverId: options2.routine.serverId });
|
|
15400
|
+
await output.writeFile(JSON.stringify({ version: 1, scope, key }), "utf8");
|
|
15401
|
+
await output.sync();
|
|
15402
|
+
await output.close();
|
|
15403
|
+
await rename6(temporary, path);
|
|
15404
|
+
temporary = void 0;
|
|
15405
|
+
preserveLock = false;
|
|
15406
|
+
return key;
|
|
15407
|
+
} finally {
|
|
15408
|
+
await output.close().catch(() => void 0);
|
|
15409
|
+
}
|
|
15410
|
+
} catch (error) {
|
|
15411
|
+
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)) {
|
|
15412
|
+
preserveLock = false;
|
|
15413
|
+
}
|
|
15414
|
+
if (preserveLock) {
|
|
15415
|
+
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.", {
|
|
15416
|
+
failureCode: "mint_outcome_unknown",
|
|
15417
|
+
retryable: false
|
|
15418
|
+
});
|
|
15419
|
+
}
|
|
15420
|
+
if (error instanceof CliError) throw error;
|
|
15421
|
+
throw protocolFailure2("key_store_unavailable");
|
|
15422
|
+
} finally {
|
|
15423
|
+
if (temporary) await unlink3(temporary).catch(() => void 0);
|
|
15424
|
+
if (lock) {
|
|
15425
|
+
await lock.close();
|
|
15426
|
+
if (!preserveLock) await unlink3(lockPath).catch(() => void 0);
|
|
15427
|
+
}
|
|
15428
|
+
}
|
|
15429
|
+
}
|
|
15430
|
+
|
|
14916
15431
|
// src/commands/routines.ts
|
|
14917
15432
|
function routineCliError(error) {
|
|
14918
15433
|
if (error instanceof CliError) return error;
|
|
@@ -14984,7 +15499,8 @@ async function resolveRoutineAuthority(client2, agent, timeoutMs) {
|
|
|
14984
15499
|
if (error instanceof CliError && ["gateway_not_found", "gateway_bad_request", "capability_unavailable"].includes(error.code)) {
|
|
14985
15500
|
throw new CliError(
|
|
14986
15501
|
"capability_unavailable",
|
|
14987
|
-
"This Temporal Bot requires the Grokbox Temporal Routine bridge before Routine state can be read safely."
|
|
15502
|
+
"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.",
|
|
15503
|
+
{ failureCode: "routine_bridge_missing" }
|
|
14988
15504
|
);
|
|
14989
15505
|
}
|
|
14990
15506
|
throw error;
|
|
@@ -15034,16 +15550,21 @@ function nativePrompts(value, catalog) {
|
|
|
15034
15550
|
}
|
|
15035
15551
|
return prompts;
|
|
15036
15552
|
}
|
|
15037
|
-
function findRoutine(snapshot,
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15553
|
+
function findRoutine(snapshot, target, allowName = false) {
|
|
15554
|
+
const exact = snapshot.catalog.routines.find((routine) => routine.id === target);
|
|
15555
|
+
if (exact) return exact;
|
|
15556
|
+
if (!allowName) {
|
|
15557
|
+
try {
|
|
15558
|
+
routineId(target);
|
|
15559
|
+
} catch (error) {
|
|
15560
|
+
throw routineCliError(error);
|
|
15561
|
+
}
|
|
15562
|
+
throw routineCliError(new RoutineError("not_found_in_window"));
|
|
15043
15563
|
}
|
|
15044
|
-
const
|
|
15045
|
-
if (
|
|
15046
|
-
|
|
15564
|
+
const named = snapshot.catalog.routines.filter((routine) => routine.name.toLocaleLowerCase() === target.toLocaleLowerCase());
|
|
15565
|
+
if (named.length > 1) throw new CliError("target_ambiguous", "Routine name matched more than one Routine.");
|
|
15566
|
+
if (!named[0]) throw routineCliError(new RoutineError("not_found_in_window"));
|
|
15567
|
+
return named[0];
|
|
15047
15568
|
}
|
|
15048
15569
|
function mutableRoutine(snapshot, id) {
|
|
15049
15570
|
const row = findRoutine(snapshot, id);
|
|
@@ -15104,7 +15625,7 @@ async function listOrShow(deps, client2, action, agentTarget, routineTarget, raw
|
|
|
15104
15625
|
writeSuccess(deps.stdout, { ...snapshot.catalog, authority: snapshot.authority }, snapshot.discovery);
|
|
15105
15626
|
return;
|
|
15106
15627
|
}
|
|
15107
|
-
const routine = findRoutine(snapshot, routineTarget ?? "");
|
|
15628
|
+
const routine = findRoutine(snapshot, routineTarget ?? "", true);
|
|
15108
15629
|
writeSuccess(
|
|
15109
15630
|
deps.stdout,
|
|
15110
15631
|
{ schemaVersion: 1, agentId: agent.id, authority: snapshot.authority, routine, coverage: snapshot.catalog.coverage },
|
|
@@ -15297,10 +15818,13 @@ async function setRoutineEnabled(deps, client2, agentTarget, routineTarget, acti
|
|
|
15297
15818
|
}
|
|
15298
15819
|
async function showWebhookCredential(deps, client2, agentTarget, routineTarget, raw) {
|
|
15299
15820
|
requireConfirmed(raw);
|
|
15821
|
+
if (raw.verify === true) {
|
|
15822
|
+
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.");
|
|
15823
|
+
}
|
|
15300
15824
|
const io = ioFromOpts(raw);
|
|
15301
15825
|
const agent = await resolveAgent(client2, agentTarget, io.timeoutMs);
|
|
15302
15826
|
const snapshot = await readSnapshot(client2, agent, io.timeoutMs);
|
|
15303
|
-
const routine = findRoutine(snapshot, routineTarget);
|
|
15827
|
+
const routine = findRoutine(snapshot, routineTarget, true);
|
|
15304
15828
|
if (routine.trigger.type !== "webhook") {
|
|
15305
15829
|
throw new CliError("capability_unavailable", "Routine is not webhook-triggered.");
|
|
15306
15830
|
}
|
|
@@ -15380,7 +15904,58 @@ async function deleteRoutine(deps, client2, agentTarget, routineTarget, raw) {
|
|
|
15380
15904
|
})
|
|
15381
15905
|
}, after.discovery);
|
|
15382
15906
|
}
|
|
15907
|
+
async function cloudRoutines(deps, action, agentTarget, routineTarget, raw) {
|
|
15908
|
+
if (action === "webhook-credential") requireConfirmed(raw);
|
|
15909
|
+
const io = ioFromOpts(raw);
|
|
15910
|
+
const context = {
|
|
15911
|
+
backendUrl: normalizedBackend(deps.serverBackendUrl ?? DEFAULT_SERVER_BACKEND_URL),
|
|
15912
|
+
token: await resolveSecretRef(deps, deps.serverAccessTokenRef),
|
|
15913
|
+
timeoutMs: io.timeoutMs
|
|
15914
|
+
};
|
|
15915
|
+
const agents = await listServerAgents(deps, context);
|
|
15916
|
+
const agent = findRosterRow(agents, agentTarget, ["agent"]);
|
|
15917
|
+
const agentId = asString(agent.id);
|
|
15918
|
+
const routines = await listServerRoutines(deps, { ...context, agentId });
|
|
15919
|
+
const coverage = { kind: "native_returned_window", limit: 100, atLimit: routines.length === 100, complete: false };
|
|
15920
|
+
const catalog = { schemaVersion: 1, agentId, routines, coverage };
|
|
15921
|
+
if (action === "list") {
|
|
15922
|
+
if (io.table) deps.stdout.write(listTable(catalog));
|
|
15923
|
+
else writeSuccess(deps.stdout, { ...catalog, authority: "server-temporal" });
|
|
15924
|
+
return;
|
|
15925
|
+
}
|
|
15926
|
+
let selected = routines.filter((routine2) => routine2.id === routineTarget || routine2.serverId === routineTarget);
|
|
15927
|
+
if (selected.length === 0) {
|
|
15928
|
+
selected = routines.filter((routine2) => routine2.name.toLocaleLowerCase() === (routineTarget ?? "").toLocaleLowerCase());
|
|
15929
|
+
}
|
|
15930
|
+
if (selected.length > 1) throw new CliError("target_ambiguous", "Routine target matched more than one Routine.");
|
|
15931
|
+
const routine = selected[0];
|
|
15932
|
+
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.");
|
|
15933
|
+
if (action === "show") {
|
|
15934
|
+
writeSuccess(deps.stdout, { schemaVersion: 1, agentId, authority: "server-temporal", routine, coverage });
|
|
15935
|
+
return;
|
|
15936
|
+
}
|
|
15937
|
+
if (routine.trigger.type !== "webhook") throw new CliError("capability_unavailable", "Routine is not webhook-triggered.");
|
|
15938
|
+
const key = await cachedWebhookKey(deps, { ...context, configDir: deps.configDir, agentId, routine });
|
|
15939
|
+
const keyLive = raw.verify === true ? await probeWebhookKey(deps, { ...context, key, serverId: routine.serverId }) : void 0;
|
|
15940
|
+
writeSuccess(deps.stdout, {
|
|
15941
|
+
schemaVersion: 1,
|
|
15942
|
+
agentId,
|
|
15943
|
+
routineId: routine.id,
|
|
15944
|
+
serverId: routine.serverId,
|
|
15945
|
+
authority: "server-temporal",
|
|
15946
|
+
url: routine.webhookUrl,
|
|
15947
|
+
keyPresent: true,
|
|
15948
|
+
keyIncluded: raw.revealKey === true,
|
|
15949
|
+
promptIncluded: false,
|
|
15950
|
+
...keyLive === void 0 ? {} : { keyLive },
|
|
15951
|
+
...raw.revealKey === true ? { key } : {}
|
|
15952
|
+
});
|
|
15953
|
+
}
|
|
15383
15954
|
async function runRoutines(deps, action, agentTarget, routineTarget, raw) {
|
|
15955
|
+
if (deps.serverAccessTokenRef !== void 0 && (action === "list" || action === "show" || action === "webhook-credential")) {
|
|
15956
|
+
await cloudRoutines(deps, action, agentTarget, routineTarget, raw);
|
|
15957
|
+
return;
|
|
15958
|
+
}
|
|
15384
15959
|
const client2 = new GatewayClient(deps);
|
|
15385
15960
|
if (action === "list" || action === "show") {
|
|
15386
15961
|
await listOrShow(deps, client2, action, agentTarget, routineTarget, raw);
|
|
@@ -15450,11 +16025,11 @@ async function runSend(deps, target, raw) {
|
|
|
15450
16025
|
}
|
|
15451
16026
|
|
|
15452
16027
|
// src/skills.ts
|
|
15453
|
-
import { join as
|
|
16028
|
+
import { join as join12 } from "node:path";
|
|
15454
16029
|
var CORE_SKILL_NAME = "core";
|
|
15455
16030
|
var CORE_SKILL_SUMMARY = "Core grokbox usage guide.";
|
|
15456
16031
|
async function loadCoreMarkdown(deps, full) {
|
|
15457
|
-
const overview = await deps.readFile(
|
|
16032
|
+
const overview = await deps.readFile(join12(deps.skillsDir, "core.md"));
|
|
15458
16033
|
if (!full) return overview.trimEnd() + "\n";
|
|
15459
16034
|
return `${overview.trimEnd()}
|
|
15460
16035
|
|
|
@@ -15517,9 +16092,9 @@ var FAMILY_DESCRIPTIONS = {
|
|
|
15517
16092
|
is: "Read state projections"
|
|
15518
16093
|
};
|
|
15519
16094
|
function publicCommanderMessage(error) {
|
|
15520
|
-
const
|
|
15521
|
-
if (/bearer|authorization|token/i.test(
|
|
15522
|
-
return
|
|
16095
|
+
const text2 = error.message.replace(/^error: /i, "").trim();
|
|
16096
|
+
if (/bearer|authorization|token/i.test(text2)) return "Invalid usage.";
|
|
16097
|
+
return text2.length > 0 ? text2 : "Invalid usage.";
|
|
15523
16098
|
}
|
|
15524
16099
|
function unexpectedMessage(error) {
|
|
15525
16100
|
if (error instanceof Error) {
|
|
@@ -15658,6 +16233,8 @@ function addLeaf(deps, program2, parents, leaf, action) {
|
|
|
15658
16233
|
sandboxKeepaliveIntervalMs: profile.sandbox?.keepalive_interval_ms,
|
|
15659
16234
|
quotaSource: profile.quota?.source,
|
|
15660
16235
|
quotaAccessTokenRef: profile.quota?.access_token_ref,
|
|
16236
|
+
serverAccessTokenRef: profile.server?.access_token_ref,
|
|
16237
|
+
serverBackendUrl: profile.server?.backend_url,
|
|
15661
16238
|
gatewayServerUrl: profile.gateway_url,
|
|
15662
16239
|
gatewayTokenRef: profile.gateway_token_ref,
|
|
15663
16240
|
gatewayHeadersRef: profile.gateway_headers_ref,
|
package/package.json
CHANGED
package/skills/core.md
CHANGED
|
@@ -41,6 +41,10 @@ grokbox box wake
|
|
|
41
41
|
grokbox box keepalive status
|
|
42
42
|
grokbox agents list --table
|
|
43
43
|
grokbox agents routines list <agent> --table
|
|
44
|
+
grokbox profile update <name> --server-access-token-ref file:/absolute/path/to/cloud-token
|
|
45
|
+
grokbox profile use <name>
|
|
46
|
+
grokbox agents routines show <agent> <routine-id>
|
|
47
|
+
grokbox agents routines webhook credential <agent> <routine-id> --confirm --verify
|
|
44
48
|
grokbox groups list --table
|
|
45
49
|
grokbox send <target> --text "hello"
|
|
46
50
|
grokbox history tail <target> --limit 50
|
|
@@ -72,7 +76,8 @@ Targets accept an exact ID first, then an unambiguous case-insensitive name/titl
|
|
|
72
76
|
`groups list/show/create/update/delete` only own product groups. Membership is only
|
|
73
77
|
`groups members list/add/remove/set`; every member must resolve to a non-group agent and a group
|
|
74
78
|
must retain 1-6 unique members.
|
|
75
|
-
3. **Manage
|
|
79
|
+
3. **Manage Routines through one interface**: `agents routines list/show` and `agents routines webhook credential <agent> <routine-id>` use the selected box Profile's cloud SSOT whenever `server.access_token_ref` is configured. Configure with `profile add/update --server-access-token-ref` and optional `--server-backend-url`, using a protected secret reference. Cloud reads preserve envelopes and bounded metadata with `serverId`, webhook URL, and `mutable:false`, never keys or prompts. The Server-returned automation ID is authoritative. There is no separate command root, source flag, desktop discovery, or roster union. First credential access needs `--confirm` to mint; repeated access reuses a local cache scoped to backend, credential digest, agent, Server ID, and creation time. Minting is never automatically retried. `--reveal-key` explicitly reveals the key, never a cloud prompt. `--verify` probes only a fixed, distinct nonexistent ID, requires strict 401 `missing required scope` evidence, supports abort, and never POSTs to the real Routine. Cloud failures are reported directly: check the selected Profile reference/backend and authorization/protocol error, not another route.
|
|
80
|
+
Without cloud configuration, Gateway/Host behavior remains, including fail-closed missing Temporal bridge checks. Mutations retain the Host contract: Box-local create is disabled, later mutations require fresh `--expect-revision` and `--confirm`, and unsupported Temporal create/update refuse. Cloud adds no CRUD. Unknown write outcomes require reconciliation, not automatic replay. Host credential disclosure retains its existing contract. Cloud authority grants no Gateway, daemon, Sandbox, or quota permissions.
|
|
76
81
|
4. **Search/read transcript**: `history search <query>`, `history tail <target>`, and
|
|
77
82
|
`history thread <target> --root <entry-id>` own transcript access. Object `show` commands do
|
|
78
83
|
not add transcript side projections.
|