@moondeck/station 0.0.1 → 1.0.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 (326) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -2
  3. package/bin/uai-host.mjs +130 -0
  4. package/db/migrations/0000_host_tasks.sql +12 -0
  5. package/db/migrations/0001_host_ui.sql +11 -0
  6. package/db/migrations/0002_host_github_tokens.sql +8 -0
  7. package/db/migrations/0003_host_ssh_keys.sql +8 -0
  8. package/db/migrations/0004_host_owner_name.sql +1 -0
  9. package/db/migrations/0005_host_env_vars.sql +5 -0
  10. package/db/migrations/0006_host_project_env.sql +9 -0
  11. package/db/migrations/0007_host_github_token_kind.sql +7 -0
  12. package/db/migrations/0008_host_mcp_connections.sql +18 -0
  13. package/db/migrations/0009_host_agent_sessions.sql +15 -0
  14. package/db/migrations/0010_host_engine_accounts.sql +13 -0
  15. package/db/migrations/0011_codex_thread_id.sql +5 -0
  16. package/db/migrations/0012_agent_session_attach_key.sql +4 -0
  17. package/db/migrations/0013_github_credential_generations.sql +11 -0
  18. package/db/migrations/0014_host_inventory_event_index.sql +1 -0
  19. package/db/migrations/0015_host_settings.sql +9 -0
  20. package/db/migrations/0016_task_environment.sql +2 -0
  21. package/db/migrations/0017_engine_account_scope.sql +7 -0
  22. package/db/migrations/0018_engine_account_position.sql +3 -0
  23. package/db/migrations/0019_engine_account_store_ref.sql +3 -0
  24. package/db/migrations/0020_user_credential_store_ref.sql +6 -0
  25. package/db/migrations/0021_project_env_store_ref.sql +3 -0
  26. package/db/migrations/0022_task_project_ids.sql +5 -0
  27. package/db/migrations/0023_project_env_org.sql +4 -0
  28. package/db/migrations/0024_task_env_mode.sql +9 -0
  29. package/db/migrations/0025_project_env_authority.sql +147 -0
  30. package/db/migrations/0026_project_env_replica.sql +150 -0
  31. package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
  32. package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
  33. package/db/migrations/0029_task_shared_files_mode.sql +5 -0
  34. package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
  35. package/db/migrations/meta/_journal.json +223 -0
  36. package/db/schema.ts +850 -0
  37. package/images/standard/Dockerfile +347 -0
  38. package/images/standard/README.md +167 -0
  39. package/images/standard/container/code-server-settings.json +63 -0
  40. package/images/standard/container/corepack-version +1 -0
  41. package/images/standard/container/uai-init +704 -0
  42. package/images/standard/container/uai-materialize-runtimes +1527 -0
  43. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  44. package/images/standard/tool-versions +2 -0
  45. package/lib/__fixtures__/skill-index/SKILL.md +10 -0
  46. package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
  47. package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
  48. package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
  49. package/lib/agent-cli.ts +1507 -0
  50. package/lib/agent-errors.ts +22 -0
  51. package/lib/agent.ts +672 -0
  52. package/lib/agents/attach-compatibility.ts +51 -0
  53. package/lib/agents/claude.ts +918 -0
  54. package/lib/agents/codex-token-usage.fixture.json +130 -0
  55. package/lib/agents/codex.ts +1392 -0
  56. package/lib/agents/cursor-period-usage.ts +253 -0
  57. package/lib/agents/cursor.ts +562 -0
  58. package/lib/agents/dispatch.ts +78 -0
  59. package/lib/agents/durable-proc.ts +334 -0
  60. package/lib/agents/factory.ts +57 -0
  61. package/lib/agents/grok.ts +381 -0
  62. package/lib/agents/kimi.ts +329 -0
  63. package/lib/agents/machine-durable.ts +604 -0
  64. package/lib/agents/mock.ts +133 -0
  65. package/lib/agents/mode.ts +64 -0
  66. package/lib/agents/opencode.ts +508 -0
  67. package/lib/agents/output-source.ts +3 -0
  68. package/lib/agents/proc.ts +221 -0
  69. package/lib/agents/rate-limit.ts +337 -0
  70. package/lib/agents/registry.ts +182 -0
  71. package/lib/agents/session-state.ts +197 -0
  72. package/lib/agents/skill-index-preamble.fixture.ts +26 -0
  73. package/lib/agents/transport.ts +741 -0
  74. package/lib/agents/types.ts +254 -0
  75. package/lib/agents/uai.ts +106 -0
  76. package/lib/agents/usage.ts +44 -0
  77. package/lib/apple-one-off.ts +277 -0
  78. package/lib/apple-runtime-recycle.ts +237 -0
  79. package/lib/apple-uninstall-teardown.ts +224 -0
  80. package/lib/assignment-evidence.ts +94 -0
  81. package/lib/attachments.ts +48 -0
  82. package/lib/browser-testing.ts +1537 -0
  83. package/lib/capture-flow.ts +332 -0
  84. package/lib/cloud-credential-cleanup-adapter.ts +123 -0
  85. package/lib/cloud-credential-host.ts +512 -0
  86. package/lib/cloud-credential-leases.ts +2587 -0
  87. package/lib/cloud-credential-runtime.ts +1357 -0
  88. package/lib/cloud-state.ts +56 -0
  89. package/lib/codex-auth.ts +456 -0
  90. package/lib/command-db.ts +310 -0
  91. package/lib/compose-network-recovery.ts +283 -0
  92. package/lib/connection-secret-redaction.ts +25 -0
  93. package/lib/container-runtime.ts +1453 -0
  94. package/lib/credential-cleanup-inventory.ts +490 -0
  95. package/lib/credential-consumers/engine-anchor.ts +1880 -0
  96. package/lib/credential-consumers/engines.ts +797 -0
  97. package/lib/credential-consumers/files.ts +934 -0
  98. package/lib/credential-consumers/github.ts +702 -0
  99. package/lib/credential-consumers/mcp.ts +389 -0
  100. package/lib/credential-consumers/task-files.ts +127 -0
  101. package/lib/credential-consumers/task-secrets.ts +10 -0
  102. package/lib/credential-store-pickup.ts +149 -0
  103. package/lib/credential-store-scope.ts +112 -0
  104. package/lib/credential-store.ts +932 -0
  105. package/lib/credentials-activation-authority.ts +709 -0
  106. package/lib/credentials-purge.ts +1006 -0
  107. package/lib/custom-connection-capture.ts +395 -0
  108. package/lib/custom-connection-launch.ts +384 -0
  109. package/lib/custom-connection-runtime.ts +141 -0
  110. package/lib/db.ts +80 -0
  111. package/lib/disk-pressure.ts +42 -0
  112. package/lib/docker-exec.ts +156 -0
  113. package/lib/engine-accounts.ts +1970 -0
  114. package/lib/engine-connect.ts +321 -0
  115. package/lib/engine-credential-env.ts +52 -0
  116. package/lib/engine-login.ts +2933 -0
  117. package/lib/engines.ts +1342 -0
  118. package/lib/enrollment-state.ts +251 -0
  119. package/lib/env-file.ts +246 -0
  120. package/lib/env-schema.ts +891 -0
  121. package/lib/env.ts +195 -0
  122. package/lib/fs-aliases.ts +49 -0
  123. package/lib/git-clone-credentials.ts +334 -0
  124. package/lib/git-diff.ts +530 -0
  125. package/lib/git-identity.ts +715 -0
  126. package/lib/github-git-auth.ts +207 -0
  127. package/lib/github-origin.ts +28 -0
  128. package/lib/github-tokens.ts +2439 -0
  129. package/lib/host-cloud-url.ts +62 -0
  130. package/lib/host-config.ts +279 -0
  131. package/lib/host-env.ts +424 -0
  132. package/lib/host-logs.ts +965 -0
  133. package/lib/internal-connections.ts +5 -0
  134. package/lib/keyed-promise-tail.ts +23 -0
  135. package/lib/launch-mode.ts +25 -0
  136. package/lib/lease.ts +239 -0
  137. package/lib/legacy-host.ts +769 -0
  138. package/lib/legacy-runtime-retirement.ts +424 -0
  139. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  140. package/lib/machine-exec.ts +238 -0
  141. package/lib/machine-gateway-tunnel.ts +344 -0
  142. package/lib/machine-keys.ts +65 -0
  143. package/lib/machine-project-env-loader.ts +152 -0
  144. package/lib/machine-provider-aws.ts +923 -0
  145. package/lib/machine-provider-local.ts +338 -0
  146. package/lib/machine-provider.ts +90 -0
  147. package/lib/managed-activation-watcher.ts +72 -0
  148. package/lib/managed-install-owner-watcher.ts +55 -0
  149. package/lib/managed-operation-drain.ts +57 -0
  150. package/lib/managed-runtime.ts +4296 -0
  151. package/lib/managed-update-scheduler.ts +125 -0
  152. package/lib/mcp-capture.ts +865 -0
  153. package/lib/mcp-config-lock.ts +2 -0
  154. package/lib/mcp-connections.ts +959 -0
  155. package/lib/mcp-gateway.ts +2594 -0
  156. package/lib/obs.ts +512 -0
  157. package/lib/openrouter-budget.ts +247 -0
  158. package/lib/orchestrator.ts +11409 -0
  159. package/lib/person-capture.ts +327 -0
  160. package/lib/preview-ports.ts +85 -0
  161. package/lib/preview-sidecar.ts +331 -0
  162. package/lib/project-env-authority.ts +1319 -0
  163. package/lib/project-env-feature.ts +74 -0
  164. package/lib/project-env-legacy-tags.ts +130 -0
  165. package/lib/project-env-loader.ts +242 -0
  166. package/lib/project-env-replica.ts +2094 -0
  167. package/lib/project-env-schema-capture.ts +342 -0
  168. package/lib/project-env-staging.ts +148 -0
  169. package/lib/project-env-task-policy.ts +176 -0
  170. package/lib/project-env-task-replica.ts +1752 -0
  171. package/lib/release-feed.ts +61 -0
  172. package/lib/release-manifest.ts +710 -0
  173. package/lib/release-trust.ts +46 -0
  174. package/lib/repo-clone.ts +38 -0
  175. package/lib/routing/shadow.ts +261 -0
  176. package/lib/runner-boot.ts +10 -0
  177. package/lib/runtime-activation-tail.ts +279 -0
  178. package/lib/runtime-archive.ts +1216 -0
  179. package/lib/runtime-authority.ts +166 -0
  180. package/lib/runtime-guard.ts +36 -0
  181. package/lib/runtime-provider-state.ts +169 -0
  182. package/lib/runtime-reconnect.ts +86 -0
  183. package/lib/runtime-state.ts +1033 -0
  184. package/lib/safe-http.ts +364 -0
  185. package/lib/secrets-proxy/address.ts +95 -0
  186. package/lib/secrets-proxy/audit.ts +30 -0
  187. package/lib/secrets-proxy/ca-store.ts +200 -0
  188. package/lib/secrets-proxy/ca.ts +470 -0
  189. package/lib/secrets-proxy/container-steps.ts +138 -0
  190. package/lib/secrets-proxy/der.ts +273 -0
  191. package/lib/secrets-proxy/engine.ts +1633 -0
  192. package/lib/secrets-proxy/host.ts +93 -0
  193. package/lib/secrets-proxy/hostname.ts +57 -0
  194. package/lib/secrets-proxy/placeholders.ts +196 -0
  195. package/lib/secrets-proxy/policy.ts +150 -0
  196. package/lib/secrets-proxy/readiness.ts +148 -0
  197. package/lib/secrets-proxy/runtime.ts +582 -0
  198. package/lib/secrets-proxy/scrub.ts +559 -0
  199. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  200. package/lib/secrets-proxy/task-scripts.ts +262 -0
  201. package/lib/secrets-proxy/task-trust.ts +605 -0
  202. package/lib/secrets-proxy/trust-watch.ts +80 -0
  203. package/lib/secrets.ts +71 -0
  204. package/lib/shared-files-s3.ts +976 -0
  205. package/lib/shared-files.ts +156 -0
  206. package/lib/sidecar-assign.ts +49 -0
  207. package/lib/sidecar-guard.ts +58 -0
  208. package/lib/sidecar-resume.ts +178 -0
  209. package/lib/sidecar.ts +69 -0
  210. package/lib/signin-box-credentials.ts +465 -0
  211. package/lib/signin-box.ts +114 -0
  212. package/lib/skill-index-example.fixture.ts +56 -0
  213. package/lib/skill-layout.ts +417 -0
  214. package/lib/skills.ts +489 -0
  215. package/lib/ssh.ts +401 -0
  216. package/lib/standard-image-cleanup.ts +117 -0
  217. package/lib/standard-image-pin.ts +207 -0
  218. package/lib/standard-image.ts +1697 -0
  219. package/lib/stopped-task-status-queue.ts +120 -0
  220. package/lib/task-container-cli.ts +269 -0
  221. package/lib/task-credential-cleanup.ts +752 -0
  222. package/lib/task-diff-sandbox.ts +464 -0
  223. package/lib/task-diff.ts +91 -0
  224. package/lib/task-env-forwarder.ts +228 -0
  225. package/lib/task-env-source.ts +249 -0
  226. package/lib/task-environment/anchor.ts +31 -0
  227. package/lib/task-environment/apple-container.ts +860 -0
  228. package/lib/task-environment/docker.ts +1344 -0
  229. package/lib/task-environment/index.ts +466 -0
  230. package/lib/task-environment/legacy-adoption.ts +467 -0
  231. package/lib/task-environment/machine-environment.ts +61 -0
  232. package/lib/task-environment/machine-task-up.ts +1227 -0
  233. package/lib/task-environment/machine.ts +974 -0
  234. package/lib/task-environment/registry.ts +59 -0
  235. package/lib/task-environment/self-identity.ts +20 -0
  236. package/lib/task-environment/self.ts +566 -0
  237. package/lib/task-environment/types.ts +642 -0
  238. package/lib/task-environment/workspace-files.ts +685 -0
  239. package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
  240. package/lib/task-files-machine-retirement-outbox.ts +592 -0
  241. package/lib/task-files-machine-retirement.ts +116 -0
  242. package/lib/task-files-readiness.ts +127 -0
  243. package/lib/task-gateway-port.ts +22 -0
  244. package/lib/task-gateway-protocol.ts +190 -0
  245. package/lib/task-gateway-recovery.ts +47 -0
  246. package/lib/task-gateway-state.ts +512 -0
  247. package/lib/task-identity.ts +19 -0
  248. package/lib/task-inventory.ts +682 -0
  249. package/lib/task-machine-credentials-activation.ts +95 -0
  250. package/lib/task-machine-swap-safety.ts +729 -0
  251. package/lib/task-person-credentials.ts +76 -0
  252. package/lib/task-project-ops-host.ts +147 -0
  253. package/lib/task-project-ops.ts +1062 -0
  254. package/lib/task-secret-staging.ts +16 -0
  255. package/lib/task-status.ts +46 -0
  256. package/lib/task-token.ts +46 -0
  257. package/lib/token-broker-adopt.ts +344 -0
  258. package/lib/token-broker-lambda.ts +105 -0
  259. package/lib/token-broker-local.ts +392 -0
  260. package/lib/token-broker-revision.ts +310 -0
  261. package/lib/token-broker-ssm.ts +525 -0
  262. package/lib/token-broker.ts +218 -0
  263. package/lib/token-lanes/openai-oauth.ts +123 -0
  264. package/lib/token-lanes/registry.ts +45 -0
  265. package/lib/token-lanes/types.ts +163 -0
  266. package/lib/token-lanes/xai-oauth.ts +165 -0
  267. package/lib/transcript.ts +128 -0
  268. package/lib/tunnel-registry.ts +220 -0
  269. package/lib/tunnel-runtime.ts +235 -0
  270. package/lib/ulid.ts +16 -0
  271. package/lib/user-bus-env.ts +116 -0
  272. package/lib/user-credentials-store.ts +435 -0
  273. package/lib/vnode-pressure.ts +142 -0
  274. package/package.json +115 -8
  275. package/runner/runner.mjs +442 -0
  276. package/scripts/agent/_common.sh +633 -0
  277. package/scripts/agent/env-hook.sh +626 -0
  278. package/scripts/agent/moondeck-cli.sh +5 -0
  279. package/scripts/agent/task-down.sh +291 -0
  280. package/scripts/agent/task-status.sh +70 -0
  281. package/scripts/agent/task-up.sh +3761 -0
  282. package/scripts/agent/uai-env-exec.sh +26 -0
  283. package/scripts/install/darwin.ts +1048 -0
  284. package/scripts/install/linux.ts +958 -0
  285. package/scripts/install/types.ts +78 -0
  286. package/scripts/install/util.ts +254 -0
  287. package/scripts/install/win.ts +143 -0
  288. package/src/activation-respawn.ts +155 -0
  289. package/src/apple-tunnel-route.ts +104 -0
  290. package/src/capture-wire.ts +102 -0
  291. package/src/cli.ts +2583 -0
  292. package/src/cloud-credentials.ts +2144 -0
  293. package/src/cloud-origins.ts +23 -0
  294. package/src/credential-location.ts +248 -0
  295. package/src/credential-wire-validation.ts +25 -0
  296. package/src/custom-connections.ts +237 -0
  297. package/src/engine-account-paths.ts +11 -0
  298. package/src/engine-models.ts +39 -0
  299. package/src/engine-usage.ts +104 -0
  300. package/src/event-outbox.ts +185 -0
  301. package/src/index.ts +2899 -0
  302. package/src/load-env.ts +97 -0
  303. package/src/machine-tunnel-route.ts +156 -0
  304. package/src/main.ts +6349 -0
  305. package/src/never-proxied-domains.ts +31 -0
  306. package/src/outbound-frame.ts +31 -0
  307. package/src/paths.ts +81 -0
  308. package/src/project-env-latest.ts +130 -0
  309. package/src/project-env-policy.ts +396 -0
  310. package/src/project-env-wording.ts +53 -0
  311. package/src/protocol.ts +5545 -0
  312. package/src/runtime-bootstrap.ts +165 -0
  313. package/src/station-identity.ts +96 -0
  314. package/src/task-env-names.ts +28 -0
  315. package/src/task-secrets.ts +75 -0
  316. package/src/tunnel-dial.ts +102 -0
  317. package/src/ui/server.ts +852 -0
  318. package/src/ui/types.ts +173 -0
  319. package/src/workspace-path-policy.ts +41 -0
  320. package/ui/app.js +1102 -0
  321. package/ui/index.html +90 -0
  322. package/ui/style.css +685 -0
  323. package/ui/uai-favicon.svg +21 -0
  324. package/ui/uai-logo-black.svg +9 -0
  325. package/ui/uai-wheel.svg +9 -0
  326. package/bin/moondeck-station.mjs +0 -3
