grokbot-control 0.0.0-stage → 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.
Files changed (122) hide show
  1. package/README.md +160 -2
  2. package/dist/auth/lock.d.ts +14 -0
  3. package/dist/auth/lock.js +71 -0
  4. package/dist/auth/provider.d.ts +89 -0
  5. package/dist/auth/provider.js +884 -0
  6. package/dist/auth/store.d.ts +25 -0
  7. package/dist/auth/store.js +106 -0
  8. package/dist/auth/types.d.ts +138 -0
  9. package/dist/auth/types.js +1 -0
  10. package/dist/cli/config.d.ts +26 -0
  11. package/dist/cli/config.js +98 -0
  12. package/dist/cli/help.d.ts +1 -0
  13. package/dist/cli/help.js +83 -0
  14. package/dist/cli/io.d.ts +6 -0
  15. package/dist/cli/io.js +61 -0
  16. package/dist/cli/main.d.ts +2 -0
  17. package/dist/cli/main.js +275 -0
  18. package/dist/cli/sync.d.ts +4 -0
  19. package/dist/cli/sync.js +105 -0
  20. package/dist/connect/client.d.ts +32 -0
  21. package/dist/connect/client.js +257 -0
  22. package/dist/dto/result.d.ts +33 -0
  23. package/dist/dto/result.js +29 -0
  24. package/dist/dto/types.d.ts +63 -0
  25. package/dist/dto/types.js +2 -0
  26. package/dist/errors.d.ts +31 -0
  27. package/dist/errors.js +64 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +11 -0
  30. package/dist/mcp/main.d.ts +2 -0
  31. package/dist/mcp/main.js +82 -0
  32. package/dist/mcp/tools.d.ts +24 -0
  33. package/dist/mcp/tools.js +408 -0
  34. package/dist/mcp/zod-json.d.ts +2 -0
  35. package/dist/mcp/zod-json.js +4 -0
  36. package/dist/ops/auth.d.ts +48 -0
  37. package/dist/ops/auth.js +89 -0
  38. package/dist/ops/bot.d.ts +52 -0
  39. package/dist/ops/bot.js +211 -0
  40. package/dist/ops/limits.d.ts +10 -0
  41. package/dist/ops/limits.js +31 -0
  42. package/dist/ops/map.d.ts +7 -0
  43. package/dist/ops/map.js +57 -0
  44. package/dist/ops/message.d.ts +33 -0
  45. package/dist/ops/message.js +118 -0
  46. package/dist/ops/plane.d.ts +26 -0
  47. package/dist/ops/plane.js +33 -0
  48. package/dist/ops/progress.d.ts +34 -0
  49. package/dist/ops/progress.js +92 -0
  50. package/dist/ops/session.d.ts +13 -0
  51. package/dist/ops/session.js +34 -0
  52. package/dist/ops/sidebar.d.ts +70 -0
  53. package/dist/ops/sidebar.js +205 -0
  54. package/dist/sync/attachments.d.ts +76 -0
  55. package/dist/sync/attachments.js +274 -0
  56. package/dist/sync/core.d.ts +92 -0
  57. package/dist/sync/core.js +1027 -0
  58. package/dist/sync/decode.d.ts +83 -0
  59. package/dist/sync/decode.js +229 -0
  60. package/dist/sync/download-policy.d.ts +3 -0
  61. package/dist/sync/download-policy.js +103 -0
  62. package/dist/sync/fs.d.ts +24 -0
  63. package/dist/sync/fs.js +252 -0
  64. package/dist/sync/hydrate.d.ts +43 -0
  65. package/dist/sync/hydrate.js +472 -0
  66. package/dist/sync/render.d.ts +5 -0
  67. package/dist/sync/render.js +100 -0
  68. package/dist/testing/mock-persist.d.ts +3 -0
  69. package/dist/testing/mock-persist.js +45 -0
  70. package/dist/testing/mock-transport.d.ts +18 -0
  71. package/dist/testing/mock-transport.js +202 -0
  72. package/docs/acceptance.md +19 -0
  73. package/docs/architecture.md +128 -0
  74. package/docs/auth-lock.md +87 -0
  75. package/docs/implementation-plan.md +14 -0
  76. package/docs/implementation-status.md +92 -0
  77. package/docs/proto-drift.md +9 -0
  78. package/docs/protocol/README.md +34 -0
  79. package/docs/protocol/attachment-pagination-0.79.3.md +32 -0
  80. package/docs/protocol/auth-flow.md +51 -0
  81. package/docs/protocol/baseline-manifest.md +72 -0
  82. package/docs/protocol/baselines/full-schema-diff-0.78.4-to-0.79.2.txt +239 -0
  83. package/docs/protocol/baselines/full-schema-diff-0.78.4-to-0.79.3.txt +240 -0
  84. package/docs/protocol/baselines/full-schema-diff-0.79.2-to-0.79.3.txt +23 -0
  85. package/docs/protocol/baselines/schema-0.79.3/README.md +7 -0
  86. package/docs/protocol/baselines/schema-0.79.3/agent.v1.proto +5184 -0
  87. package/docs/protocol/baselines/schema-0.79.3/aiserver.v1.proto +18182 -0
  88. package/docs/protocol/baselines/schema-0.79.3/aiserver.v1_2.proto +54 -0
  89. package/docs/protocol/baselines/schema-0.79.3/anyrun.v1.proto +37 -0
  90. package/docs/protocol/baselines/schema-0.79.3/internapi.v1.proto +35 -0
  91. package/docs/protocol/baselines/schema-0.79.3/snapshot.json.gz +0 -0
  92. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AgentStoreService.proto +34 -0
  93. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AiService.proto +17 -0
  94. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AnalyticsService.proto +17 -0
  95. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.BackgroundComposerService.proto +19 -0
  96. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.DashboardService.proto +101 -0
  97. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.GrokBotService.proto +473 -0
  98. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.SandBoxService.proto +64 -0
  99. package/docs/protocol/baselines/used-surface-0.78.4.json +3694 -0
  100. package/docs/protocol/baselines/used-surface-0.79.2.json +3835 -0
  101. package/docs/protocol/baselines/used-surface-0.79.3.json +3835 -0
  102. package/docs/protocol/baselines/used-surface-diff-0.78.4-to-0.79.2.md +34 -0
  103. package/docs/protocol/baselines/used-surface-diff-0.78.4-to-0.79.3.md +33 -0
  104. package/docs/protocol/compat-matrix.md +20 -0
  105. package/docs/protocol/static-restore.md +64 -0
  106. package/docs/protocol/surface-map.md +39 -0
  107. package/docs/protocol/transcript-body-0.79.3.md +98 -0
  108. package/docs/protocol/update-runbook.md +30 -0
  109. package/docs/protocol-map.md +106 -0
  110. package/docs/references.md +55 -0
  111. package/docs/requirements.md +26 -0
  112. package/docs/review.md +36 -0
  113. package/docs/sync-format.md +98 -0
  114. package/package.json +60 -4
  115. package/vendor/proto-gen/NOTICE.md +23 -0
  116. package/vendor/proto-gen/agent.v1_pb.js +9503 -0
  117. package/vendor/proto-gen/aiserver.v1_2_pb.js +105 -0
  118. package/vendor/proto-gen/aiserver.v1_pb.js +33163 -0
  119. package/vendor/proto-gen/anyrun.v1_pb.js +64 -0
  120. package/vendor/proto-gen/internapi.v1_pb.js +60 -0
  121. package/vendor/proto-gen/package.json +10 -0
  122. package/vendor/proto-gen/svc_aiserver.v1.GrokBotService_connect.js +4193 -0
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+ import { syncCommand } from "./sync.js";
3
+ import { HELP } from "./help.js";
4
+ import { buildPlane, flag, hasFlag, parseGlobals, persistMockState, } from "./config.js";
5
+ import { catchToExit, emitResult, usageExit } from "./io.js";
6
+ import { failResult } from "../dto/result.js";
7
+ import { parseCliLimit } from "../ops/limits.js";
8
+ async function main() {
9
+ const argv = process.argv.slice(2);
10
+ if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
11
+ process.stdout.write(HELP);
12
+ return 0;
13
+ }
14
+ const { globals, rest } = parseGlobals(argv);
15
+ const asJson = globals.json || !process.stdout.isTTY;
16
+ const [noun, verb] = rest;
17
+ if (!noun || !verb)
18
+ usageExit("missing <noun> <verb>", "gbc --help");
19
+ try {
20
+ if (noun === "sync")
21
+ return emitResult(await syncCommand(globals, rest), asJson);
22
+ const plane = buildPlane(globals);
23
+ const write = {
24
+ dryRun: globals.dryRun,
25
+ allowWrite: globals.allowWrites,
26
+ };
27
+ let code = 0;
28
+ if (noun === "auth") {
29
+ const ops = plane.authOps;
30
+ if (verb === "whoami" || verb === "status") {
31
+ code = emitResult(await ops.whoami(), asJson);
32
+ }
33
+ else if (verb === "login-begin") {
34
+ code = emitResult(await ops.begin(), asJson);
35
+ }
36
+ else if (verb === "poll") {
37
+ code = emitResult(await ops.poll(), asJson);
38
+ }
39
+ else if (verb === "complete" || verb === "login") {
40
+ const timeout = flag(rest, "--timeout");
41
+ if (timeout !== undefined && !/^\d+$/.test(timeout))
42
+ usageExit("--timeout must be an integer number of seconds", "--timeout=300");
43
+ if (verb === "login") {
44
+ const begin = await ops.begin();
45
+ if (!begin.ok) {
46
+ code = emitResult(begin, asJson);
47
+ persistMockState();
48
+ return code;
49
+ }
50
+ // Human-facing URL goes to stderr; stdout carries only the final result.
51
+ process.stderr.write(JSON.stringify({
52
+ event: "open_browser",
53
+ authorization_url: begin.data
54
+ .authorization_url,
55
+ }) + "\n");
56
+ }
57
+ const ac = new AbortController();
58
+ const onSig = () => ac.abort();
59
+ process.once("SIGINT", onSig);
60
+ process.once("SIGTERM", onSig);
61
+ try {
62
+ code = emitResult(await ops.complete(timeout === undefined ? undefined : Number(timeout), ac.signal), asJson);
63
+ }
64
+ finally {
65
+ process.off("SIGINT", onSig);
66
+ process.off("SIGTERM", onSig);
67
+ }
68
+ }
69
+ else if (verb === "cancel") {
70
+ code = emitResult(await ops.cancel(), asJson);
71
+ }
72
+ else if (verb === "refresh") {
73
+ code = emitResult(await ops.refresh({ force: hasFlag(rest, "--force") }), asJson);
74
+ }
75
+ else if (verb === "logout") {
76
+ code = emitResult(await ops.logout(), asJson);
77
+ }
78
+ else {
79
+ usageExit(`unknown command: auth ${verb}`, "gbc auth whoami|login|login-begin|complete|poll|cancel|refresh[--force]|logout");
80
+ }
81
+ }
82
+ else if (noun === "bot" && verb === "list") {
83
+ code = emitResult(await plane.bot.list({ limit: parseCliLimit(flag(rest, "--limit")) }), asJson);
84
+ }
85
+ else if (noun === "bot" && verb === "get") {
86
+ const id = flag(rest, "--agent-id");
87
+ if (!id)
88
+ usageExit("bot get requires --agent-id");
89
+ code = emitResult(await plane.bot.get(id), asJson);
90
+ }
91
+ else if (noun === "bot" && verb === "create") {
92
+ const name = flag(rest, "--name");
93
+ if (!name)
94
+ usageExit("bot create requires --name");
95
+ code = emitResult(await plane.bot.create({
96
+ name,
97
+ description: flag(rest, "--description") || "",
98
+ title: flag(rest, "--title"),
99
+ agentId: flag(rest, "--agent-id"),
100
+ language: flag(rest, "--language"),
101
+ kickstart: hasFlag(rest, "--kickstart"),
102
+ }, write), asJson);
103
+ }
104
+ else if (noun === "bot" && verb === "update") {
105
+ const id = flag(rest, "--agent-id");
106
+ if (!id)
107
+ usageExit("bot update requires --agent-id");
108
+ code = emitResult(await plane.bot.update(id, {
109
+ name: flag(rest, "--name"),
110
+ description: flag(rest, "--description"),
111
+ title: flag(rest, "--title"),
112
+ }, write), asJson);
113
+ }
114
+ else if (noun === "bot" && verb === "delete") {
115
+ const id = flag(rest, "--agent-id");
116
+ if (!id)
117
+ usageExit("bot delete requires --agent-id");
118
+ code = emitResult(await plane.bot.delete(id, {
119
+ ...write,
120
+ confirm: flag(rest, "--confirm"),
121
+ }), asJson);
122
+ }
123
+ else if (noun === "bot" && verb === "status") {
124
+ const id = flag(rest, "--agent-id");
125
+ if (!id)
126
+ usageExit("bot status requires --agent-id");
127
+ code = emitResult(await plane.progress.status([id]), asJson);
128
+ }
129
+ else if (noun === "bot" && verb === "todos") {
130
+ const id = flag(rest, "--agent-id");
131
+ if (!id)
132
+ usageExit("--agent-id required");
133
+ code = emitResult(await plane.progress.todos(id), asJson);
134
+ }
135
+ else if (noun === "bot" && verb === "tasks") {
136
+ const id = flag(rest, "--agent-id");
137
+ if (!id)
138
+ usageExit("--agent-id required");
139
+ code = emitResult(await plane.progress.tasks(id), asJson);
140
+ }
141
+ else if (noun === "bot" && verb === "subagents") {
142
+ const id = flag(rest, "--agent-id");
143
+ if (!id)
144
+ usageExit("--agent-id required");
145
+ code = emitResult(await plane.progress.subagents(id), asJson);
146
+ }
147
+ else if (noun === "bot" && verb === "interrupt") {
148
+ const id = flag(rest, "--agent-id");
149
+ if (!id)
150
+ usageExit("--agent-id required");
151
+ code = emitResult(await plane.progress.interrupt(id, {
152
+ ...write,
153
+ confirm: flag(rest, "--confirm"),
154
+ reason: flag(rest, "--reason"),
155
+ sessionId: flag(rest, "--session-id"),
156
+ }), asJson);
157
+ }
158
+ else if (noun === "message" && verb === "send") {
159
+ const agentId = flag(rest, "--agent-id");
160
+ const sessionId = flag(rest, "--session-id");
161
+ let text = flag(rest, "--text");
162
+ if (hasFlag(rest, "--text-file")) {
163
+ const fs = await import("node:fs");
164
+ const file = flag(rest, "--text-file");
165
+ if (!file)
166
+ usageExit("--text-file requires a path", "gbc message send ... --text-file ./msg.txt");
167
+ try {
168
+ text = fs.readFileSync(file, "utf8");
169
+ }
170
+ catch (err) {
171
+ usageExit(`cannot read --text-file: ${err.message}`);
172
+ }
173
+ }
174
+ if (hasFlag(rest, "--text-stdin"))
175
+ text = await readStdin();
176
+ if (!agentId || text == null) {
177
+ usageExit("message send requires --agent-id and --text|--text-file|--text-stdin; omitted --session-id uses default");
178
+ }
179
+ code = emitResult(await plane.message.send({
180
+ agentId,
181
+ sessionId: sessionId ?? "",
182
+ text,
183
+ messageId: flag(rest, "--message-id"),
184
+ dryRun: write.dryRun,
185
+ allowWrite: write.allowWrite,
186
+ }), asJson);
187
+ }
188
+ else if (noun === "message" && verb === "status") {
189
+ const agentId = flag(rest, "--agent-id");
190
+ const sessionId = flag(rest, "--session-id");
191
+ const messageId = flag(rest, "--message-id");
192
+ if (!agentId || !messageId)
193
+ usageExit("message status requires --agent-id --message-id; optional --session-id");
194
+ code = emitResult(await plane.message.status({
195
+ agentId,
196
+ sessionId: sessionId ?? "",
197
+ messageId,
198
+ }), asJson);
199
+ }
200
+ else if (noun === "message" && verb === "history") {
201
+ const agentId = flag(rest, "--agent-id");
202
+ if (!agentId)
203
+ usageExit("--agent-id required");
204
+ code = emitResult(await plane.message.history({
205
+ agentId,
206
+ limit: parseCliLimit(flag(rest, "--limit")),
207
+ }), asJson);
208
+ }
209
+ else if (noun === "session" && verb === "list") {
210
+ const agentId = flag(rest, "--agent-id");
211
+ if (!agentId)
212
+ usageExit("--agent-id required");
213
+ code = emitResult(await plane.session.list(agentId), asJson);
214
+ }
215
+ else if (noun === "sidebar" && verb === "list") {
216
+ code = emitResult(await plane.sidebar.list(), asJson);
217
+ }
218
+ else if (noun === "sidebar" && verb === "create") {
219
+ const name = flag(rest, "--name");
220
+ if (!name)
221
+ usageExit("sidebar create requires --name");
222
+ code = emitResult(await plane.sidebar.createSection(name, write), asJson);
223
+ }
224
+ else if (noun === "sidebar" && verb === "rename") {
225
+ const id = flag(rest, "--section-id");
226
+ const name = flag(rest, "--name");
227
+ if (!id || !name)
228
+ usageExit("sidebar rename requires --section-id --name");
229
+ code = emitResult(await plane.sidebar.renameSection(id, name, write), asJson);
230
+ }
231
+ else if (noun === "sidebar" && verb === "delete") {
232
+ const id = flag(rest, "--section-id");
233
+ if (!id)
234
+ usageExit("--section-id required");
235
+ code = emitResult(await plane.sidebar.deleteSection(id, {
236
+ ...write,
237
+ confirm: flag(rest, "--confirm"),
238
+ }), asJson);
239
+ }
240
+ else if (noun === "sidebar" && verb === "move") {
241
+ const agentId = flag(rest, "--agent-id");
242
+ const sectionId = flag(rest, "--section-id");
243
+ if (!agentId || !sectionId)
244
+ usageExit("sidebar move requires --agent-id --section-id");
245
+ code = emitResult(await plane.sidebar.moveBot(agentId, sectionId, write), asJson);
246
+ }
247
+ else if (noun === "room") {
248
+ code = emitResult(failResult("unsupported", "Room RPCs are intentionally not mixed into sidebar; not implemented in this cut", {
249
+ hint: "Use sidebar for App sidebar grouping.",
250
+ }), asJson);
251
+ }
252
+ else {
253
+ usageExit(`unknown command: ${noun} ${verb}`, "gbc --help");
254
+ }
255
+ persistMockState();
256
+ return code;
257
+ }
258
+ catch (e) {
259
+ persistMockState();
260
+ return catchToExit(e, asJson);
261
+ }
262
+ }
263
+ function readStdin() {
264
+ return new Promise((resolve, reject) => {
265
+ if (process.stdin.isTTY) {
266
+ reject(new Error("stdin is a TTY; pass --text or --text-file"));
267
+ return;
268
+ }
269
+ const chunks = [];
270
+ process.stdin.on("data", (c) => chunks.push(Buffer.from(c)));
271
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
272
+ process.stdin.on("error", reject);
273
+ });
274
+ }
275
+ main().then((code) => process.exit(code));
@@ -0,0 +1,4 @@
1
+ import { type CliGlobal } from "./config.js";
2
+ import type { ResultEnvelope } from "../dto/result.js";
3
+ /** Sync opts are deliberately strict; an unknown selector must not broaden a pull. */
4
+ export declare function syncCommand(globals: CliGlobal, rest: string[]): Promise<ResultEnvelope>;
@@ -0,0 +1,105 @@
1
+ import { SyncOps } from "../sync/core.js";
2
+ import { buildPlane, flag, hasFlag } from "./config.js";
3
+ import { GbcError } from "../errors.js";
4
+ /** Sync opts are deliberately strict; an unknown selector must not broaden a pull. */
5
+ export async function syncCommand(globals, rest) {
6
+ const verb = rest[1];
7
+ if (!["pull", "status", "render", "attachments"].includes(verb))
8
+ throw new GbcError("usage", "sync pull|status|render|attachments required");
9
+ const values = new Set([
10
+ "--dir",
11
+ "--agent-id",
12
+ "--session-id",
13
+ ...(verb === "pull"
14
+ ? [
15
+ "--account-id",
16
+ "--team-id",
17
+ "--max-pages",
18
+ "--page-size",
19
+ "--hydrate",
20
+ "--max-hydrate-requests",
21
+ "--hydrate-max-bytes",
22
+ "--hydrate-max-body-bytes",
23
+ "--max-bytes",
24
+ "--timeout-ms",
25
+ ]
26
+ : []),
27
+ ...(verb === "attachments"
28
+ ? [
29
+ "--path",
30
+ "--max-bytes",
31
+ "--max-file-bytes",
32
+ "--timeout-ms",
33
+ "--account-id",
34
+ "--team-id",
35
+ ]
36
+ : []),
37
+ ]);
38
+ const switches = new Set(["pull", "attachments"].includes(verb) ? ["--ack-unverified-scope"] : []);
39
+ const seen = new Set();
40
+ for (let j = 2; j < rest.length; j++) {
41
+ const k = rest[j].split("=")[0];
42
+ if (seen.has(k) || (!values.has(k) && !switches.has(k)))
43
+ throw new GbcError("usage", "unknown/duplicate sync flag (v1 requires one --agent-id; --all not supported)");
44
+ seen.add(k);
45
+ if (values.has(k)) {
46
+ flag(rest, k);
47
+ if (!rest[j].includes("="))
48
+ j++;
49
+ }
50
+ else if (rest[j].includes("="))
51
+ throw new GbcError("usage", "acknowledgement is a switch, not a value flag");
52
+ }
53
+ const dir = flag(rest, "--dir"), agentId = flag(rest, "--agent-id"), sessionId = flag(rest, "--session-id");
54
+ if (!dir)
55
+ throw new GbcError("usage", "sync requires --dir <absolute private root>");
56
+ const local = new SyncOps({}, globals.mock ? "mock://gbc" : globals.baseUrl, globals.mock);
57
+ if (verb === "status")
58
+ return local.status({ dir, agentId, sessionId });
59
+ if (verb === "render")
60
+ return local.render({ dir, agentId, sessionId, dryRun: globals.dryRun });
61
+ function number(name) {
62
+ const v = flag(rest, name);
63
+ if (v === undefined)
64
+ return;
65
+ if (!/^\d+$/.test(v) || !Number.isSafeInteger(Number(v)))
66
+ throw new GbcError("usage", `${name} requires an integer`);
67
+ return Number(v);
68
+ }
69
+ if (verb === "attachments") {
70
+ const input = {
71
+ dir,
72
+ agentId,
73
+ sessionId,
74
+ path: flag(rest, "--path"),
75
+ accountId: flag(rest, "--account-id") ?? "",
76
+ teamId: flag(rest, "--team-id") ?? "",
77
+ acknowledgeUnverifiedScope: hasFlag(rest, "--ack-unverified-scope"),
78
+ dryRun: globals.dryRun,
79
+ maxBytes: number("--max-bytes"),
80
+ maxFileBytes: number("--max-file-bytes"),
81
+ timeoutMs: number("--timeout-ms"),
82
+ };
83
+ return (globals.dryRun ? local : buildPlane(globals).sync).attachments(input);
84
+ }
85
+ if (!agentId)
86
+ throw new GbcError("usage", "sync pull requires --agent-id");
87
+ const input = {
88
+ dir,
89
+ agentId,
90
+ sessionId,
91
+ accountId: flag(rest, "--account-id") ?? "",
92
+ teamId: flag(rest, "--team-id") ?? "",
93
+ acknowledgeUnverifiedScope: hasFlag(rest, "--ack-unverified-scope"),
94
+ dryRun: globals.dryRun,
95
+ pageSize: number("--page-size"),
96
+ hydrate: flag(rest, "--hydrate"),
97
+ maxHydrateRequests: number("--max-hydrate-requests"),
98
+ hydrateMaxBytes: number("--hydrate-max-bytes"),
99
+ hydrateMaxBodyBytes: number("--hydrate-max-body-bytes"),
100
+ maxPages: number("--max-pages"),
101
+ maxBytes: number("--max-bytes"),
102
+ timeoutMs: number("--timeout-ms"),
103
+ };
104
+ return (globals.dryRun ? local : buildPlane(globals).sync).pull(input);
105
+ }
@@ -0,0 +1,32 @@
1
+ import { ConnectError } from "@connectrpc/connect";
2
+ import type { Interceptor, Transport } from "@connectrpc/connect";
3
+ import type { AuthProvider } from "../auth/types.js";
4
+ import { GbcError } from "../errors.js";
5
+ /**
6
+ * Generated Connect client. Typed loosely because vendored JS ServiceType
7
+ * lacks full TS generics; runtime methods match GrokBotService.
8
+ */
9
+ export type GrokBotClient = any;
10
+ export type ConnectConfig = {
11
+ baseUrl: string;
12
+ auth?: AuthProvider;
13
+ transport?: Transport;
14
+ /** Optional transport decoded-response cap (sync uses 16MiB). */
15
+ readMaxBytes?: number;
16
+ };
17
+ /**
18
+ * Project read-only unary RPCs eligible for at most one 401 refresh+retry.
19
+ * Writes are never retried. Membership is by Connect method name (PascalCase),
20
+ * never by HTTP verb alone.
21
+ */
22
+ export declare const READ_ONLY_UNARY_METHODS: Set<string>;
23
+ /** Carry GbcError through Connect as `cause` so ops can restore structured codes. */
24
+ export declare function gbcAsConnect(gbc: GbcError): ConnectError;
25
+ export declare function unwrapGbcCause(err: unknown): GbcError | undefined;
26
+ export declare function createAuthInterceptor(auth: AuthProvider): Interceptor;
27
+ export declare function createGrokBotClient(cfg: ConnectConfig): GrokBotClient;
28
+ export type MapErrorOpts = {
29
+ /** True when the RPC is a mutation (create/update/delete/send/sidebar write/interrupt). */
30
+ write?: boolean;
31
+ };
32
+ export declare function mapConnectError(err: unknown, requestId: string, opts?: MapErrorOpts): GbcError;
@@ -0,0 +1,257 @@
1
+ import { createClient } from "@connectrpc/connect";
2
+ import { createConnectTransport } from "@connectrpc/connect-node";
3
+ import { Code, ConnectError } from "@connectrpc/connect";
4
+ import { GrokBotService } from "#proto-gen/GrokBotService";
5
+ import { GbcError } from "../errors.js";
6
+ /**
7
+ * Project read-only unary RPCs eligible for at most one 401 refresh+retry.
8
+ * Writes are never retried. Membership is by Connect method name (PascalCase),
9
+ * never by HTTP verb alone.
10
+ */
11
+ export const READ_ONLY_UNARY_METHODS = new Set([
12
+ "ListGrokBotAgents",
13
+ "ListGrokBotAgentStatuses",
14
+ "GetGrokBotUserRuntimeSettings",
15
+ "ListGrokBotAgentSessions",
16
+ "GetGrokBotSendStatus",
17
+ "ListGrokBotAgentTodos",
18
+ "ListGrokBotAgentTasks",
19
+ "ListGrokBotSubagents",
20
+ "ListGrokBotTranscriptEntries",
21
+ "ResolveGrokBotAttachmentReads",
22
+ "PresignSandBoxStoreReads",
23
+ ]);
24
+ function isUnauthenticated(err) {
25
+ if (err instanceof ConnectError)
26
+ return err.code === Code.Unauthenticated;
27
+ const anyErr = err;
28
+ const c = String(anyErr.code ?? "");
29
+ return c === "unauthenticated" || c === "16" || c === String(Code.Unauthenticated);
30
+ }
31
+ function methodName(req) {
32
+ return req.method?.name ?? "";
33
+ }
34
+ function isReadOnlyUnary(req) {
35
+ if (req.stream)
36
+ return false;
37
+ return READ_ONLY_UNARY_METHODS.has(methodName(req));
38
+ }
39
+ function connectCodeFor(gbc) {
40
+ switch (gbc.code) {
41
+ case "unauthenticated":
42
+ case "auth":
43
+ case "auth_revoked":
44
+ case "auth_refresh_unconfigured":
45
+ case "auth_refresh_client_mismatch":
46
+ case "auth_refresh_subject_mismatch":
47
+ return Code.Unauthenticated;
48
+ case "denied":
49
+ case "permission_denied":
50
+ return Code.PermissionDenied;
51
+ case "auth_refresh_network":
52
+ case "auth_refresh_timeout":
53
+ case "auth_refresh_rate_limited":
54
+ case "auth_refresh_cooldown":
55
+ case "auth_refresh_lock_timeout":
56
+ return Code.Unavailable;
57
+ default:
58
+ return gbc.retryable ? Code.Unavailable : Code.FailedPrecondition;
59
+ }
60
+ }
61
+ /** Carry GbcError through Connect as `cause` so ops can restore structured codes. */
62
+ export function gbcAsConnect(gbc) {
63
+ return new ConnectError(gbc.message, connectCodeFor(gbc), undefined, undefined, gbc);
64
+ }
65
+ export function unwrapGbcCause(err) {
66
+ if (err instanceof GbcError)
67
+ return err;
68
+ if (err instanceof ConnectError && err.cause instanceof GbcError)
69
+ return err.cause;
70
+ const anyErr = err;
71
+ if (anyErr?.cause instanceof GbcError)
72
+ return anyErr.cause;
73
+ return undefined;
74
+ }
75
+ export function createAuthInterceptor(auth) {
76
+ return (next) => async (req) => {
77
+ let token;
78
+ try {
79
+ token = await auth.getAccessToken();
80
+ }
81
+ catch (e) {
82
+ // Fail locally (request NOT sent). Wrap as ConnectError with GbcError cause so
83
+ // mapConnectError can restore structured codes (Connect may not pass plain Error).
84
+ const gbc = e instanceof GbcError
85
+ ? new GbcError(e.code, e.message, {
86
+ retryable: e.retryable,
87
+ outcome: e.outcome,
88
+ hint: e.hint,
89
+ exitCode: e.exitCode,
90
+ details: {
91
+ ...((typeof e.details === "object" && e.details) || {}),
92
+ rpc_dispatched: false,
93
+ },
94
+ })
95
+ : new GbcError("unauthenticated", "credential load/refresh failed (request not sent)", {
96
+ exitCode: 4,
97
+ details: { rpc_dispatched: false },
98
+ });
99
+ throw gbcAsConnect(gbc);
100
+ }
101
+ if (!token) {
102
+ throw gbcAsConnect(new GbcError("unauthenticated", "not logged in: run `gbc auth login` (request not sent)", {
103
+ exitCode: 4,
104
+ details: { rpc_dispatched: false },
105
+ }));
106
+ }
107
+ req.header.set("authorization", `Bearer ${token}`);
108
+ try {
109
+ return await next(req);
110
+ }
111
+ catch (err) {
112
+ if (!isUnauthenticated(err))
113
+ throw err;
114
+ // Closest to App Qz / Grok Build recovery: only force-rotate when the rejected
115
+ // bearer is still the current disk token; otherwise adopt the newer pair.
116
+ // Never replay writes. Optional: one refresh+retry for whitelisted read-only unary.
117
+ let refreshed = false;
118
+ try {
119
+ await auth.refresh({ force: true, rejectedAccessToken: token });
120
+ refreshed = true;
121
+ }
122
+ catch {
123
+ /* keep original 401; creds may be cleared on shouldLogout */
124
+ }
125
+ const allowRetry = refreshed && isReadOnlyUnary(req);
126
+ if (!allowRetry)
127
+ throw err;
128
+ let token2;
129
+ try {
130
+ token2 = await auth.getAccessToken();
131
+ }
132
+ catch {
133
+ throw err;
134
+ }
135
+ if (!token2 || token2 === token)
136
+ throw err;
137
+ req.header.set("authorization", `Bearer ${token2}`);
138
+ return next(req);
139
+ }
140
+ };
141
+ }
142
+ export function createGrokBotClient(cfg) {
143
+ const interceptors = [];
144
+ if (cfg.auth)
145
+ interceptors.push(createAuthInterceptor(cfg.auth));
146
+ const transport = cfg.transport ??
147
+ createConnectTransport({
148
+ baseUrl: cfg.baseUrl.replace(/\/$/, ""),
149
+ httpVersion: "1.1",
150
+ readMaxBytes: cfg.readMaxBytes,
151
+ interceptors,
152
+ });
153
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
+ return createClient(GrokBotService, transport);
155
+ }
156
+ /**
157
+ * Codes that do not prove the server did NOT apply a mutation. For writes these
158
+ * become outcome=unknown / retryable=false / exit 10 (reconcile, never blind-retry).
159
+ */
160
+ const WRITE_AMBIGUOUS = new Set(["unavailable", "rpc_error", "internal", "unknown_rpc", "resource_exhausted"]);
161
+ export function mapConnectError(err, requestId, opts) {
162
+ const caused = unwrapGbcCause(err);
163
+ if (caused) {
164
+ const details = {
165
+ ...((typeof caused.details === "object" && caused.details) || {}),
166
+ request_id: requestId,
167
+ };
168
+ return new GbcError(caused.code, caused.message, {
169
+ retryable: caused.retryable,
170
+ outcome: caused.outcome,
171
+ hint: caused.hint,
172
+ exitCode: caused.exitCode,
173
+ details,
174
+ });
175
+ }
176
+ const base = mapConnectErrorBase(err, requestId);
177
+ if (opts?.write && WRITE_AMBIGUOUS.has(base.code)) {
178
+ return new GbcError("unknown", `write outcome unknown (${base.code}): ${base.message}`, {
179
+ outcome: "unknown",
180
+ retryable: false,
181
+ exitCode: 10,
182
+ hint: "Server may or may not have applied this write. Reconcile with list/get_send_status/history before any retry; do not blindly resend.",
183
+ details: { request_id: requestId, rpc_code: base.code },
184
+ });
185
+ }
186
+ return base;
187
+ }
188
+ function mapConnectErrorBase(err, requestId) {
189
+ const anyErr = err;
190
+ const msg = anyErr.rawMessage || anyErr.message || String(err);
191
+ const codeStr = String(anyErr.code ?? "");
192
+ if (anyErr.name === "AbortError" ||
193
+ /fetch failed|ECONNRESET|socket hang up|network/i.test(msg)) {
194
+ return new GbcError("unknown", `transport ended without a known outcome: ${msg}`, {
195
+ outcome: "unknown",
196
+ retryable: false,
197
+ exitCode: 10,
198
+ hint: "Do not blindly resend create/send/delete. Reconcile with list/get_send_status/history.",
199
+ details: { request_id: requestId },
200
+ });
201
+ }
202
+ switch (codeStr) {
203
+ case "unauthenticated":
204
+ case "16":
205
+ return new GbcError("unauthenticated", msg, { exitCode: 4, details: { request_id: requestId, rpc_dispatched: true } });
206
+ case "not_found":
207
+ case "5":
208
+ return new GbcError("not_found", msg, { exitCode: 5 });
209
+ case "already_exists":
210
+ case "aborted":
211
+ case "6":
212
+ case "10":
213
+ return new GbcError("conflict", msg, { exitCode: 6 });
214
+ case "permission_denied":
215
+ case "7":
216
+ return new GbcError("permission_denied", msg, { exitCode: 7 });
217
+ case "failed_precondition":
218
+ case "9":
219
+ return new GbcError("failed_precondition", msg, {
220
+ exitCode: 7,
221
+ hint: "May indicate sidebar_sections_base conflict — re-read and retry patch.",
222
+ });
223
+ case "unavailable":
224
+ case "14":
225
+ return new GbcError("unavailable", msg, { retryable: true, exitCode: 8 });
226
+ case "deadline_exceeded":
227
+ case "4":
228
+ return new GbcError("deadline_exceeded", msg, {
229
+ outcome: "unknown",
230
+ retryable: false,
231
+ exitCode: 10,
232
+ hint: "Deadline without response: treat write as unknown; reconcile; do not auto-replay.",
233
+ });
234
+ case "canceled":
235
+ case "1":
236
+ return new GbcError("canceled", msg, {
237
+ outcome: "unknown",
238
+ retryable: false,
239
+ exitCode: 10,
240
+ hint: "Request canceled ≠ InterruptGrokBotAgentRun.",
241
+ });
242
+ case "internal":
243
+ case "13":
244
+ return new GbcError("internal", msg, { exitCode: 9 });
245
+ case "unknown":
246
+ case "2":
247
+ return new GbcError("unknown_rpc", msg, { exitCode: 9 });
248
+ case "resource_exhausted":
249
+ case "8":
250
+ return new GbcError("resource_exhausted", msg, { retryable: true, exitCode: 8 });
251
+ case "invalid_argument":
252
+ case "3":
253
+ return new GbcError("invalid_argument", msg, { exitCode: 2 });
254
+ default:
255
+ return new GbcError("rpc_error", msg, { exitCode: 9, details: { code: codeStr } });
256
+ }
257
+ }