@spunto/build 0.20.4 → 0.21.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spunto/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Spunto's shared Build engine — the devcontainer image protocol and VS Code extension registry clients, with no database, no HTTP framework and no UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,15 @@ export type AgentCatalog = {
|
|
|
47
47
|
/** The variable a task's chosen parameters reach the runner in, as one JSON object. */
|
|
48
48
|
export const OPTIONS_ENV = "SPUNTO_AGENT_OPTIONS"
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The variable the org's MCP servers reach the runner in (RFC 0049): a JSON array of
|
|
52
|
+
* `{ name, path }`, `path` being the platform's gateway for that server, resolved by the runner
|
|
53
|
+
* against `SPUNTO_API_URL` and called with `SPUNTO_WORKER_TOKEN`. No vendor token, no harness shape.
|
|
54
|
+
*/
|
|
55
|
+
export const MCP_ENV = "SPUNTO_AGENT_MCP"
|
|
56
|
+
|
|
57
|
+
export type AgentMcpServer = { name: string; path: string }
|
|
58
|
+
|
|
50
59
|
const CLAUDE_EFFORT: AgentParamValue[] = [
|
|
51
60
|
{ value: "low", label: "Low" },
|
|
52
61
|
{ value: "medium", label: "Medium" },
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
// adapter it already had (`claude-runner`)
|
|
14
14
|
// env SPUNTO_AGENT_OPTIONS — the task's parameters, as declared by the app's catalog
|
|
15
15
|
// (`./catalogs.ts`): `{"effort":"high"}`
|
|
16
|
+
// SPUNTO_AGENT_MCP — the org's MCP servers (RFC 0049), in the platform's words:
|
|
17
|
+
// `[{"name":"linear","path":"/api/mcp-gateway/…"}]`, reached through the platform's
|
|
18
|
+
// gateway with the machine's own identity (`SPUNTO_WORKER_TOKEN`)
|
|
16
19
|
// exit 0 once the input is closed and the last turn is over, 1 when the session could not run
|
|
17
20
|
//
|
|
18
21
|
// Credentials are not this file's business: whatever the member set as secrets (an
|
|
@@ -90,6 +93,30 @@ function readOptions() {
|
|
|
90
93
|
}
|
|
91
94
|
const params = readOptions()
|
|
92
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The org's MCP servers (RFC 0049), as the SDK's `mcpServers`. Each is the platform's gateway, not
|
|
98
|
+
* the vendor's server: the machine shows its own identity and never holds the vendor's token. One
|
|
99
|
+
* that can't be reached (no API URL, no identity) is left out rather than failing the session.
|
|
100
|
+
*/
|
|
101
|
+
function readMcpServers() {
|
|
102
|
+
let listed
|
|
103
|
+
try {
|
|
104
|
+
listed = JSON.parse(process.env.SPUNTO_AGENT_MCP || "[]")
|
|
105
|
+
} catch {
|
|
106
|
+
return {}
|
|
107
|
+
}
|
|
108
|
+
const api = (process.env.SPUNTO_API_URL || "").replace(/\/$/, "")
|
|
109
|
+
const token = process.env.SPUNTO_WORKER_TOKEN
|
|
110
|
+
if (!Array.isArray(listed) || !api || !token) return {}
|
|
111
|
+
const servers = {}
|
|
112
|
+
for (const s of listed) {
|
|
113
|
+
if (!s || typeof s.name !== "string" || typeof s.path !== "string") continue
|
|
114
|
+
servers[s.name] = { type: "http", url: `${api}${s.path}`, headers: { Authorization: `Bearer ${token}` } }
|
|
115
|
+
}
|
|
116
|
+
return servers
|
|
117
|
+
}
|
|
118
|
+
const mcpServers = readMcpServers()
|
|
119
|
+
|
|
93
120
|
let sdk
|
|
94
121
|
try {
|
|
95
122
|
sdk = await import(SDK)
|
|
@@ -105,6 +132,8 @@ const session = sdk.query({
|
|
|
105
132
|
...(args.model ? { model: args.model } : {}),
|
|
106
133
|
...(args.resume ? { resume: args.resume } : {}),
|
|
107
134
|
...(typeof params.effort === "string" ? { effort: params.effort } : {}),
|
|
135
|
+
// Added to the servers Claude Code reads from its settings (`.mcp.json`, `~/.claude.json`).
|
|
136
|
+
...(Object.keys(mcpServers).length ? { mcpServers } : {}),
|
|
108
137
|
// A task runs unattended in a disposable machine — what `--dangerously-skip-permissions` did.
|
|
109
138
|
permissionMode: "bypassPermissions",
|
|
110
139
|
allowDangerouslySkipPermissions: true,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// stdin {"type":"user","text"} · {"type":"interrupt","id"} · {"type":"title",…} (ignored: Codex
|
|
7
7
|
// does not name its threads) ; closed = the session ends after its turn
|
|
8
8
|
// stdout the SDK's `ThreadEvent`s, one per line (`codex-runner` in the platform's adapters)
|
|
9
|
+
// env SPUNTO_AGENT_MCP — the org's MCP servers, through the platform's gateway (RFC 0049)
|
|
9
10
|
// env SPUNTO_AGENT_OPTIONS — the task's parameters (`./catalogs.ts`):
|
|
10
11
|
// `{"reasoningEffort":"high","webSearch":"live"}`
|
|
11
12
|
// exit 0 once the input is closed and the last turn is over, 1 when the session could not run
|
|
@@ -59,6 +60,29 @@ function readOptions() {
|
|
|
59
60
|
}
|
|
60
61
|
const params = readOptions()
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The org's MCP servers (RFC 0049), as Codex's `mcp_servers` config. Each is the platform's
|
|
65
|
+
* gateway: Codex reads the machine's identity from `SPUNTO_WORKER_TOKEN` itself
|
|
66
|
+
* (`bearer_token_env_var`), and never holds the vendor's token.
|
|
67
|
+
*/
|
|
68
|
+
function readMcpServers() {
|
|
69
|
+
let listed
|
|
70
|
+
try {
|
|
71
|
+
listed = JSON.parse(process.env.SPUNTO_AGENT_MCP || "[]")
|
|
72
|
+
} catch {
|
|
73
|
+
return {}
|
|
74
|
+
}
|
|
75
|
+
const api = (process.env.SPUNTO_API_URL || "").replace(/\/$/, "")
|
|
76
|
+
if (!Array.isArray(listed) || !api || !process.env.SPUNTO_WORKER_TOKEN) return {}
|
|
77
|
+
const servers = {}
|
|
78
|
+
for (const s of listed) {
|
|
79
|
+
if (!s || typeof s.name !== "string" || typeof s.path !== "string") continue
|
|
80
|
+
servers[s.name] = { url: `${api}${s.path}`, bearer_token_env_var: "SPUNTO_WORKER_TOKEN" }
|
|
81
|
+
}
|
|
82
|
+
return servers
|
|
83
|
+
}
|
|
84
|
+
const mcpServers = readMcpServers()
|
|
85
|
+
|
|
62
86
|
let sdk
|
|
63
87
|
try {
|
|
64
88
|
sdk = await import(SDK)
|
|
@@ -67,7 +91,10 @@ try {
|
|
|
67
91
|
}
|
|
68
92
|
|
|
69
93
|
const apiKey = process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY
|
|
70
|
-
const codex = new sdk.Codex(
|
|
94
|
+
const codex = new sdk.Codex({
|
|
95
|
+
...(apiKey ? { apiKey } : {}),
|
|
96
|
+
...(Object.keys(mcpServers).length ? { config: { mcp_servers: mcpServers } } : {}),
|
|
97
|
+
})
|
|
71
98
|
const threadOptions = {
|
|
72
99
|
workingDirectory: process.cwd(),
|
|
73
100
|
...(args.model ? { model: args.model } : {}),
|
|
@@ -91,5 +91,5 @@ export function harnessOfFeature(featureId: string): AgentHarness | null {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export { AGENT_FEATURE_SCRIPTS, AGENT_HOME, AGENT_NODE_VERSION } from "./features"
|
|
94
|
-
export { AGENT_CATALOGS, OPTIONS_ENV, agentOptionsError } from "./catalogs"
|
|
95
|
-
export type { AgentCatalog, AgentModel, AgentParam, AgentParamValue } from "./catalogs"
|
|
94
|
+
export { AGENT_CATALOGS, MCP_ENV, OPTIONS_ENV, agentOptionsError } from "./catalogs"
|
|
95
|
+
export type { AgentCatalog, AgentMcpServer, AgentModel, AgentParam, AgentParamValue } from "./catalogs"
|
|
@@ -19,7 +19,7 @@ import { fileURLToPath } from "node:url"
|
|
|
19
19
|
import os from "node:os"
|
|
20
20
|
import net from "node:net"
|
|
21
21
|
|
|
22
|
-
const VERSION = "0.
|
|
22
|
+
const VERSION = "0.11.0"
|
|
23
23
|
const API = (process.env.SPUNTO_API_URL ?? "").replace(/\/$/, "")
|
|
24
24
|
// `let`: a fork (RFC 0039 § 4.10) hands this process the identity of the copy it now runs in.
|
|
25
25
|
let TOKEN = process.env.SPUNTO_SANDBOX_TOKEN ?? ""
|
|
@@ -430,8 +430,13 @@ function diskUsage() {
|
|
|
430
430
|
return out
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
+
// The box's live status — what its page's Resources panel and its "disk nearly full" warning read
|
|
434
|
+
// (`agent.heartbeat`). Not telemetry: nothing of it is stored, the platform keeps the last one in
|
|
435
|
+
// memory. Removed with the telemetry on 2026-10-19 (667cfa0f) by mistake: every sandbox page said
|
|
436
|
+
// "Loading stats…" for good, and a full disk said nothing.
|
|
433
437
|
function heartbeat() {
|
|
434
|
-
|
|
438
|
+
const r = boxResources()
|
|
439
|
+
send({ type: "heartbeat", load: os.loadavg(), memFree: r.memFree, memTotal: r.memTotal, cpuPercent: r.cpuPercent, cpus: r.cpus, uptime: os.uptime(), disks: diskUsage() })
|
|
435
440
|
reportProject()
|
|
436
441
|
}
|
|
437
442
|
|
|
@@ -23,9 +23,10 @@ echo "[feature] google-cloud-cli: installing..."
|
|
|
23
23
|
apt-get update -y -qq
|
|
24
24
|
apt-get install -y -qq apt-transport-https ca-certificates gnupg curl
|
|
25
25
|
|
|
26
|
-
# Modern apt key setup (no deprecated apt-key)
|
|
26
|
+
# Modern apt key setup (no deprecated apt-key). --batch --yes: a recipe re-baked in place finds the
|
|
27
|
+
# keyring of its first bake, and gpg asks on /dev/tty before overwriting it — no tty, exit 2.
|
|
27
28
|
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
|
|
28
|
-
| gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
|
29
|
+
| gpg --batch --yes --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
|
29
30
|
|
|
30
31
|
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
|
|
31
32
|
| tee /etc/apt/sources.list.d/google-cloud-sdk.list
|