@huynle/hub 0.6.0 → 0.7.1
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/CHANGELOG.md +800 -0
- package/Dockerfile +30 -0
- package/README.md +870 -66
- package/package.json +45 -2
- package/packages/claude-code/package.json +12 -0
- package/packages/claude-code/src/bridge.ts +79 -0
- package/packages/claude-code/src/daemon.ts +339 -0
- package/packages/claude-code/src/hook-input.ts +44 -0
- package/packages/claude-code/src/ids.ts +27 -0
- package/packages/claude-code/src/index.ts +18 -0
- package/packages/claude-code/src/launcher.ts +265 -0
- package/packages/claude-code/src/main.ts +127 -0
- package/packages/claude-code/src/mcp.ts +109 -0
- package/packages/claude-code/src/paths.ts +34 -0
- package/packages/claude-code/src/plugin.ts +99 -0
- package/packages/claude-code/src/references.ts +51 -0
- package/packages/claude-code/src/sessions.ts +600 -0
- package/packages/claude-code/src/tools.ts +66 -0
- package/packages/claude-code/src/wire.ts +71 -0
- package/packages/claude-code/src/wrapper.ts +155 -0
- package/packages/cli/dist/hub-cli.js +10162 -0
- package/packages/codex/package.json +12 -0
- package/packages/codex/src/app-server.ts +44 -0
- package/packages/codex/src/ids.ts +28 -0
- package/packages/codex/src/index.ts +11 -0
- package/packages/codex/src/jsonrpc.ts +168 -0
- package/packages/codex/src/main.ts +122 -0
- package/packages/codex/src/paths.ts +65 -0
- package/packages/codex/src/sessions.ts +578 -0
- package/packages/codex/src/spoke.ts +249 -0
- package/packages/codex/src/stdio.ts +101 -0
- package/packages/codex/src/websocket.ts +206 -0
- package/packages/codex/src/wrapper.ts +100 -0
- package/packages/node/package.json +12 -0
- package/packages/node/src/config.ts +184 -0
- package/packages/node/src/connection.ts +283 -0
- package/packages/node/src/daemon.ts +281 -0
- package/packages/node/src/git.ts +107 -0
- package/packages/node/src/harnesses.ts +60 -0
- package/packages/node/src/host.ts +319 -0
- package/packages/node/src/index.ts +17 -0
- package/packages/node/src/main.ts +11 -0
- package/packages/node/src/openshell.ts +73 -0
- package/packages/node/src/run.ts +106 -0
- package/packages/preset/package.json +12 -0
- package/packages/preset/presets/aider.json +8 -0
- package/packages/preset/presets/goose.json +8 -0
- package/packages/preset/src/index.ts +2 -0
- package/packages/preset/src/main.ts +195 -0
- package/packages/preset/src/presets.ts +87 -0
- package/packages/preset/src/spoke.ts +251 -0
- package/packages/preset/src/tmux.ts +160 -0
- package/packages/protocol/package.json +12 -0
- package/packages/protocol/src/harness-events.ts +207 -0
- package/packages/protocol/src/index.ts +1215 -0
- package/packages/protocol/src/launch.ts +172 -0
- package/packages/protocol/src/node.ts +409 -0
- package/packages/protocol/src/preset.ts +144 -0
- package/packages/protocol/src/webhook.ts +62 -0
- package/packages/protocol/src/wire.ts +54 -0
- package/packages/runtime-openshell/image/Dockerfile +36 -0
- package/packages/runtime-openshell/image/README.md +78 -0
- package/packages/runtime-openshell/image/build.ts +203 -0
- package/packages/runtime-openshell/image/entry.sh +163 -0
- package/packages/runtime-openshell/image/image.ts +119 -0
- package/packages/runtime-openshell/image/start.sh +24 -0
- package/packages/runtime-openshell/package.json +12 -0
- package/packages/runtime-openshell/src/cli.ts +584 -0
- package/packages/runtime-openshell/src/index.ts +6 -0
- package/packages/runtime-openshell/src/policy.ts +155 -0
- package/packages/runtime-openshell/src/profiles.ts +181 -0
- package/packages/runtime-openshell/src/runtime.ts +475 -0
- package/packages/runtime-sdk/package.json +12 -0
- package/packages/runtime-sdk/src/index.ts +87 -0
- package/packages/sdk/package.json +12 -0
- package/packages/sdk/src/address.ts +25 -0
- package/packages/sdk/src/client.ts +502 -0
- package/packages/sdk/src/envelope.ts +108 -0
- package/packages/sdk/src/errors.ts +106 -0
- package/packages/sdk/src/index.ts +10 -0
- package/packages/sdk/src/sse.ts +67 -0
- package/packages/sdk/src/types.ts +458 -0
- package/packages/sdk/src/webhook.ts +102 -0
- package/packages/spoke-sdk/package.json +12 -0
- package/packages/spoke-sdk/src/activity.ts +3 -0
- package/packages/spoke-sdk/src/connection.ts +295 -0
- package/packages/spoke-sdk/src/delivery.ts +119 -0
- package/packages/spoke-sdk/src/enroll.ts +79 -0
- package/packages/spoke-sdk/src/harness-hello.ts +79 -0
- package/packages/spoke-sdk/src/harness-spoke.ts +263 -0
- package/packages/spoke-sdk/src/index.ts +12 -0
- package/packages/spoke-sdk/src/prompt-refs.ts +154 -0
- package/packages/spoke-sdk/src/prompt-source.ts +8 -0
- package/packages/spoke-sdk/src/session-ids.ts +49 -0
- package/packages/spoke-sdk/src/session-tokens.ts +78 -0
- package/scripts/hub-claude-code.service +34 -0
- package/scripts/hub-codex-spoke.service +34 -0
- package/scripts/hub-node.service +35 -0
- package/scripts/io.github.huynle.hub-claude-code.plist +53 -0
- package/scripts/io.github.huynle.hub-codex-spoke.plist +53 -0
- package/scripts/io.github.huynle.hub-node.plist +52 -0
- package/src/central/actions.ts +233 -0
- package/src/central/agent/acting-routes.ts +133 -0
- package/src/central/agent/acting-switch.ts +93 -0
- package/src/central/agent/acting.ts +599 -0
- package/src/central/agent/actor.ts +193 -0
- package/src/central/agent/app-tokens.ts +142 -0
- package/src/central/agent/audit.ts +185 -0
- package/src/central/agent/decisions.ts +530 -0
- package/src/central/agent/delivery.ts +678 -0
- package/src/central/agent/directory-routes.ts +185 -0
- package/src/central/agent/directory.ts +473 -0
- package/src/central/agent/envelope.ts +51 -0
- package/src/central/agent/errors.ts +72 -0
- package/src/central/agent/escalation.ts +219 -0
- package/src/central/agent/grant-requests.ts +221 -0
- package/src/central/agent/grant-routes.ts +70 -0
- package/src/central/agent/grants.ts +235 -0
- package/src/central/agent/launch-engine.ts +1025 -0
- package/src/central/agent/launch-routes.ts +252 -0
- package/src/central/agent/launch-spec.ts +289 -0
- package/src/central/agent/launch-store.ts +264 -0
- package/src/central/agent/launch-tokens.ts +206 -0
- package/src/central/agent/message-limits.ts +123 -0
- package/src/central/agent/message-routes.ts +385 -0
- package/src/central/agent/messages.ts +442 -0
- package/src/central/agent/names.ts +86 -0
- package/src/central/agent/placement.ts +419 -0
- package/src/central/agent/policy.ts +378 -0
- package/src/central/agent/read-routes.ts +389 -0
- package/src/central/agent/routes.ts +278 -0
- package/src/central/agent/session-tokens.ts +227 -0
- package/src/central/agent/versioned.ts +26 -0
- package/src/central/agent/watch.ts +221 -0
- package/src/central/agent/webhook-routes.ts +78 -0
- package/src/central/agent/webhooks.ts +449 -0
- package/src/central/agent-data.ts +177 -0
- package/src/central/auth.ts +224 -121
- package/src/central/backend/file-state.ts +240 -0
- package/src/central/backend/index.ts +113 -0
- package/src/central/backend/memory-bus.ts +93 -0
- package/src/central/backend/memory-catalog.ts +90 -0
- package/src/central/backend/memory-counters.ts +102 -0
- package/src/central/backend/memory-events.ts +45 -0
- package/src/central/backend/memory-kv.ts +38 -0
- package/src/central/backend/memory-state.ts +77 -0
- package/src/central/backend/postgres-migrate.ts +100 -0
- package/src/central/backend/postgres-state.ts +197 -0
- package/src/central/backend/types.ts +244 -0
- package/src/central/backend/valkey-bus.ts +425 -0
- package/src/central/backend/valkey-catalog.ts +190 -0
- package/src/central/backend/valkey-client.ts +36 -0
- package/src/central/backend/valkey-counters.ts +38 -0
- package/src/central/backend/valkey-events.ts +168 -0
- package/src/central/backend/valkey-kv.ts +54 -0
- package/src/central/backend/valkey-locks.ts +92 -0
- package/src/central/cluster/directory.ts +141 -0
- package/src/central/cluster/feed.ts +84 -0
- package/src/central/cluster/leader.ts +232 -0
- package/src/central/cluster/live.ts +151 -0
- package/src/central/cluster/recommend.ts +59 -0
- package/src/central/cluster/rpc.ts +150 -0
- package/src/central/cluster/view.ts +232 -0
- package/src/central/config.ts +197 -3
- package/src/central/done.ts +96 -0
- package/src/central/events-log.ts +100 -0
- package/src/central/http.ts +1067 -171
- package/src/central/import.ts +318 -0
- package/src/central/main.ts +211 -12
- package/src/central/nodes.ts +314 -0
- package/src/central/oidc.ts +34 -14
- package/src/central/push-target.ts +118 -18
- package/src/central/push.ts +256 -82
- package/src/central/registry.ts +902 -92
- package/src/central/say-stream.ts +128 -0
- package/src/central/session-token-push.ts +233 -0
- package/src/central/speech/openai.ts +21 -14
- package/src/central/speech/openrouter.ts +27 -10
- package/src/central/speech/provider.ts +54 -0
- package/src/central/speech/service.ts +22 -7
- package/src/central/spoke-ws.ts +522 -44
- package/src/central/store-shared.ts +518 -0
- package/src/central/store.ts +572 -189
- package/src/central/token-keys.ts +165 -0
- package/src/config.ts +49 -1
- package/src/index.ts +165 -12
- package/src/protocol.ts +3 -650
- package/src/rpc.ts +27 -2
- package/src/spoke/client.ts +3 -272
- package/src/spoke/conversation.ts +16 -12
- package/src/spoke/deliver.ts +75 -0
- package/src/spoke/enroll.ts +2 -79
- package/src/spoke/env-inject.ts +160 -0
- package/src/spoke/hub-rpc.ts +115 -0
- package/src/spoke/hub-tool.ts +409 -0
- package/src/spoke/launch-source.ts +22 -0
- package/src/spoke/launchers.ts +68 -0
- package/src/spoke/locations.ts +3 -2
- package/src/spoke/node-roots.ts +61 -0
- package/src/spoke/prompt-refs.ts +116 -0
- package/src/spoke/session-tokens.ts +2 -0
- package/src/spoke/spoke.ts +465 -28
- package/src/spoke/taskboard.ts +243 -0
- package/src/spoke/timeline.ts +184 -6
- package/src/spoke/tracker.ts +79 -4
- package/src/status.ts +203 -10
- package/src/tui-commands.ts +13 -6
- package/src/tui-messaging.ts +93 -0
- package/src/tui.ts +15 -0
- package/web/dist/assets/highlight-y4rJE6Tt.js +5 -0
- package/web/dist/assets/index-D9zMbxSg.css +1 -0
- package/web/dist/assets/index-db1z4duA.js +20 -0
- package/web/dist/assets/ort-wasm-simd-threaded-CxTQ5xH-.wasm +0 -0
- package/web/dist/assets/silero-worker-C8-8vLVS.js +11 -0
- package/web/dist/assets/silero_vad-Dt9-bTDn.onnx +0 -0
- package/web/dist/index.html +9 -2
- package/web/dist/sw.js +79 -15
- package/web/dist/assets/index-DClBvfLL.css +0 -1
- package/web/dist/assets/index-Djo_yF0a.js +0 -12
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hub-sandbox-entry: runs the harness of a hub launch inside an OpenShell sandbox (packages/runtime-openshell/image).
|
|
3
|
+
# Started detached by hub-sandbox-start (the runtime execs that; the sandbox has no main command, so it stays Ready
|
|
4
|
+
# and exec keeps working after the harness ends). In order:
|
|
5
|
+
# 1. waits until the runtime's start op has uploaded .hub/config.json (hub URL, launch token, launch ID: the
|
|
6
|
+
# plugin's spoke reads it; this script never does) and touched .hub/ready; a .hub/stop first ends the wait;
|
|
7
|
+
# 2. adds .hub/env.json (a JSON object of extra variables; never over the ones below or OpenShell's CA variables);
|
|
8
|
+
# 3. sets the offline switches and XDG dirs under the sandbox home, writes the global OpenCode config loading the
|
|
9
|
+
# baked hub plugin, and runs `opencode serve --service` on loopback in /sandbox/work;
|
|
10
|
+
# 4. sends the server one request so it loads its instance (and with it the plugin, which connects to the hub);
|
|
11
|
+
# 5. on .hub/stop ("graceful": SIGTERM, SIGKILL after HUB_STOP_GRACE seconds; "kill": SIGKILL) or the harness's own
|
|
12
|
+
# end, writes its exit code to .hub/exited and exits with it.
|
|
13
|
+
# Same script in the image (/usr/local) and the bootstrap fallback (/sandbox/.hub/opt): the harness and plugin are
|
|
14
|
+
# found next to it (<root>/bin/opencode, <root>/lib/hub/node_modules/@huynle/hub). Bash 3.2 compatible (tests run it
|
|
15
|
+
# on macOS too). Environment overrides, for tests: HUB_SANDBOX_HOME, HUB_HARNESS_PORT, HUB_STOP_GRACE,
|
|
16
|
+
# HUB_POLL_INTERVAL.
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
20
|
+
root=$(cd "$here/.." && pwd)
|
|
21
|
+
home="${HUB_SANDBOX_HOME:-/sandbox}"
|
|
22
|
+
hub="$home/.hub"
|
|
23
|
+
opencode="$root/bin/opencode"
|
|
24
|
+
plugin="$root/lib/hub/node_modules/@huynle/hub"
|
|
25
|
+
port="${HUB_HARNESS_PORT:-4096}"
|
|
26
|
+
grace="${HUB_STOP_GRACE:-10}"
|
|
27
|
+
poll="${HUB_POLL_INTERVAL:-0.5}"
|
|
28
|
+
|
|
29
|
+
say() { echo "[hub-sandbox] $*" >&2; }
|
|
30
|
+
|
|
31
|
+
mkdir -p "$hub"
|
|
32
|
+
echo "$$" >"$hub/entry.pid"
|
|
33
|
+
rm -f "$hub/exited"
|
|
34
|
+
|
|
35
|
+
# 1. Wait for the start op.
|
|
36
|
+
say "waiting for $hub/config.json and $hub/ready"
|
|
37
|
+
until [ -f "$hub/config.json" ] && [ -e "$hub/ready" ]; do
|
|
38
|
+
if [ -e "$hub/stop" ]; then
|
|
39
|
+
say "stopped before the harness started"
|
|
40
|
+
rm -f "$hub/entry.pid"
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
sleep "$poll"
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
# 2. Extra environment: NUL-separated NAME=value pairs from the harness's own bun (no jq in the fallback image).
|
|
47
|
+
protected() {
|
|
48
|
+
case "$1" in
|
|
49
|
+
OPENCODE_DISABLE_* | XDG_* | HOME | PATH | HUB_LAUNCH_CONFIG | HUB_SANDBOX_HOME | BUN_* | LD_*) return 0 ;;
|
|
50
|
+
NODE_EXTRA_CA_CERTS | SSL_CERT_FILE | SSL_CERT_DIR | CURL_CA_BUNDLE | REQUESTS_CA_BUNDLE | GIT_SSL_CAINFO | DENO_CERT) return 0 ;;
|
|
51
|
+
esac
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
if [ -f "$hub/env.json" ]; then
|
|
55
|
+
pairs=$(mktemp "${TMPDIR:-/tmp}/hub-env.XXXXXX") || exit 2
|
|
56
|
+
# shellcheck disable=SC2016
|
|
57
|
+
if ! HUB_ENV_JSON="$hub/env.json" BUN_BE_BUN=1 "$opencode" -e '
|
|
58
|
+
// An uncaught error in `bun -e` can end with status 0: fail explicitly.
|
|
59
|
+
let env
|
|
60
|
+
try {
|
|
61
|
+
env = JSON.parse(require("node:fs").readFileSync(process.env.HUB_ENV_JSON, "utf8"))
|
|
62
|
+
} catch {
|
|
63
|
+
process.exit(2)
|
|
64
|
+
}
|
|
65
|
+
if (typeof env !== "object" || env === null || Array.isArray(env)) process.exit(2)
|
|
66
|
+
let out = ""
|
|
67
|
+
for (const [k, v] of Object.entries(env)) {
|
|
68
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k) || !["string", "number", "boolean"].includes(typeof v)) continue
|
|
69
|
+
const value = String(v)
|
|
70
|
+
if (!value.includes("\0")) out += `${k}=${value}\0`
|
|
71
|
+
}
|
|
72
|
+
process.stdout.write(out)
|
|
73
|
+
' >"$pairs" 2>/dev/null </dev/null; then
|
|
74
|
+
say "invalid $hub/env.json: expected a JSON object"
|
|
75
|
+
rm -f "$pairs" "$hub/entry.pid"
|
|
76
|
+
echo 2 >"$hub/exited"
|
|
77
|
+
exit 2
|
|
78
|
+
fi
|
|
79
|
+
while IFS= read -r -d '' pair; do
|
|
80
|
+
name="${pair%%=*}"
|
|
81
|
+
if protected "$name"; then
|
|
82
|
+
say "ignoring $name from env.json"
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
85
|
+
export "${pair?}"
|
|
86
|
+
done <"$pairs"
|
|
87
|
+
rm -f "$pairs"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 3. The harness's environment and config.
|
|
91
|
+
export OPENCODE_DISABLE_MODELS_FETCH=1 OPENCODE_DISABLE_AUTOUPDATE=1 OPENCODE_DISABLE_PROJECT_CONFIG=1
|
|
92
|
+
export XDG_CONFIG_HOME="$home/.xdg/config" XDG_DATA_HOME="$home/.xdg/data" XDG_STATE_HOME="$home/.xdg/state" XDG_CACHE_HOME="$home/.xdg/cache"
|
|
93
|
+
export HUB_LAUNCH_CONFIG="$hub/config.json"
|
|
94
|
+
unset BUN_BE_BUN
|
|
95
|
+
if [ -z "${OPENCODE_PASSWORD:-}" ]; then
|
|
96
|
+
OPENCODE_PASSWORD=$(od -An -N24 -tx1 /dev/urandom | tr -d ' \n')
|
|
97
|
+
export OPENCODE_PASSWORD
|
|
98
|
+
fi
|
|
99
|
+
mkdir -p "$XDG_CONFIG_HOME/opencode" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME"
|
|
100
|
+
json_path=$(printf '%s' "$plugin" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
|
|
101
|
+
printf '{ "plugins": [{ "package": "%s", "options": {} }] }\n' "$json_path" >"$XDG_CONFIG_HOME/opencode/opencode.jsonc"
|
|
102
|
+
|
|
103
|
+
workdir="$home"
|
|
104
|
+
[ -d "$home/work" ] && workdir="$home/work"
|
|
105
|
+
cd "$workdir" || exit 1
|
|
106
|
+
say "starting opencode serve --service on 127.0.0.1:$port in $workdir"
|
|
107
|
+
"$opencode" serve --service --hostname 127.0.0.1 --port "$port" &
|
|
108
|
+
harness=$!
|
|
109
|
+
echo "$harness" >"$hub/harness.pid"
|
|
110
|
+
|
|
111
|
+
# 4. One authorized request loads the server's instance and the plugin (a --service server may keep its own password
|
|
112
|
+
# in $XDG_CONFIG_HOME/opencode/service.json).
|
|
113
|
+
# shellcheck disable=SC2016
|
|
114
|
+
HUB_KICK_URL="http://127.0.0.1:$port/api/agent" BUN_BE_BUN=1 "$opencode" -e '
|
|
115
|
+
const fs = require("node:fs")
|
|
116
|
+
const password = () => {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(fs.readFileSync(`${process.env.XDG_CONFIG_HOME}/opencode/service.json`, "utf8")).password ?? process.env.OPENCODE_PASSWORD
|
|
119
|
+
} catch {
|
|
120
|
+
return process.env.OPENCODE_PASSWORD
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const end = Date.now() + 60_000; Date.now() < end; await Bun.sleep(500)) {
|
|
124
|
+
const ok = await fetch(process.env.HUB_KICK_URL, { headers: { authorization: `Basic ${btoa(`opencode:${password()}`)}` } }).then((r) => r.ok, () => false)
|
|
125
|
+
if (ok) {
|
|
126
|
+
console.error("[hub-sandbox] the harness answered; its plugin is loading")
|
|
127
|
+
process.exit(0)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.error("[hub-sandbox] the harness did not answer within 60 s")
|
|
131
|
+
' </dev/null &
|
|
132
|
+
kick=$!
|
|
133
|
+
|
|
134
|
+
# 5. Supervise.
|
|
135
|
+
stop_harness() {
|
|
136
|
+
if [ "$1" = kill ]; then
|
|
137
|
+
say "stop (kill): SIGKILL"
|
|
138
|
+
kill -KILL "$harness" 2>/dev/null
|
|
139
|
+
return
|
|
140
|
+
fi
|
|
141
|
+
say "stop ($1): SIGTERM, SIGKILL after ${grace}s"
|
|
142
|
+
kill -TERM "$harness" 2>/dev/null
|
|
143
|
+
local started=$SECONDS
|
|
144
|
+
while kill -0 "$harness" 2>/dev/null && [ $((SECONDS - started)) -lt "$grace" ]; do sleep "$poll"; done
|
|
145
|
+
kill -0 "$harness" 2>/dev/null && kill -KILL "$harness" 2>/dev/null
|
|
146
|
+
}
|
|
147
|
+
trap 'stop_harness graceful' TERM INT
|
|
148
|
+
|
|
149
|
+
while kill -0 "$harness" 2>/dev/null; do
|
|
150
|
+
if [ -e "$hub/stop" ]; then
|
|
151
|
+
mode=$(head -n 1 "$hub/stop" 2>/dev/null | tr -d '[:space:]')
|
|
152
|
+
stop_harness "${mode:-graceful}"
|
|
153
|
+
break
|
|
154
|
+
fi
|
|
155
|
+
sleep "$poll"
|
|
156
|
+
done
|
|
157
|
+
wait "$harness"
|
|
158
|
+
code=$?
|
|
159
|
+
kill "$kick" 2>/dev/null
|
|
160
|
+
echo "$code" >"$hub/exited"
|
|
161
|
+
rm -f "$hub/harness.pid" "$hub/entry.pid"
|
|
162
|
+
say "the harness exited with $code"
|
|
163
|
+
exit "$code"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// The hub sandbox image and its upload fallback (P5 spike report gut64rwq §7–§8): naming, layout and the commands
|
|
2
|
+
// build.ts and the openshell runtime run. Pure: nothing here touches Docker, the network or a sandbox.
|
|
3
|
+
//
|
|
4
|
+
// Layout (the same under both roots, so one entry script serves both):
|
|
5
|
+
// <root>/bin/opencode the Linux OpenCode build from npm @opencode/cli-linux-<arch>
|
|
6
|
+
// <root>/bin/hub-sandbox-start starts the entry script detached (setsid nohup), returns at once
|
|
7
|
+
// <root>/bin/hub-sandbox-entry waits for .hub/config.json + .hub/ready, runs `opencode serve
|
|
8
|
+
// --service`, stops it on .hub/stop
|
|
9
|
+
// <root>/lib/hub/node_modules/@huynle/hub the hub plugin (this repository's package), with its dependencies
|
|
10
|
+
// Roots: IMAGE_ROOT in the hub-sandbox image; BOOTSTRAP_ROOT when the runtime uploads the bootstrap tarball into a
|
|
11
|
+
// sandbox on the gateway's default image (/usr is read-only there; only /sandbox and /tmp are writable).
|
|
12
|
+
|
|
13
|
+
/** The OpenCode release the image pins (the version the repository's live tests run against). */
|
|
14
|
+
export const OPENCODE_VERSION = "2.1.4"
|
|
15
|
+
/** The image repository name; a registry prefix makes it pullable (the vm driver pulls over HTTPS only). */
|
|
16
|
+
export const IMAGE_NAME = "hub-sandbox"
|
|
17
|
+
/** Where the image installs the harness, scripts and plugin. */
|
|
18
|
+
export const IMAGE_ROOT = "/usr/local"
|
|
19
|
+
/** Where the bootstrap fallback unpacks the same layout inside a sandbox. */
|
|
20
|
+
export const BOOTSTRAP_ROOT = "/sandbox/.hub/opt"
|
|
21
|
+
/** The sandbox's writable working directory: uploads, downloads and the bootstrap live under it. */
|
|
22
|
+
export const SANDBOX_HOME = "/sandbox"
|
|
23
|
+
export const START_SCRIPT = "hub-sandbox-start"
|
|
24
|
+
export const ENTRY_SCRIPT = "hub-sandbox-entry"
|
|
25
|
+
export const PLUGIN_SUBPATH = "lib/hub/node_modules/@huynle/hub"
|
|
26
|
+
|
|
27
|
+
export type LinuxArch = "arm64" | "x64"
|
|
28
|
+
|
|
29
|
+
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
|
|
30
|
+
|
|
31
|
+
/** The Linux build matching a host architecture (process.arch or uname -m); throws for others. */
|
|
32
|
+
export function linuxArch(arch: string): LinuxArch {
|
|
33
|
+
if (arch === "arm64" || arch === "aarch64") return "arm64"
|
|
34
|
+
if (arch === "x64" || arch === "amd64" || arch === "x86_64") return "x64"
|
|
35
|
+
throw new Error(`no Linux OpenCode build for architecture ${arch} (arm64 and x64 only)`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function dockerPlatform(arch: LinuxArch): string {
|
|
39
|
+
return arch === "arm64" ? "linux/arm64" : "linux/amd64"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pinned(version: string): string {
|
|
43
|
+
if (!VERSION.test(version)) throw new Error(`the OpenCode version must be an exact version, not ${JSON.stringify(version)}`)
|
|
44
|
+
return version
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The npm package of the Linux OpenCode build: @opencode/cli-linux-<arch>@<version>. */
|
|
48
|
+
export function opencodePackage(arch: LinuxArch, version: string = OPENCODE_VERSION): string {
|
|
49
|
+
return `@opencode/cli-linux-${arch}@${pinned(version)}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The registry tarball of that package (it holds package/bin/opencode). */
|
|
53
|
+
export function opencodeTarballURL(arch: LinuxArch, version: string = OPENCODE_VERSION): string {
|
|
54
|
+
return `https://registry.npmjs.org/@opencode/cli-linux-${arch}/-/cli-linux-${arch}-${pinned(version)}.tgz`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The registry metadata of that version (dist.integrity, dist.tarball). */
|
|
58
|
+
export function opencodeMetadataURL(arch: LinuxArch, version: string = OPENCODE_VERSION): string {
|
|
59
|
+
return `https://registry.npmjs.org/@opencode/cli-linux-${arch}/${pinned(version)}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** <name>:<hub version>-opencode<OpenCode version>. */
|
|
63
|
+
export function imageTag(hubVersion: string, opencodeVersion: string = OPENCODE_VERSION, name: string = IMAGE_NAME): string {
|
|
64
|
+
return `${name}:${hubVersion}-opencode${pinned(opencodeVersion)}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function bootstrapTarballName(hubVersion: string, arch: LinuxArch, opencodeVersion: string = OPENCODE_VERSION): string {
|
|
68
|
+
return `${IMAGE_NAME}-bootstrap-${hubVersion}-opencode${pinned(opencodeVersion)}-linux-${arch}.tgz`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The harness binary under a root; the sandbox policy must name it (policy.ts HARNESS_BINARY for the image). */
|
|
72
|
+
export function harnessBinary(root: string = IMAGE_ROOT): string {
|
|
73
|
+
return `${root}/bin/opencode`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function pluginDir(root: string = IMAGE_ROOT): string {
|
|
77
|
+
return `${root}/${PLUGIN_SUBPATH}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The argv the runtime execs (after start uploaded .hub/config.json and touched .hub/ready, or before). */
|
|
81
|
+
export function startCommand(root: string = IMAGE_ROOT): string[] {
|
|
82
|
+
return [`${root}/bin/${START_SCRIPT}`]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Unpacks an uploaded bootstrap tarball (a path under /sandbox) into the bootstrap root, then removes it. */
|
|
86
|
+
export function bootstrapCommands(uploaded: string, root: string = BOOTSTRAP_ROOT): string[][] {
|
|
87
|
+
const parts = uploaded.split("/")
|
|
88
|
+
if (!uploaded.startsWith(`${SANDBOX_HOME}/`) || parts.includes("..") || parts.includes(".")) throw new Error(`the bootstrap tarball must be uploaded under ${SANDBOX_HOME}, not ${uploaded}`)
|
|
89
|
+
return [
|
|
90
|
+
["mkdir", "-p", root],
|
|
91
|
+
["tar", "-xzf", uploaded, "-C", root],
|
|
92
|
+
["rm", "-f", uploaded],
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface DockerBuildOptions {
|
|
97
|
+
readonly tag: string
|
|
98
|
+
readonly arch: LinuxArch
|
|
99
|
+
/** The staged build context (build.ts stage). */
|
|
100
|
+
readonly context: string
|
|
101
|
+
readonly hubVersion: string
|
|
102
|
+
readonly opencodeVersion?: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** `docker build` arguments for one platform. */
|
|
106
|
+
export function dockerBuildArgs(o: DockerBuildOptions): string[] {
|
|
107
|
+
return [
|
|
108
|
+
"build",
|
|
109
|
+
"--platform",
|
|
110
|
+
dockerPlatform(o.arch),
|
|
111
|
+
"--tag",
|
|
112
|
+
o.tag,
|
|
113
|
+
"--build-arg",
|
|
114
|
+
`HUB_VERSION=${o.hubVersion}`,
|
|
115
|
+
"--build-arg",
|
|
116
|
+
`OPENCODE_VERSION=${pinned(o.opencodeVersion ?? OPENCODE_VERSION)}`,
|
|
117
|
+
o.context,
|
|
118
|
+
]
|
|
119
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hub-sandbox-start: starts hub-sandbox-entry detached and returns at once (the runtime execs it; spike report
|
|
3
|
+
# gut64rwq §6: a process started by exec survives the exec when it has its own session and no open stdio). Logs go to
|
|
4
|
+
# <sandbox home>/.hub/harness.log. Starting again while the entry runs does nothing.
|
|
5
|
+
set -u
|
|
6
|
+
|
|
7
|
+
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
8
|
+
hub="${HUB_SANDBOX_HOME:-/sandbox}/.hub"
|
|
9
|
+
log="$hub/harness.log"
|
|
10
|
+
mkdir -p "$hub"
|
|
11
|
+
|
|
12
|
+
if [ -f "$hub/entry.pid" ] && kill -0 "$(cat "$hub/entry.pid" 2>/dev/null)" 2>/dev/null; then
|
|
13
|
+
echo "hub-sandbox: the harness is already running (pid $(cat "$hub/entry.pid"))"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if command -v setsid >/dev/null 2>&1; then
|
|
18
|
+
setsid nohup "$here/hub-sandbox-entry" >>"$log" 2>&1 </dev/null &
|
|
19
|
+
else
|
|
20
|
+
nohup "$here/hub-sandbox-entry" >>"$log" 2>&1 </dev/null &
|
|
21
|
+
fi
|
|
22
|
+
# The entry rewrites this with its own pid; written here too so a start right after sees it.
|
|
23
|
+
echo "$!" >"$hub/entry.pid"
|
|
24
|
+
echo "hub-sandbox: started (pid $!, log $log)"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@huynle/hub-runtime-openshell",
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "The `openshell` runtime of `hub node`: launches in NVIDIA OpenShell sandboxes, driven through the openshell CLI (internal; shipped inside @huynle/hub, not published on its own).",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
}
|
|
12
|
+
}
|