@moondeck/station 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/uai-host.mjs +130 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/0005_host_env_vars.sql +5 -0
- package/db/migrations/0006_host_project_env.sql +9 -0
- package/db/migrations/0007_host_github_token_kind.sql +7 -0
- package/db/migrations/0008_host_mcp_connections.sql +18 -0
- package/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/0010_host_engine_accounts.sql +13 -0
- package/db/migrations/0011_codex_thread_id.sql +5 -0
- package/db/migrations/0012_agent_session_attach_key.sql +4 -0
- package/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/0017_engine_account_scope.sql +7 -0
- package/db/migrations/0018_engine_account_position.sql +3 -0
- package/db/migrations/0019_engine_account_store_ref.sql +3 -0
- package/db/migrations/0020_user_credential_store_ref.sql +6 -0
- package/db/migrations/0021_project_env_store_ref.sql +3 -0
- package/db/migrations/0022_task_project_ids.sql +5 -0
- package/db/migrations/0023_project_env_org.sql +4 -0
- package/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/0029_task_shared_files_mode.sql +5 -0
- package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
- package/db/migrations/meta/_journal.json +223 -0
- package/db/schema.ts +850 -0
- package/images/standard/Dockerfile +347 -0
- package/images/standard/README.md +167 -0
- package/images/standard/container/code-server-settings.json +63 -0
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +704 -0
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/images/standard/tool-versions +2 -0
- package/lib/__fixtures__/skill-index/SKILL.md +10 -0
- package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
- package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
- package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
- package/lib/agent-cli.ts +1507 -0
- package/lib/agent-errors.ts +22 -0
- package/lib/agent.ts +672 -0
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/claude.ts +918 -0
- package/lib/agents/codex-token-usage.fixture.json +130 -0
- package/lib/agents/codex.ts +1392 -0
- package/lib/agents/cursor-period-usage.ts +253 -0
- package/lib/agents/cursor.ts +562 -0
- package/lib/agents/dispatch.ts +78 -0
- package/lib/agents/durable-proc.ts +334 -0
- package/lib/agents/factory.ts +57 -0
- package/lib/agents/grok.ts +381 -0
- package/lib/agents/kimi.ts +329 -0
- package/lib/agents/machine-durable.ts +604 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/mode.ts +64 -0
- package/lib/agents/opencode.ts +508 -0
- package/lib/agents/output-source.ts +3 -0
- package/lib/agents/proc.ts +221 -0
- package/lib/agents/rate-limit.ts +337 -0
- package/lib/agents/registry.ts +182 -0
- package/lib/agents/session-state.ts +197 -0
- package/lib/agents/skill-index-preamble.fixture.ts +26 -0
- package/lib/agents/transport.ts +741 -0
- package/lib/agents/types.ts +254 -0
- package/lib/agents/uai.ts +106 -0
- package/lib/agents/usage.ts +44 -0
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +237 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/assignment-evidence.ts +94 -0
- package/lib/attachments.ts +48 -0
- package/lib/browser-testing.ts +1537 -0
- package/lib/capture-flow.ts +332 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2587 -0
- package/lib/cloud-credential-runtime.ts +1357 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/codex-auth.ts +456 -0
- package/lib/command-db.ts +310 -0
- package/lib/compose-network-recovery.ts +283 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/container-runtime.ts +1453 -0
- package/lib/credential-cleanup-inventory.ts +490 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +797 -0
- package/lib/credential-consumers/files.ts +934 -0
- package/lib/credential-consumers/github.ts +702 -0
- package/lib/credential-consumers/mcp.ts +389 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-consumers/task-secrets.ts +10 -0
- package/lib/credential-store-pickup.ts +149 -0
- package/lib/credential-store-scope.ts +112 -0
- package/lib/credential-store.ts +932 -0
- package/lib/credentials-activation-authority.ts +709 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +384 -0
- package/lib/custom-connection-runtime.ts +141 -0
- package/lib/db.ts +80 -0
- package/lib/disk-pressure.ts +42 -0
- package/lib/docker-exec.ts +156 -0
- package/lib/engine-accounts.ts +1970 -0
- package/lib/engine-connect.ts +321 -0
- package/lib/engine-credential-env.ts +52 -0
- package/lib/engine-login.ts +2933 -0
- package/lib/engines.ts +1342 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +246 -0
- package/lib/env-schema.ts +891 -0
- package/lib/env.ts +195 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +530 -0
- package/lib/git-identity.ts +715 -0
- package/lib/github-git-auth.ts +207 -0
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +2439 -0
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-env.ts +424 -0
- package/lib/host-logs.ts +965 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/launch-mode.ts +25 -0
- package/lib/lease.ts +239 -0
- package/lib/legacy-host.ts +769 -0
- package/lib/legacy-runtime-retirement.ts +424 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/machine-exec.ts +238 -0
- package/lib/machine-gateway-tunnel.ts +344 -0
- package/lib/machine-keys.ts +65 -0
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/machine-provider-aws.ts +923 -0
- package/lib/machine-provider-local.ts +338 -0
- package/lib/machine-provider.ts +90 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +57 -0
- package/lib/managed-runtime.ts +4296 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-capture.ts +865 -0
- package/lib/mcp-config-lock.ts +2 -0
- package/lib/mcp-connections.ts +959 -0
- package/lib/mcp-gateway.ts +2594 -0
- package/lib/obs.ts +512 -0
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +11409 -0
- package/lib/person-capture.ts +327 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/preview-sidecar.ts +331 -0
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-loader.ts +242 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/release-feed.ts +61 -0
- package/lib/release-manifest.ts +710 -0
- package/lib/release-trust.ts +46 -0
- package/lib/repo-clone.ts +38 -0
- package/lib/routing/shadow.ts +261 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +279 -0
- package/lib/runtime-archive.ts +1216 -0
- package/lib/runtime-authority.ts +166 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +1033 -0
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/address.ts +95 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +57 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +150 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +605 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/secrets.ts +71 -0
- package/lib/shared-files-s3.ts +976 -0
- package/lib/shared-files.ts +156 -0
- package/lib/sidecar-assign.ts +49 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +178 -0
- package/lib/sidecar.ts +69 -0
- package/lib/signin-box-credentials.ts +465 -0
- package/lib/signin-box.ts +114 -0
- package/lib/skill-index-example.fixture.ts +56 -0
- package/lib/skill-layout.ts +417 -0
- package/lib/skills.ts +489 -0
- package/lib/ssh.ts +401 -0
- package/lib/standard-image-cleanup.ts +117 -0
- package/lib/standard-image-pin.ts +207 -0
- package/lib/standard-image.ts +1697 -0
- package/lib/stopped-task-status-queue.ts +120 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-credential-cleanup.ts +752 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +91 -0
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +249 -0
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +860 -0
- package/lib/task-environment/docker.ts +1344 -0
- package/lib/task-environment/index.ts +466 -0
- package/lib/task-environment/legacy-adoption.ts +467 -0
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +1227 -0
- package/lib/task-environment/machine.ts +974 -0
- package/lib/task-environment/registry.ts +59 -0
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +566 -0
- package/lib/task-environment/types.ts +642 -0
- package/lib/task-environment/workspace-files.ts +685 -0
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-files-machine-retirement-outbox.ts +592 -0
- package/lib/task-files-machine-retirement.ts +116 -0
- package/lib/task-files-readiness.ts +127 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +47 -0
- package/lib/task-gateway-state.ts +512 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +682 -0
- package/lib/task-machine-credentials-activation.ts +95 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/task-person-credentials.ts +76 -0
- package/lib/task-project-ops-host.ts +147 -0
- package/lib/task-project-ops.ts +1062 -0
- package/lib/task-secret-staging.ts +16 -0
- package/lib/task-status.ts +46 -0
- package/lib/task-token.ts +46 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +105 -0
- package/lib/token-broker-local.ts +392 -0
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +525 -0
- package/lib/token-broker.ts +218 -0
- package/lib/token-lanes/openai-oauth.ts +123 -0
- package/lib/token-lanes/registry.ts +45 -0
- package/lib/token-lanes/types.ts +163 -0
- package/lib/token-lanes/xai-oauth.ts +165 -0
- package/lib/transcript.ts +128 -0
- package/lib/tunnel-registry.ts +220 -0
- package/lib/tunnel-runtime.ts +235 -0
- package/lib/ulid.ts +16 -0
- package/lib/user-bus-env.ts +116 -0
- package/lib/user-credentials-store.ts +435 -0
- package/lib/vnode-pressure.ts +142 -0
- package/package.json +131 -4
- package/runner/runner.mjs +442 -0
- package/scripts/agent/_common.sh +633 -0
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/moondeck-cli.sh +5 -0
- package/scripts/agent/task-down.sh +291 -0
- package/scripts/agent/task-status.sh +70 -0
- package/scripts/agent/task-up.sh +3761 -0
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/scripts/install/darwin.ts +1048 -0
- package/scripts/install/linux.ts +958 -0
- package/scripts/install/types.ts +78 -0
- package/scripts/install/util.ts +254 -0
- package/scripts/install/win.ts +143 -0
- package/src/activation-respawn.ts +155 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2583 -0
- package/src/cloud-credentials.ts +2144 -0
- package/src/cloud-origins.ts +23 -0
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +237 -0
- package/src/engine-account-paths.ts +11 -0
- package/src/engine-models.ts +39 -0
- package/src/engine-usage.ts +104 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2899 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6349 -0
- package/src/never-proxied-domains.ts +31 -0
- package/src/outbound-frame.ts +31 -0
- package/src/paths.ts +81 -0
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +396 -0
- package/src/project-env-wording.ts +53 -0
- package/src/protocol.ts +5545 -0
- package/src/runtime-bootstrap.ts +165 -0
- package/src/station-identity.ts +96 -0
- package/src/task-env-names.ts +28 -0
- package/src/task-secrets.ts +75 -0
- package/src/tunnel-dial.ts +102 -0
- package/src/ui/server.ts +852 -0
- package/src/ui/types.ts +173 -0
- package/src/workspace-path-policy.ts +41 -0
- package/ui/app.js +1102 -0
- package/ui/index.html +90 -0
- package/ui/style.css +685 -0
- package/ui/uai-favicon.svg +21 -0
- package/ui/uai-logo-black.svg +9 -0
- package/ui/uai-wheel.svg +9 -0
|
@@ -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
|
+
});
|