@growthub/cli 0.14.0 → 0.14.2
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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +99 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +84 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +107 -34
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +72 -15
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +264 -22
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +81 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +179 -117
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +34 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SidecarExpandView.jsx +37 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SwarmRunCockpit.jsx +625 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +150 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +136 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +61 -13
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +26 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/local-intelligence-browser-access.js +516 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +224 -11
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +4 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-process.js +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/sandbox-adapter-registry.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +254 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +3 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +10 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +82 -27
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +8 -6
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +554 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +364 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
- package/package.json +1 -1
|
@@ -34,28 +34,97 @@ import path from "node:path";
|
|
|
34
34
|
import { registerSandboxAdapter } from "./sandbox-adapter-registry.js";
|
|
35
35
|
|
|
36
36
|
const MAX_OUTPUT_BYTES = 1024 * 256;
|
|
37
|
+
const TELEMETRY_MARKER = "GROWTHUB_AGENT_TELEMETRY:";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Browser access — the product's OWN agent browser primitive, surfaced.
|
|
41
|
+
*
|
|
42
|
+
* The upstream Paperclip server already gives every agent browser access
|
|
43
|
+
* through one boolean: the agent config's `chrome` primitive (see
|
|
44
|
+
* `ui/src/components/agent-config-primitives.tsx` — "Enable Claude's Chrome
|
|
45
|
+
* integration by passing --chrome") gated by the chrome-lease service in
|
|
46
|
+
* `server/src/services/chrome-lease.ts` before `adapter.execute()`. The
|
|
47
|
+
* sandbox row's `browserAccess` is the SAME bit on the governed Data Model
|
|
48
|
+
* side, so a row stays portable to the upstream adapter registry without
|
|
49
|
+
* translation — exactly like the host slugs themselves.
|
|
50
|
+
*
|
|
51
|
+
* When `browserAccess` is on, each host engages its FIRST-PARTY browser
|
|
52
|
+
* integration — nothing is invented or injected by this adapter:
|
|
53
|
+
*
|
|
54
|
+
* native-flag the host CLI has a first-party browser flag and
|
|
55
|
+
* argv(request) appends it (Claude Code `--chrome`,
|
|
56
|
+
* Codex `--enable browser_use --enable in_app_browser`).
|
|
57
|
+
* env-signal the host CLI has no documented one-shot browser flag; it
|
|
58
|
+
* receives GROWTHUB_SANDBOX_BROWSER_ACCESS=1 (mirroring the
|
|
59
|
+
* upstream browser-isolation context) and its own browser
|
|
60
|
+
* integration — whatever the operator has configured in that
|
|
61
|
+
* host — honors the row's setting. We never fabricate a flag
|
|
62
|
+
* or write host config we cannot verify against the upstream
|
|
63
|
+
* tool, the same rule the auth catalog follows for login
|
|
64
|
+
* subcommands.
|
|
65
|
+
*
|
|
66
|
+
* In the orchestration graph this is what makes browser access node-level
|
|
67
|
+
* and host-agnostic: thinAdapter / ai-agent nodes execute through this same
|
|
68
|
+
* catalog, so every node inherits the row's browser grant regardless of
|
|
69
|
+
* which host runs it.
|
|
70
|
+
*/
|
|
37
71
|
|
|
38
72
|
/**
|
|
39
73
|
* Canonical Paperclip host catalog — slugs mirror `AGENT_ADAPTER_TYPES`.
|
|
40
74
|
*
|
|
41
75
|
* Each entry declares the binary the operator must have on PATH and how to
|
|
42
|
-
* invoke it for one-shot prompt execution. `argv`
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
76
|
+
* invoke it for one-shot prompt execution. `argv(request)` receives the sealed
|
|
77
|
+
* RunRequest and returns the argv array the adapter should pass — hosts with
|
|
78
|
+
* native capability flags (network sandbox mode, browser access) derive them
|
|
79
|
+
* deterministically from the governed row's saved settings. `inputMode`
|
|
80
|
+
* chooses whether the user's command is sent via stdin or as a positional
|
|
81
|
+
* argument. `installHint` is surfaced verbatim when the binary is not found,
|
|
82
|
+
* so operators get an actionable error. `browser` declares how the host's
|
|
83
|
+
* first-party browser integration is engaged (see above).
|
|
46
84
|
*/
|
|
47
85
|
const HOST_CATALOG = {
|
|
48
86
|
claude_local: {
|
|
49
87
|
label: "Claude Code (local)",
|
|
50
88
|
binary: "claude",
|
|
51
|
-
argv: () =>
|
|
89
|
+
argv: (request = {}) => {
|
|
90
|
+
const args = ["-p", "--output-format", "text"];
|
|
91
|
+
if (request.browserAccess) {
|
|
92
|
+
// Claude Code's first-party Chrome integration — the same flag the
|
|
93
|
+
// upstream server adapter passes when the agent config's `chrome`
|
|
94
|
+
// primitive is on.
|
|
95
|
+
args.push("--chrome");
|
|
96
|
+
}
|
|
97
|
+
return args;
|
|
98
|
+
},
|
|
99
|
+
browser: { lane: "native-flag", flags: ["--chrome"] },
|
|
52
100
|
inputMode: "stdin",
|
|
53
101
|
installHint: "Install Claude Code: npm i -g @anthropic-ai/claude-code"
|
|
54
102
|
},
|
|
55
103
|
codex_local: {
|
|
56
104
|
label: "Codex CLI (local)",
|
|
57
105
|
binary: "codex",
|
|
58
|
-
argv: () =>
|
|
106
|
+
argv: (request = {}) => {
|
|
107
|
+
// INTENTIONAL: networkAllow alone selects `workspace-write`. Codex's
|
|
108
|
+
// `read-only` sandbox blocks ALL outbound network, so workspace-write is
|
|
109
|
+
// the least-privileged Codex mode where the row's network grant can take
|
|
110
|
+
// effect — and writes are confined to the sealed ephemeral workdir the
|
|
111
|
+
// adapter spawns into (cwd), never the operator's repo. Browser flags
|
|
112
|
+
// remain gated on browserAccess only; network alone never opens a browser.
|
|
113
|
+
const netOn = Boolean(request.networkAllow);
|
|
114
|
+
const browserOn = Boolean(request.browserAccess);
|
|
115
|
+
const args = [
|
|
116
|
+
"exec",
|
|
117
|
+
"--skip-git-repo-check",
|
|
118
|
+
"--sandbox",
|
|
119
|
+
netOn ? "workspace-write" : "read-only",
|
|
120
|
+
];
|
|
121
|
+
if (browserOn) {
|
|
122
|
+
args.push("--enable", "browser_use", "--enable", "in_app_browser");
|
|
123
|
+
}
|
|
124
|
+
args.push("-");
|
|
125
|
+
return args;
|
|
126
|
+
},
|
|
127
|
+
browser: { lane: "native-flag", flags: ["--enable", "browser_use", "--enable", "in_app_browser"] },
|
|
59
128
|
inputMode: "stdin",
|
|
60
129
|
installHint: "Install Codex CLI: npm i -g @openai/codex"
|
|
61
130
|
},
|
|
@@ -63,6 +132,7 @@ const HOST_CATALOG = {
|
|
|
63
132
|
label: "Cursor Agent (local)",
|
|
64
133
|
binary: "cursor-agent",
|
|
65
134
|
argv: () => ["--print"],
|
|
135
|
+
browser: { lane: "env-signal" },
|
|
66
136
|
inputMode: "stdin",
|
|
67
137
|
installHint: "Install Cursor Agent CLI: curl https://cursor.com/install -fsS | bash"
|
|
68
138
|
},
|
|
@@ -70,6 +140,7 @@ const HOST_CATALOG = {
|
|
|
70
140
|
label: "Gemini CLI (local)",
|
|
71
141
|
binary: "gemini",
|
|
72
142
|
argv: () => ["-p", "-"],
|
|
143
|
+
browser: { lane: "env-signal" },
|
|
73
144
|
inputMode: "stdin",
|
|
74
145
|
installHint: "Install Gemini CLI: npm i -g @google/gemini-cli"
|
|
75
146
|
},
|
|
@@ -77,6 +148,7 @@ const HOST_CATALOG = {
|
|
|
77
148
|
label: "OpenCode (local)",
|
|
78
149
|
binary: "opencode",
|
|
79
150
|
argv: () => ["run", "--quiet"],
|
|
151
|
+
browser: { lane: "env-signal" },
|
|
80
152
|
inputMode: "stdin",
|
|
81
153
|
installHint: "Install OpenCode: npm i -g opencode-ai"
|
|
82
154
|
},
|
|
@@ -84,6 +156,7 @@ const HOST_CATALOG = {
|
|
|
84
156
|
label: "Pi (local)",
|
|
85
157
|
binary: "pi",
|
|
86
158
|
argv: () => ["run", "--stdin"],
|
|
159
|
+
browser: { lane: "env-signal" },
|
|
87
160
|
inputMode: "stdin",
|
|
88
161
|
installHint: "Install Pi CLI: refer to your Paperclip Pi distribution"
|
|
89
162
|
},
|
|
@@ -91,6 +164,7 @@ const HOST_CATALOG = {
|
|
|
91
164
|
label: "Qwen Code (local)",
|
|
92
165
|
binary: "qwen",
|
|
93
166
|
argv: () => ["-p"],
|
|
167
|
+
browser: { lane: "env-signal" },
|
|
94
168
|
inputMode: "stdin",
|
|
95
169
|
installHint: "Install Qwen Code CLI: refer to your Qwen distribution"
|
|
96
170
|
},
|
|
@@ -98,6 +172,7 @@ const HOST_CATALOG = {
|
|
|
98
172
|
label: "Hermes Paperclip (local)",
|
|
99
173
|
binary: "hermes",
|
|
100
174
|
argv: () => ["run", "--stdin"],
|
|
175
|
+
browser: { lane: "env-signal" },
|
|
101
176
|
inputMode: "stdin",
|
|
102
177
|
installHint: "Install Hermes Paperclip adapter: npm i -g hermes-paperclip-adapter"
|
|
103
178
|
},
|
|
@@ -105,6 +180,7 @@ const HOST_CATALOG = {
|
|
|
105
180
|
label: "OpenClaw Gateway (local)",
|
|
106
181
|
binary: "openclaw",
|
|
107
182
|
argv: () => ["gateway", "exec", "--stdin"],
|
|
183
|
+
browser: { lane: "env-signal" },
|
|
108
184
|
inputMode: "stdin",
|
|
109
185
|
installHint: "Install OpenClaw Gateway: refer to your Paperclip distribution"
|
|
110
186
|
}
|
|
@@ -118,6 +194,134 @@ function clampStream(buffer) {
|
|
|
118
194
|
return `${head.toString("utf8")}\n…\n[output truncated at ${MAX_OUTPUT_BYTES} bytes]`;
|
|
119
195
|
}
|
|
120
196
|
|
|
197
|
+
function safeNonNegativeInt(value) {
|
|
198
|
+
if (value === null || value === undefined || value === "") return null;
|
|
199
|
+
const n = Number(value);
|
|
200
|
+
if (!Number.isFinite(n) || n < 0) return null;
|
|
201
|
+
return Math.floor(n);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function pickFirstNumber(...values) {
|
|
205
|
+
for (const value of values) {
|
|
206
|
+
const n = safeNonNegativeInt(value);
|
|
207
|
+
if (n != null) return n;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function sumNumbers(...values) {
|
|
213
|
+
let total = 0;
|
|
214
|
+
let seen = false;
|
|
215
|
+
for (const value of values) {
|
|
216
|
+
const n = safeNonNegativeInt(value);
|
|
217
|
+
if (n == null) continue;
|
|
218
|
+
total += n;
|
|
219
|
+
seen = true;
|
|
220
|
+
}
|
|
221
|
+
return seen ? total : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function extractUsageFromObject(obj) {
|
|
225
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return { tokens: null, tools: null };
|
|
226
|
+
const usage = (obj.usage && typeof obj.usage === "object" && !Array.isArray(obj.usage))
|
|
227
|
+
? obj.usage
|
|
228
|
+
: (obj.token_usage && typeof obj.token_usage === "object" && !Array.isArray(obj.token_usage))
|
|
229
|
+
? obj.token_usage
|
|
230
|
+
: (obj.metadata?.usage && typeof obj.metadata.usage === "object" && !Array.isArray(obj.metadata.usage))
|
|
231
|
+
? obj.metadata.usage
|
|
232
|
+
: (obj.result?.usage && typeof obj.result.usage === "object" && !Array.isArray(obj.result.usage))
|
|
233
|
+
? obj.result.usage
|
|
234
|
+
: null;
|
|
235
|
+
const tokens = usage
|
|
236
|
+
? pickFirstNumber(
|
|
237
|
+
usage.total_tokens,
|
|
238
|
+
usage.totalTokens,
|
|
239
|
+
usage.tokens,
|
|
240
|
+
sumNumbers(usage.input_tokens, usage.output_tokens),
|
|
241
|
+
sumNumbers(usage.prompt_tokens, usage.completion_tokens),
|
|
242
|
+
sumNumbers(usage.inputTokens, usage.outputTokens),
|
|
243
|
+
)
|
|
244
|
+
: pickFirstNumber(obj.total_tokens, obj.totalTokens, obj.tokens);
|
|
245
|
+
const toolArrays = [
|
|
246
|
+
obj.tool_calls,
|
|
247
|
+
obj.toolCalls,
|
|
248
|
+
obj.toolInvocations,
|
|
249
|
+
obj.message?.tool_calls,
|
|
250
|
+
obj.choices?.[0]?.message?.tool_calls,
|
|
251
|
+
obj.result?.tool_calls,
|
|
252
|
+
obj.result?.toolCalls,
|
|
253
|
+
].filter(Array.isArray);
|
|
254
|
+
const tools = pickFirstNumber(
|
|
255
|
+
obj.tools,
|
|
256
|
+
obj.tool_count,
|
|
257
|
+
obj.toolCount,
|
|
258
|
+
...toolArrays.map((items) => items.length),
|
|
259
|
+
);
|
|
260
|
+
return { tokens, tools };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function mergeTelemetry(base, next) {
|
|
264
|
+
return {
|
|
265
|
+
tokens: base.tokens ?? next.tokens ?? null,
|
|
266
|
+
tools: base.tools ?? next.tools ?? null,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function parseJsonMaybe(text) {
|
|
271
|
+
const value = String(text || "").trim();
|
|
272
|
+
if (!value || !/^[\[{]/.test(value)) return null;
|
|
273
|
+
try {
|
|
274
|
+
return JSON.parse(value);
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function extractMarkedTelemetry(text) {
|
|
281
|
+
let out = { tokens: null, tools: null };
|
|
282
|
+
for (const line of String(text || "").split(/\r?\n/)) {
|
|
283
|
+
const idx = line.indexOf(TELEMETRY_MARKER);
|
|
284
|
+
if (idx === -1) continue;
|
|
285
|
+
const json = line.slice(idx + TELEMETRY_MARKER.length).trim();
|
|
286
|
+
const parsed = parseJsonMaybe(json);
|
|
287
|
+
out = mergeTelemetry(out, extractUsageFromObject(parsed));
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function extractJsonLineTelemetry(text) {
|
|
293
|
+
let out = { tokens: null, tools: null };
|
|
294
|
+
for (const line of String(text || "").split(/\r?\n/)) {
|
|
295
|
+
const parsed = parseJsonMaybe(line);
|
|
296
|
+
if (!parsed) continue;
|
|
297
|
+
out = mergeTelemetry(out, extractUsageFromObject(parsed));
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function extractStderrTextTelemetry(stderrText) {
|
|
303
|
+
const text = String(stderrText || "");
|
|
304
|
+
const total = text.match(/\b(?:total\s+tokens|tokens\s+used)\s*(?:[:=]|\r?\n)\s*([0-9][0-9,]*)/i);
|
|
305
|
+
const input = text.match(/\b(?:input|prompt)\s+tokens\s*[:=]\s*([0-9][0-9,]*)/i);
|
|
306
|
+
const output = text.match(/\b(?:output|completion)\s+tokens\s*[:=]\s*([0-9][0-9,]*)/i);
|
|
307
|
+
const tools = text.match(/\b(?:tool\s+calls?|tools\s+used)\s*[:=]\s*([0-9][0-9,]*)/i);
|
|
308
|
+
const tokens = pickFirstNumber(total?.[1]?.replace(/,/g, ""), sumNumbers(input?.[1]?.replace(/,/g, ""), output?.[1]?.replace(/,/g, "")));
|
|
309
|
+
return {
|
|
310
|
+
tokens,
|
|
311
|
+
tools: pickFirstNumber(tools?.[1]?.replace(/,/g, ""), tokens != null ? 0 : null),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function extractAgentHostTelemetry({ stdout, stderr }) {
|
|
316
|
+
let out = { tokens: null, tools: null };
|
|
317
|
+
const stdoutJson = parseJsonMaybe(stdout);
|
|
318
|
+
if (stdoutJson && !Array.isArray(stdoutJson)) out = mergeTelemetry(out, extractUsageFromObject(stdoutJson));
|
|
319
|
+
out = mergeTelemetry(out, extractMarkedTelemetry(stderr));
|
|
320
|
+
out = mergeTelemetry(out, extractJsonLineTelemetry(stderr));
|
|
321
|
+
out = mergeTelemetry(out, extractStderrTextTelemetry(stderr));
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
|
|
121
325
|
async function run(request) {
|
|
122
326
|
const hostSlug = typeof request.agentHost === "string" ? request.agentHost.trim() : "";
|
|
123
327
|
const host = HOST_CATALOG[hostSlug];
|
|
@@ -163,12 +367,13 @@ async function run(request) {
|
|
|
163
367
|
GROWTHUB_SANDBOX_AGENT_HOST: hostSlug,
|
|
164
368
|
GROWTHUB_SANDBOX_NET_ALLOW: request.networkAllow ? "1" : "0",
|
|
165
369
|
GROWTHUB_SANDBOX_NET_ALLOWLIST: Array.isArray(request.allowList) ? request.allowList.join(",") : "",
|
|
370
|
+
GROWTHUB_SANDBOX_BROWSER_ACCESS: request.browserAccess ? "1" : "0",
|
|
166
371
|
...(request.env || {})
|
|
167
372
|
};
|
|
168
373
|
|
|
169
374
|
const timeoutMs = Number.isFinite(request.timeoutMs) && request.timeoutMs > 0 ? request.timeoutMs : 60000;
|
|
170
375
|
const startedAt = Date.now();
|
|
171
|
-
const argv = host.argv(
|
|
376
|
+
const argv = host.argv(request);
|
|
172
377
|
|
|
173
378
|
return await new Promise((resolve) => {
|
|
174
379
|
let stdout = Buffer.alloc(0);
|
|
@@ -238,12 +443,15 @@ async function run(request) {
|
|
|
238
443
|
clearTimeout(timer);
|
|
239
444
|
const durationMs = Date.now() - startedAt;
|
|
240
445
|
const ok = !timedOut && exitCode === 0;
|
|
446
|
+
const stdoutText = clampStream(stdout);
|
|
447
|
+
const stderrText = clampStream(stderr);
|
|
448
|
+
const telemetry = extractAgentHostTelemetry({ stdout: stdoutText, stderr: stderrText });
|
|
241
449
|
resolve({
|
|
242
450
|
ok,
|
|
243
451
|
exitCode: typeof exitCode === "number" ? exitCode : null,
|
|
244
452
|
durationMs,
|
|
245
|
-
stdout:
|
|
246
|
-
stderr:
|
|
453
|
+
stdout: stdoutText,
|
|
454
|
+
stderr: stderrText,
|
|
247
455
|
error: timedOut
|
|
248
456
|
? `timed out after ${timeoutMs}ms`
|
|
249
457
|
: (ok ? undefined : `exit ${exitCode ?? signal ?? "unknown"}`),
|
|
@@ -253,8 +461,13 @@ async function run(request) {
|
|
|
253
461
|
binary: host.binary,
|
|
254
462
|
argv,
|
|
255
463
|
inputMode: host.inputMode,
|
|
464
|
+
browserAccess: Boolean(request.browserAccess),
|
|
465
|
+
browserLane: request.browserAccess ? host.browser.lane : null,
|
|
256
466
|
timedOut,
|
|
257
|
-
signal: signal || null
|
|
467
|
+
signal: signal || null,
|
|
468
|
+
tokens: telemetry.tokens,
|
|
469
|
+
tools: telemetry.tools,
|
|
470
|
+
telemetrySource: telemetry.tokens != null || telemetry.tools != null ? "agent-host-reported" : "unreported"
|
|
258
471
|
}
|
|
259
472
|
});
|
|
260
473
|
});
|
|
@@ -281,4 +494,4 @@ registerSandboxAdapter({
|
|
|
281
494
|
run
|
|
282
495
|
});
|
|
283
496
|
|
|
284
|
-
export { HOST_CATALOG, SUPPORTED_HOSTS };
|
|
497
|
+
export { HOST_CATALOG, SUPPORTED_HOSTS, extractAgentHostTelemetry };
|
|
@@ -190,6 +190,10 @@ async function run(request) {
|
|
|
190
190
|
endpoint,
|
|
191
191
|
model,
|
|
192
192
|
locality: "local",
|
|
193
|
+
// Truthful telemetry only — taken from the completion's usage block
|
|
194
|
+
// when the endpoint reports one, never estimated. Null means unknown.
|
|
195
|
+
tokens: Number.isFinite(outer?.usage?.total_tokens) ? outer.usage.total_tokens : null,
|
|
196
|
+
tools: Array.isArray(parsed.toolIntents) ? parsed.toolIntents.length : null,
|
|
193
197
|
},
|
|
194
198
|
};
|
|
195
199
|
} catch (err) {
|
|
@@ -108,6 +108,7 @@ async function run(request) {
|
|
|
108
108
|
GROWTHUB_SANDBOX_RUN_ID: request.runId || "",
|
|
109
109
|
GROWTHUB_SANDBOX_NET_ALLOW: request.networkAllow ? "1" : "0",
|
|
110
110
|
GROWTHUB_SANDBOX_NET_ALLOWLIST: Array.isArray(request.allowList) ? request.allowList.join(",") : "",
|
|
111
|
+
GROWTHUB_SANDBOX_BROWSER_ACCESS: request.browserAccess ? "1" : "0",
|
|
111
112
|
...(request.env || {})
|
|
112
113
|
};
|
|
113
114
|
|
|
@@ -177,7 +178,8 @@ async function run(request) {
|
|
|
177
178
|
timedOut,
|
|
178
179
|
signal: signal || null,
|
|
179
180
|
networkAllow: Boolean(request.networkAllow),
|
|
180
|
-
allowList: Array.isArray(request.allowList) ? request.allowList : []
|
|
181
|
+
allowList: Array.isArray(request.allowList) ? request.allowList : [],
|
|
182
|
+
browserAccess: Boolean(request.browserAccess)
|
|
181
183
|
}
|
|
182
184
|
});
|
|
183
185
|
});
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import "./default-local-process.js";
|
|
11
11
|
import "./default-local-agent-host.js";
|
|
12
12
|
import "./default-local-intelligence.js";
|
|
13
|
+
import "./adapters/local-intelligence-browser-access.js";
|
|
13
14
|
import { loadAllSandboxAdapters } from "./adapter-loader.js";
|
|
14
15
|
|
|
15
16
|
let baseLoaded = true; // default-local-process registered via static import
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
* timeoutMs: number, // hard cap, capped at SANDBOX_MAX_TIMEOUT_MS
|
|
28
28
|
* networkAllow: boolean, // allow outbound network from inside the sandbox
|
|
29
29
|
* allowList: string[], // hostnames the user explicitly allowed
|
|
30
|
+
* browserAccess: boolean, // row-level browser capability (implies networkAllow);
|
|
31
|
+
* // adapters surface it natively where the target supports
|
|
32
|
+
* // it and always publish GROWTHUB_SANDBOX_BROWSER_ACCESS
|
|
30
33
|
* env: Record<string,string>, // server-resolved env (NEVER sent to browser)
|
|
31
34
|
* envRefSlugs: string[], // ref slugs resolved (kept for record metadata)
|
|
32
35
|
* envRefsMissing: string[], // slugs the server could not resolve (audit-only)
|
|
@@ -99,7 +102,8 @@ function describeRegisteredSandboxAdapters() {
|
|
|
99
102
|
slug,
|
|
100
103
|
label: host?.label || slug,
|
|
101
104
|
binary: host?.binary || null,
|
|
102
|
-
installHint: host?.installHint || null
|
|
105
|
+
installHint: host?.installHint || null,
|
|
106
|
+
browserLane: host?.browser?.lane || "env-signal"
|
|
103
107
|
}))
|
|
104
108
|
: null
|
|
105
109
|
}));
|