@@ -0,0 +1,381 @@
1
+ /**
2
+ * GrokSession — a real AgentSession backed by xAI's **Grok** CLI, run inside
3
+ * the task environment:
4
+ *
5
+ * ~/.grok/bin/grok -p "<prompt>" \
6
+ * --output-format streaming-json --permission-mode bypassPermissions \
7
+ * -m grok-4.5 --rules "<briefing>" [-r <sessionId>]
8
+ *
9
+ * Like Kimi (and unlike Claude's persistent stdin / Codex's app-server), Grok
10
+ * headless mode is **one-shot per turn**: `-p` runs one prompt to completion,
11
+ * streams newline-delimited JSON, and exits. Each `send()` spawns a fresh
12
+ * provider process; sends are **serialized** so a second message can't race a
13
+ * running turn. Continuity is `-r <sessionId>` — captured from the turn-end
14
+ * event (per-agent isolation, since each session tracks its own id).
15
+ *
16
+ * Stream-json vocabulary (`--output-format streaming-json`, verified):
17
+ * {"type":"thought","data":"…"} → skipped (internal reasoning)
18
+ * {"type":"text","data":"…"} → message_delta (streamed)
19
+ * {"type":"tool_call"|"tool_call_update",…} → safely skipped (no cards yet)
20
+ * {"type":"usage",…} → skipped (per model call)
21
+ * {"type":"available_commands",…} → skipped
22
+ * {"type":"end","stopReason","sessionId",…} → message_complete + turn usage/end
23
+ * Grok 1.0.67 surfaces tool calls, but tool cards are deliberately outside
24
+ * this adapter slice. The final `end.usage` aggregates the invocation.
25
+ *
26
+ * AUTH is the operator's Grok subscription: `~/.grok/{auth.json, config.toml}`
27
+ * is docker-cp'd into the container at task-up (the Codex/Kimi config-dir
28
+ * pattern; the macOS `bin/` is NOT copied — the image supplies the Linux
29
+ * binary). auth.json is an OIDC token with a refresh_token, so the CLI renews
30
+ * it in-container. Cloud never sees it (ADR-015). `--rules` appends the channel briefing to
31
+ * Grok's default prompt (and retains the account's cloud user rules). The
32
+ * override/alias flags are ignored in 1.0.70 headless mode. Rules persist on
33
+ * resume; a changed briefing requires a fresh Grok session.
34
+ */
35
+ import { existsSync } from "node:fs";
36
+ import { homedir } from "node:os";
37
+ import { join } from "node:path";
38
+
39
+ import { requireContainerRuntimeOperational } from "../runtime-guard";
40
+ import type {
41
+ TaskEnvironmentAgentSessionSurface,
42
+ TaskEnvironmentSessionRequest,
43
+ } from "../task-environment/types";
44
+ import {
45
+ AGENT_SESSION_OUTPUT_BUFFER_BYTES,
46
+ spawnEnvironmentProcess,
47
+ type AgentEnvironmentProcess,
48
+ } from "./proc";
49
+ import { rateLimitErrorUsage } from "./rate-limit";
50
+ import { register } from "./registry";
51
+ import { GROK_MODELS } from "../../src/engine-models";
52
+ import type {
53
+ AgentEvent,
54
+ AgentEventHandler,
55
+ AgentKind,
56
+ AgentSession,
57
+ AgentUsage,
58
+ RosterAgent,
59
+ } from "./types";
60
+
61
+ const GROK_BIN = "/home/node/.grok/bin/grok";
62
+ export const GROK_LOADER_LOGIN_ENV = "0";
63
+
64
+ // Static fallback catalog verified against `grok models`; keep the shared list
65
+ // in host-agent/src/engine-models.ts (GROK_MODELS) current with the pinned CLI.
66
+ const GROK_DEFAULT_MODEL = "grok-4.7";
67
+ // Grok has no per-invocation reasoning-effort flag.
68
+ const GROK_EFFORTS: string[] = [];
69
+
70
+ function grokAuthPath(): string {
71
+ return join(process.env.UAI_OWNER_HOME?.trim() || homedir(), ".grok", "auth.json");
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Pure protocol mapping — one streaming-json line → deltas / end.
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export interface MappedGrokLine {
79
+ /** A streamed text chunk (assistant answer), if this line carried one. */
80
+ textDelta?: string;
81
+ /** True on the turn-end event. */
82
+ end?: boolean;
83
+ /** The session id from the end event, for `-r` on the next turn. */
84
+ sessionId?: string;
85
+ /** Per-invocation usage from the final end event. */
86
+ usage?: AgentUsage;
87
+ }
88
+
89
+ function nonNegativeInt(value: unknown): number | undefined {
90
+ return typeof value === "number" &&
91
+ Number.isSafeInteger(value) &&
92
+ value >= 0
93
+ ? value
94
+ : undefined;
95
+ }
96
+
97
+ function grokEndUsage(
98
+ msg: Record<string, unknown>,
99
+ model: string | undefined,
100
+ ): AgentUsage | undefined {
101
+ if (typeof msg.usage !== "object" || msg.usage === null) return undefined;
102
+ const raw = msg.usage as Record<string, unknown>;
103
+ const inputTokens = nonNegativeInt(raw.input_tokens);
104
+ const cacheReadTokens = nonNegativeInt(raw.cache_read_input_tokens);
105
+ const cacheCreateTokens = nonNegativeInt(raw.cache_creation_input_tokens);
106
+ const outputTokens = nonNegativeInt(raw.output_tokens);
107
+ const reasoningTokens = nonNegativeInt(raw.reasoning_tokens);
108
+ if (
109
+ inputTokens === undefined ||
110
+ cacheReadTokens === undefined ||
111
+ cacheCreateTokens === undefined ||
112
+ outputTokens === undefined ||
113
+ reasoningTokens === undefined
114
+ ) {
115
+ return undefined;
116
+ }
117
+ const reportedCost = msg.total_cost_usd;
118
+ const costUsd =
119
+ typeof reportedCost === "number" &&
120
+ Number.isFinite(reportedCost) &&
121
+ reportedCost >= 0
122
+ ? reportedCost
123
+ : undefined;
124
+ return {
125
+ // `modelUsage` exposes the backend build id (for example
126
+ // grok-4.5-build), while `model` is the semantic id passed to `-m` and
127
+ // therefore the id Uai's model guide resolves.
128
+ ...(model ? { model } : {}),
129
+ // Headless input_tokens is already uncached; both cache buckets are
130
+ // additive according to the 1.0.67 headless contract.
131
+ inputTokens,
132
+ cacheReadTokens,
133
+ cacheCreateTokens,
134
+ // The documented total excludes reasoning_tokens even though it includes
135
+ // output_tokens. Uai has no separate reasoning column, so include that
136
+ // distinct billed output once here.
137
+ outputTokens: outputTokens + reasoningTokens,
138
+ // Missing cost means unreported/partial, never free.
139
+ ...(costUsd === undefined ? {} : { costUsd }),
140
+ };
141
+ }
142
+
143
+ export function mapGrokLine(
144
+ line: string,
145
+ model?: string,
146
+ ): MappedGrokLine {
147
+ const trimmed = line.trim();
148
+ if (!trimmed.startsWith("{")) return {};
149
+ let msg: Record<string, unknown>;
150
+ try {
151
+ msg = JSON.parse(trimmed) as Record<string, unknown>;
152
+ } catch {
153
+ return {};
154
+ }
155
+ if (msg.type === "text" && typeof msg.data === "string") {
156
+ return { textDelta: msg.data };
157
+ }
158
+ if (msg.type === "end") {
159
+ const usage = grokEndUsage(msg, model);
160
+ return {
161
+ end: true,
162
+ sessionId: typeof msg.sessionId === "string" ? msg.sessionId : undefined,
163
+ ...(usage ? { usage } : {}),
164
+ };
165
+ }
166
+ // "thought" (reasoning) and any other types: no user-facing event.
167
+ return {};
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // Session
172
+ // ---------------------------------------------------------------------------
173
+
174
+ export type Spawner = (
175
+ request: TaskEnvironmentSessionRequest,
176
+ ) => AgentEnvironmentProcess;
177
+
178
+ export class GrokSession implements AgentSession {
179
+ readonly agentId: string;
180
+ readonly kind: AgentKind = "grok";
181
+
182
+ private readonly environment: TaskEnvironmentAgentSessionSurface;
183
+ private readonly model?: string;
184
+ private readonly systemPreamble: string;
185
+ private readonly agentEnv: Record<string, string>;
186
+ private readonly spawner: Spawner;
187
+
188
+ private readonly handlers = new Set<AgentEventHandler>();
189
+ private sessionId: string | null = null;
190
+ private current: AgentEnvironmentProcess | null = null;
191
+ private queue: Promise<void> = Promise.resolve();
192
+ private closed = false;
193
+
194
+ constructor(args: {
195
+ taskId: string;
196
+ agent: RosterAgent;
197
+ environment: TaskEnvironmentAgentSessionSurface;
198
+ systemPreamble: string;
199
+ agentEnv?: Record<string, string>;
200
+ spawner?: Spawner;
201
+ }) {
202
+ this.agentId = args.agent.id;
203
+ this.environment = args.environment;
204
+ this.model = args.agent.model;
205
+ this.systemPreamble = args.systemPreamble;
206
+ this.agentEnv = args.agentEnv ?? {};
207
+ this.spawner =
208
+ args.spawner ?? ((request) => spawnEnvironmentProcess(this.environment, request));
209
+ }
210
+
211
+ onEvent(handler: AgentEventHandler): () => void {
212
+ this.handlers.add(handler);
213
+ return () => this.handlers.delete(handler);
214
+ }
215
+
216
+ private emit(event: AgentEvent): void {
217
+ if (this.closed && event.type !== "exit") return;
218
+ for (const h of this.handlers) h(event);
219
+ }
220
+
221
+ async send(text: string): Promise<void> {
222
+ if (this.closed) return;
223
+ const turn = this.queue.catch(() => {}).then(() => this.runTurn(text));
224
+ this.queue = turn;
225
+ return turn;
226
+ }
227
+
228
+ private buildRequest(prompt: string): TaskEnvironmentSessionRequest {
229
+ // Host-resident API key (pasted in the engines panel, ADR-015): forward
230
+ // name-only — docker takes the value from the client env, keeping the
231
+ // secret out of argv. Subscription logins need nothing here (task-up
232
+ // copies ~/.grok into the container instead).
233
+ const inheritEnv = process.env.XAI_API_KEY ? ["XAI_API_KEY"] : [];
234
+ const argv = [
235
+ GROK_BIN,
236
+ "-p",
237
+ prompt,
238
+ "--output-format",
239
+ "streaming-json",
240
+ // The container is the isolation boundary — auto-run tools.
241
+ "--permission-mode",
242
+ "bypassPermissions",
243
+ ];
244
+ if (this.model) argv.push("-m", this.model);
245
+ if (this.systemPreamble.trim().length > 0) {
246
+ // Verified on the shipped 1.0.70: headless ignores both system-prompt
247
+ // override flags, but --rules reaches the model whole and persists on -r.
248
+ argv.push("--rules", this.systemPreamble);
249
+ }
250
+ // Resume the same conversation across turns (captured from the last end).
251
+ if (this.sessionId) argv.push("-r", this.sessionId);
252
+ return {
253
+ argv: argv as [string, ...string[]],
254
+ projectEnvProfile: "task",
255
+ user: "node",
256
+ inheritEnv,
257
+ env: {
258
+ ...this.agentEnv,
259
+ // Grok gates MCP/LSP servers (even user-scope ones) behind folder
260
+ // trust, and /workspace is never trusted: it is not a git repo and
261
+ // headless runs have no prompt. `grok mcp doctor` on a task machine:
262
+ // "folder untrusted … not started for an untrusted folder"; with this
263
+ // off, "2 healthy". The container/machine IS the isolation boundary —
264
+ // the same reasoning as --permission-mode bypassPermissions above
265
+ // (live 2026-09-08, first Grok agents with MCP servers).
266
+ GROK_FOLDER_TRUST: "0",
267
+ // Grok 1.0.67 still enables login-shell capture by default and honors
268
+ // GROK_LOGIN_ENV. That capture reuses static state for
269
+ // LocalNonPersistent commands; loader tasks refresh per command, so
270
+ // keep the stale snapshot path disabled.
271
+ ...(this.environment.descriptor.envMode === "loader"
272
+ ? { GROK_LOGIN_ENV: GROK_LOADER_LOGIN_ENV }
273
+ : {}),
274
+ },
275
+ maxOutputBytes: AGENT_SESSION_OUTPUT_BUFFER_BYTES,
276
+ };
277
+ }
278
+
279
+ private async runTurn(text: string): Promise<void> {
280
+ if (this.closed) return;
281
+ requireContainerRuntimeOperational();
282
+ const child = this.spawner(this.buildRequest(text));
283
+ this.current = child;
284
+
285
+ let acc = "";
286
+ let sawText = false;
287
+ let turnUsage: AgentUsage | undefined;
288
+ let buf = "";
289
+ const consume = (chunk: string): void => {
290
+ buf += chunk;
291
+ let nl: number;
292
+ while ((nl = buf.indexOf("\n")) >= 0) {
293
+ const line = buf.slice(0, nl);
294
+ buf = buf.slice(nl + 1);
295
+ const m = mapGrokLine(line, this.model);
296
+ if (m.sessionId) this.sessionId = m.sessionId;
297
+ if (m.end) turnUsage = m.usage;
298
+ if (typeof m.textDelta === "string") {
299
+ sawText = true;
300
+ acc += m.textDelta;
301
+ this.emit({ type: "message_delta", text: m.textDelta });
302
+ }
303
+ }
304
+ };
305
+ child.stdout?.on("data", (b: Buffer) => consume(b.toString("utf8")));
306
+ let stderr = "";
307
+ child.stderr?.on("data", (b: Buffer) => {
308
+ stderr += b.toString("utf8");
309
+ });
310
+
311
+ await new Promise<void>((resolve) => {
312
+ const finish = (code: number | null, spawnErr?: string): void => {
313
+ this.current = null;
314
+ if (buf.trim().length > 0) consume("\n"); // flush trailing line
315
+ if (this.closed) return resolve();
316
+ if (spawnErr) {
317
+ this.emit({ type: "error", message: `grok spawn failed: ${spawnErr}` });
318
+ } else if (code !== 0) {
319
+ const tail = stderr.trim().slice(-500);
320
+ const message = `grok exited ${code ?? "null"}${tail ? `: ${tail}` : ""}`;
321
+ // Same path as Claude/Codex/OpenCode: a usage-cap / 429 error emits
322
+ // account_usage (source error-text) so the orchestrator can rotate.
323
+ const accountUsage = rateLimitErrorUsage(message);
324
+ if (accountUsage) {
325
+ this.emit({ type: "account_usage", usage: accountUsage });
326
+ this.emit({
327
+ type: "error",
328
+ message,
329
+ retryable: "rate_limit",
330
+ ...(accountUsage.resetsAt === undefined
331
+ ? {}
332
+ : { resetsAt: accountUsage.resetsAt }),
333
+ });
334
+ } else {
335
+ this.emit({ type: "error", message });
336
+ }
337
+ }
338
+ // Finalize the streamed message (no-op text if the turn produced none).
339
+ if (sawText) this.emit({ type: "message_complete", text: acc });
340
+ this.emit(
341
+ turnUsage
342
+ ? { type: "turn_complete", usage: turnUsage }
343
+ : { type: "turn_complete" },
344
+ );
345
+ resolve();
346
+ };
347
+ child.on("exit", (code) => finish(code));
348
+ child.on("error", (err) => finish(null, err.message));
349
+ });
350
+ }
351
+
352
+ async interrupt(): Promise<void> {
353
+ this.current?.kill("SIGKILL");
354
+ }
355
+
356
+ // bypassPermissions auto-approves; no permission requests are emitted.
357
+ async resolvePermission(): Promise<void> {
358
+ /* no-op */
359
+ }
360
+
361
+ async close(): Promise<void> {
362
+ if (this.closed) return;
363
+ this.closed = true;
364
+ this.current?.kill("SIGKILL");
365
+ this.current = null;
366
+ this.emit({ type: "exit", code: 0 });
367
+ this.handlers.clear();
368
+ }
369
+ }
370
+
371
+ register({
372
+ kind: "grok",
373
+ label: "Grok",
374
+ supportedModels: () => [...GROK_MODELS],
375
+ defaultModel: GROK_DEFAULT_MODEL,
376
+ supportedEfforts: () => [...GROK_EFFORTS],
377
+ available: () =>
378
+ existsSync(grokAuthPath()) || Boolean(process.env.XAI_API_KEY),
379
+ create: async ({ taskId, agent, environment, systemPreamble, agentEnv }) =>
380
+ new GrokSession({ taskId, agent, environment, systemPreamble, agentEnv }),
381
+ });