Compare commits
4
Commits
569551fe9c
...
a6bef210e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6bef210e9 | ||
|
|
36f10f1256 | ||
|
|
95ba918629 | ||
|
|
dd9b41e72f |
@@ -0,0 +1,154 @@
|
||||
# Native release builds of the patched grok binary.
|
||||
#
|
||||
# Matrix is host=target (no cross): Windows/Linux amd64 and macOS arm64.
|
||||
# work/ is derived the same way as `make apply`, but the upstream checkout is
|
||||
# a depth-1 fetch of the pinned SHA so CI does not clone full grok-build history.
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUST_BACKTRACE: "1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.artifact }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 180
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact: grok-linux-amd64
|
||||
exe: xai-grok-pager
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact: grok-windows-amd64
|
||||
exe: xai-grok-pager.exe
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
artifact: grok-macos-arm64
|
||||
exe: xai-grok-pager
|
||||
|
||||
steps:
|
||||
- name: Disable CRLF conversion
|
||||
run: git config --global core.autocrlf false
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@1.94.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
version: "29.3"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Apply patches onto pinned upstream
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REV="$(grep -vE '^\s*(#|$)' upstream.rev | head -n1 | tr -d '[:space:]')"
|
||||
[[ -n "$REV" ]] || { echo "upstream.rev has no revision" >&2; exit 1; }
|
||||
|
||||
git init work
|
||||
git -C work remote add origin https://github.com/xai-org/grok-build.git
|
||||
git -C work fetch --depth 1 origin "$REV"
|
||||
git -C work checkout --force --detach FETCH_HEAD
|
||||
git -C work checkout -B fork
|
||||
git -C work tag -f base
|
||||
git -C work config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git -C work config user.name "github-actions[bot]"
|
||||
|
||||
shopt -s nullglob
|
||||
patches=("$GITHUB_WORKSPACE"/patches/*.patch)
|
||||
if ((${#patches[@]})); then
|
||||
git -C work am --3way --keep-cr --whitespace=nowarn "${patches[@]}"
|
||||
fi
|
||||
echo "work/ ready: upstream ${REV:0:12} + ${#patches[@]} patch(es)"
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: work
|
||||
key: ${{ matrix.target }}
|
||||
cache-targets: false
|
||||
|
||||
- name: Release build
|
||||
working-directory: work
|
||||
run: cargo build --release --locked -p xai-grok-pager-bin --target ${{ matrix.target }}
|
||||
|
||||
- name: Package
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
ARTIFACT: ${{ matrix.artifact }}
|
||||
EXE: ${{ matrix.exe }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
src="work/target/${TARGET}/release/${EXE}"
|
||||
[[ -f "$src" ]] || { echo "missing $src" >&2; ls -la "work/target/${TARGET}/release" >&2; exit 1; }
|
||||
|
||||
mkdir -p dist
|
||||
if [[ "$EXE" == *.exe ]]; then
|
||||
dest="dist/${ARTIFACT}.exe"
|
||||
else
|
||||
dest="dist/${ARTIFACT}"
|
||||
strip "$src" || true
|
||||
fi
|
||||
cp "$src" "$dest"
|
||||
|
||||
{
|
||||
echo "artifact=$(basename "$dest")"
|
||||
echo "target=${TARGET}"
|
||||
echo "git=${GITHUB_SHA}"
|
||||
echo "upstream=$(grep -vE '^\s*(#|$)' upstream.rev | head -n1 | tr -d '[:space:]')"
|
||||
echo "rustc=$(rustc --version)"
|
||||
} > "dist/${ARTIFACT}.txt"
|
||||
|
||||
if command -v sha256sum >/dev/null; then
|
||||
(cd dist && sha256sum "$(basename "$dest")" "${ARTIFACT}.txt" > "${ARTIFACT}.sha256")
|
||||
else
|
||||
(cd dist && shasum -a 256 "$(basename "$dest")" "${ARTIFACT}.txt" > "${ARTIFACT}.sha256")
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist/*
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: dist/*
|
||||
generate_release_notes: true
|
||||
fail_on_unmatched_files: true
|
||||
@@ -0,0 +1,108 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: iceBear67 <icebear67@sfclub.cc>
|
||||
Date: Sat, 15 Aug 2026 06:39:14 +0000
|
||||
Subject: [PATCH] Add [remote_control] config section for the /rc glance bridge
|
||||
|
||||
|
||||
diff --git a/crates/codegen/xai-grok-shell/src/agent/config.rs b/crates/codegen/xai-grok-shell/src/agent/config.rs
|
||||
index 3dd50b3217936911d6858e4c9c169d7609e1a0fd..f39d1bd460a4635dd4767afe92622ab9b04952a1 100644
|
||||
--- a/crates/codegen/xai-grok-shell/src/agent/config.rs
|
||||
+++ b/crates/codegen/xai-grok-shell/src/agent/config.rs
|
||||
@@ -1014,6 +1014,79 @@ pub struct CliConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub session_picker_grouped: Option<bool>,
|
||||
}
|
||||
+/// `[remote_control]` section: the `/rc` bridge to a grok-glance server.
|
||||
+///
|
||||
+/// `/rc` mirrors the session you are already in to glance over ACP -- it does not
|
||||
+/// restart it, switch it to headless, or hand it over. Both ends stay live and
|
||||
+/// either can drive the session.
|
||||
+///
|
||||
+/// Every field has an env override, which is how the API key is normally supplied:
|
||||
+/// a bearer token in a plaintext config file is a poor default.
|
||||
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
+#[serde(default)]
|
||||
+pub struct RemoteControlConfig {
|
||||
+ /// Glance WebSocket endpoint, e.g. `wss://glance.example.com/api/acp/agent`.
|
||||
+ /// Env `GROK_RC_URL`.
|
||||
+ #[serde(skip_serializing_if = "Option::is_none")]
|
||||
+ pub url: Option<String>,
|
||||
+ /// Bearer token presented on the WebSocket upgrade. Env `GROK_RC_API_KEY`.
|
||||
+ #[serde(skip_serializing_if = "Option::is_none")]
|
||||
+ pub api_key: Option<String>,
|
||||
+ /// Connect at session start instead of waiting for `/rc`. Env `GROK_RC_AUTO_START`.
|
||||
+ #[serde(skip_serializing_if = "Option::is_none")]
|
||||
+ pub auto_start: Option<bool>,
|
||||
+ /// Session updates retained for replay when glance (re)connects.
|
||||
+ /// See [`RemoteControlConfig::DEFAULT_REPLAY_BUFFER`].
|
||||
+ #[serde(skip_serializing_if = "Option::is_none")]
|
||||
+ pub replay_buffer: Option<usize>,
|
||||
+}
|
||||
+impl RemoteControlConfig {
|
||||
+ /// Bounded so a long session cannot grow the bridge without limit; large
|
||||
+ /// enough that a browser attaching mid-session still sees useful history.
|
||||
+ pub const DEFAULT_REPLAY_BUFFER: usize = 2048;
|
||||
+ /// Env wins over config, and an empty value counts as unset -- an exported
|
||||
+ /// but blank `GROK_RC_URL` should not mask a working config entry.
|
||||
+ fn env_override(key: &str) -> Option<String> {
|
||||
+ std::env::var(key)
|
||||
+ .ok()
|
||||
+ .map(|v| v.trim().to_owned())
|
||||
+ .filter(|v| !v.is_empty())
|
||||
+ }
|
||||
+ pub fn resolved_url(&self) -> Option<String> {
|
||||
+ Self::env_override("GROK_RC_URL").or_else(|| {
|
||||
+ self.url
|
||||
+ .as_deref()
|
||||
+ .map(str::trim)
|
||||
+ .filter(|v| !v.is_empty())
|
||||
+ .map(str::to_owned)
|
||||
+ })
|
||||
+ }
|
||||
+ pub fn resolved_api_key(&self) -> Option<String> {
|
||||
+ Self::env_override("GROK_RC_API_KEY").or_else(|| {
|
||||
+ self.api_key
|
||||
+ .as_deref()
|
||||
+ .map(str::trim)
|
||||
+ .filter(|v| !v.is_empty())
|
||||
+ .map(str::to_owned)
|
||||
+ })
|
||||
+ }
|
||||
+ pub fn auto_start_enabled(&self) -> bool {
|
||||
+ Self::env_override("GROK_RC_AUTO_START")
|
||||
+ .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
|
||||
+ .or(self.auto_start)
|
||||
+ .unwrap_or(false)
|
||||
+ }
|
||||
+ pub fn replay_buffer_len(&self) -> usize {
|
||||
+ self.replay_buffer
|
||||
+ .filter(|n| *n > 0)
|
||||
+ .unwrap_or(Self::DEFAULT_REPLAY_BUFFER)
|
||||
+ }
|
||||
+ /// Both a URL and a key are required; `/rc` reports which one is missing
|
||||
+ /// rather than dialing an endpoint that will reject it.
|
||||
+ pub fn is_configured(&self) -> bool {
|
||||
+ self.resolved_url().is_some() && self.resolved_api_key().is_some()
|
||||
+ }
|
||||
+}
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct DiagnosticsConfig {
|
||||
@@ -1348,6 +1421,9 @@ pub struct Config {
|
||||
pub paths: PathsConfig,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub cli: CliConfig,
|
||||
+ /// `[remote_control]` section: the `/rc` bridge to grok-glance.
|
||||
+ #[serde(default, skip_serializing)]
|
||||
+ pub remote_control: RemoteControlConfig,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub models: ModelsConfig,
|
||||
#[serde(default, skip_serializing)]
|
||||
@@ -1759,6 +1835,7 @@ impl Default for Config {
|
||||
feedback: FeedbackConfig::default(),
|
||||
paths: PathsConfig::default(),
|
||||
cli: CliConfig::default(),
|
||||
+ remote_control: RemoteControlConfig::default(),
|
||||
models: ModelsConfig::default(),
|
||||
harness: HarnessConfig::default(),
|
||||
relay: RelayConfig::default(),
|
||||
File diff suppressed because it is too large
Load Diff
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run a command with its disk-write rate held under a ceiling.
|
||||
#
|
||||
# Cargo on this box can saturate the virtual disk badly enough to take the
|
||||
# whole machine out. The usual levers are unavailable here and it is worth
|
||||
# recording why, so nobody burns an afternoon rediscovering it:
|
||||
#
|
||||
# cgroup v2 io.max -- /sys/fs/cgroup is mounted ro and cannot be remounted
|
||||
# or re-mounted elsewhere; the container has no
|
||||
# CAP_SYS_ADMIN, so this fails even under sudo.
|
||||
# vm.dirty_bytes -- /proc/sys is read-only.
|
||||
# ionice -- the scheduler is mq-deadline, where IO priority is a
|
||||
# no-op, and /sys/block/*/queue/scheduler is read-only
|
||||
# so BFQ cannot be selected.
|
||||
# nice -- CPU only. Writeback happens in kernel flusher threads
|
||||
# that never see the nice value, so a niced build
|
||||
# saturates the disk exactly as fast as an un-niced one.
|
||||
#
|
||||
# What is left is duty-cycling from userspace: sample the block device's
|
||||
# written-sectors counter, and when the observed rate exceeds the ceiling,
|
||||
# SIGSTOP the whole process group until it drops. Crude, but it acts on
|
||||
# measured throughput rather than on a scheduler hint, which is the only
|
||||
# property that actually matters here.
|
||||
#
|
||||
# scripts/paced.sh -- make build
|
||||
# MBPS=30 scripts/paced.sh -- env CARGO_CMD=test PKG=xai-grok-pager ./scripts/build.sh --lib
|
||||
set -euo pipefail
|
||||
|
||||
# Measured on this box, idle, 1 GB transfers:
|
||||
#
|
||||
# read 210 MB/s (O_DIRECT and buffered alike)
|
||||
# write 50 MB/s (identical for buffered+fdatasync, O_DIRECT 1M, O_DIRECT 64k)
|
||||
#
|
||||
# Write is pinned at ~50 MB/s regardless of block size or IO mode, so it is a
|
||||
# volume-level cap rather than disk physics, and it is 4x slower than read.
|
||||
# A short burst can reach ~150 MB/s on what look like burst credits -- do not
|
||||
# calibrate against that, it is the number that made an earlier 40 MB/s ceiling
|
||||
# useless. 15 MB/s is under a third of sustained capacity, which leaves the rest
|
||||
# of the machine usable while a build runs.
|
||||
MBPS="${MBPS:-15}" # write ceiling in MB/s, averaged over one window
|
||||
DEV="${DEV:-vdb}" # block device to watch; the disk, not the partition
|
||||
WINDOW="${WINDOW:-1}" # sampling window in seconds
|
||||
|
||||
[[ "${1:-}" == "--" ]] && shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "usage: [MBPS=60] [DEV=vdb] scripts/paced.sh -- <command...>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
stats_field() {
|
||||
# /proc/diskstats field 10 is sectors written, in 512-byte units.
|
||||
awk -v dev="$DEV" '$3 == dev { print $10; exit }' /proc/diskstats
|
||||
}
|
||||
|
||||
if [[ -z "$(stats_field)" ]]; then
|
||||
echo "paced: no such device in /proc/diskstats: $DEV" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Its own process group, so one signal reaches cargo and every rustc it spawned.
|
||||
#
|
||||
# `set -m` rather than setsid: setsid only forks when it is already a group
|
||||
# leader, so under `cmd &` it usually execs in place -- but when it does fork,
|
||||
# $! is setsid's pid, which exits immediately. The loop below then sees the
|
||||
# child "finish" instantly and reports success while the real build is still
|
||||
# running. Job control gives the background job its own pgid deterministically.
|
||||
set -m
|
||||
"$@" &
|
||||
child=$!
|
||||
pgid=$child
|
||||
set +m
|
||||
|
||||
cleanup() {
|
||||
# Never leave the build stopped: a SIGSTOPped process group that outlives
|
||||
# this script looks exactly like a hang.
|
||||
kill -CONT -- "-$pgid" 2>/dev/null || true
|
||||
kill -TERM -- "-$pgid" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup INT TERM
|
||||
|
||||
ceiling_sectors=$(( MBPS * 1024 * 1024 / 512 * WINDOW ))
|
||||
prev=$(stats_field)
|
||||
stalled=0
|
||||
|
||||
while kill -0 "$child" 2>/dev/null; do
|
||||
sleep "$WINDOW"
|
||||
now=$(stats_field)
|
||||
delta=$(( now - prev ))
|
||||
prev=$now
|
||||
|
||||
if (( delta > ceiling_sectors )); then
|
||||
# Stop for as long as the overshoot implies, capped so the build still
|
||||
# makes progress even when something else on the box is writing hard.
|
||||
pause=$(( delta / ceiling_sectors ))
|
||||
(( pause > 4 )) && pause=4
|
||||
stalled=$(( stalled + pause ))
|
||||
kill -STOP -- "-$pgid" 2>/dev/null || true
|
||||
sleep "$pause"
|
||||
kill -CONT -- "-$pgid" 2>/dev/null || true
|
||||
prev=$(stats_field)
|
||||
fi
|
||||
done
|
||||
|
||||
rc=0
|
||||
wait "$child" || rc=$?
|
||||
trap - INT TERM
|
||||
echo "paced: done rc=$rc (throttled ${stalled}s at ${MBPS}MB/s ceiling on /dev/$DEV)" >&2
|
||||
# Propagate the wrapped command's status. Swallowing it once already produced a
|
||||
# confident "build finished, exit 0" for a build that had in fact been killed.
|
||||
exit "$rc"
|
||||
Reference in New Issue
Block a user