runcloud 0.1.117 → 0.1.119
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/dist/diagnose.js +29 -21
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.1.119.tgz +0 -0
- package/skills/run-cloud/SKILL.md +7 -1
- package/skills/run-cloud-sandboxes/SKILL.md +50 -1
- package/skills/run-cloud-sandboxes/agents/openai.yaml +2 -2
- package/runcloud-0.1.117.tgz +0 -0
package/dist/diagnose.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
-
import { GoogleAuth
|
|
3
|
+
import { GoogleAuth } from 'google-auth-library';
|
|
4
4
|
import { renderSessionDiagnosticTerminal, writeSessionDiagnosticBundle, } from './sessionDiagnostics.js';
|
|
5
5
|
const execFileAsync = promisify(execFile);
|
|
6
6
|
const AUTH_ATTEMPT_TIMEOUT_MS = 10_000;
|
|
@@ -87,10 +87,8 @@ function extractAuthorization(headers) {
|
|
|
87
87
|
const h = headers;
|
|
88
88
|
return h?.Authorization ?? h?.authorization;
|
|
89
89
|
}
|
|
90
|
-
async function gcloudIdentityToken(audience
|
|
91
|
-
const args = ['auth', 'print-identity-token',
|
|
92
|
-
if (serviceAccount)
|
|
93
|
-
args.push(`--impersonate-service-account=${serviceAccount}`);
|
|
90
|
+
async function gcloudIdentityToken(audience) {
|
|
91
|
+
const args = ['auth', 'print-identity-token', '--audiences=' + audience];
|
|
94
92
|
const { stdout } = await execFileAsync('gcloud', args, { timeout: 15_000, maxBuffer: 1024 * 1024 });
|
|
95
93
|
const token = stdout.trim();
|
|
96
94
|
if (!token)
|
|
@@ -123,22 +121,32 @@ function exactAudienceAuthorization(authorization, audience) {
|
|
|
123
121
|
}
|
|
124
122
|
return authorization;
|
|
125
123
|
}
|
|
126
|
-
async function
|
|
127
|
-
return withDeadline('service-account
|
|
124
|
+
async function serviceAccountIdentityToken(audience, serviceAccount) {
|
|
125
|
+
return withDeadline('service-account ID-token minting', async (signal) => {
|
|
128
126
|
const sourceClient = await new GoogleAuth({
|
|
129
127
|
projectId: AUTH_DISCOVERY_PROJECT_ID,
|
|
130
128
|
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
|
131
129
|
clientOptions: { transporterOptions: { signal, timeout: AUTH_ATTEMPT_TIMEOUT_MS } },
|
|
132
130
|
}).getClient();
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
const sourceToken = await sourceClient.getAccessToken();
|
|
132
|
+
const authorization = typeof sourceToken === 'string' ? sourceToken : sourceToken.token;
|
|
133
|
+
if (!authorization)
|
|
134
|
+
throw new Error('ADC returned an empty source access token');
|
|
135
|
+
const response = await fetch('https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/' +
|
|
136
|
+
encodeURIComponent(serviceAccount) + ':generateIdToken', {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: 'Bearer ' + authorization,
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify({ audience, includeEmail: true }),
|
|
143
|
+
signal,
|
|
139
144
|
});
|
|
140
|
-
const
|
|
141
|
-
|
|
145
|
+
const body = await response.json().catch(() => ({}));
|
|
146
|
+
if (!response.ok || typeof body.token !== 'string') {
|
|
147
|
+
throw new Error("generateIdToken failed (" + response.status + "): " + JSON.stringify(body.error ?? body));
|
|
148
|
+
}
|
|
149
|
+
return exactAudienceAuthorization('Bearer ' + body.token, audience);
|
|
142
150
|
});
|
|
143
151
|
}
|
|
144
152
|
export async function resolveOpsAuthHeader(env = process.env, requestedAudience, requestedServiceAccount) {
|
|
@@ -158,13 +166,13 @@ export async function resolveOpsAuthHeader(env = process.env, requestedAudience,
|
|
|
158
166
|
catch (error) {
|
|
159
167
|
adcError = error instanceof Error ? error.message : String(error);
|
|
160
168
|
}
|
|
161
|
-
let
|
|
169
|
+
let serviceAccountError = 'not configured';
|
|
162
170
|
if (serviceAccount && env.DIAGNOSE_DISABLE_IMPERSONATION !== '1') {
|
|
163
171
|
try {
|
|
164
|
-
return await
|
|
172
|
+
return await serviceAccountIdentityToken(audience, serviceAccount);
|
|
165
173
|
}
|
|
166
174
|
catch (error) {
|
|
167
|
-
|
|
175
|
+
serviceAccountError = error instanceof Error ? error.message : String(error);
|
|
168
176
|
}
|
|
169
177
|
}
|
|
170
178
|
const token = env.DIAGNOSE_OPS_TOKEN;
|
|
@@ -173,13 +181,13 @@ export async function resolveOpsAuthHeader(env = process.env, requestedAudience,
|
|
|
173
181
|
}
|
|
174
182
|
if (env.DIAGNOSE_DISABLE_GCLOUD !== '1') {
|
|
175
183
|
try {
|
|
176
|
-
return exactAudienceAuthorization(`Bearer ${await gcloudIdentityToken(audience
|
|
184
|
+
return exactAudienceAuthorization(`Bearer ${await gcloudIdentityToken(audience)}`, audience);
|
|
177
185
|
}
|
|
178
186
|
catch (error) {
|
|
179
187
|
const gcloudError = error instanceof Error ? error.message : String(error);
|
|
180
188
|
throw new Error(`No usable ops credential for audience ${audience}.\n` +
|
|
181
189
|
`ADC failed: ${truncate(adcError, 500)}\n` +
|
|
182
|
-
`Service-account
|
|
190
|
+
`Service-account ID-token minting failed: ${truncate(serviceAccountError, 500)}\n` +
|
|
183
191
|
`gcloud failed: ${truncate(gcloudError, 500)}\n\n` +
|
|
184
192
|
'Repair access by adding the Google identity to control-plane `operators`, applying Terraform, ' +
|
|
185
193
|
'and running `gcloud auth application-default login`, then rerun. ' +
|
|
@@ -188,7 +196,7 @@ export async function resolveOpsAuthHeader(env = process.env, requestedAudience,
|
|
|
188
196
|
}
|
|
189
197
|
throw new Error(`No ops credential available for audience ${audience}. ` +
|
|
190
198
|
`ADC failed: ${truncate(adcError, 300)}. ` +
|
|
191
|
-
`Service-account
|
|
199
|
+
`Service-account ID-token minting failed: ${truncate(serviceAccountError, 300)}. ` +
|
|
192
200
|
'Run `gcloud auth application-default login` after the operator Terraform has been applied, or export DIAGNOSE_OPS_TOKEN.');
|
|
193
201
|
}
|
|
194
202
|
function truncate(value, max = 200) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.1.
|
|
1
|
+
export const CLI_VERSION = '0.1.119';
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -12,11 +12,17 @@ Keep this skill as a router. Do not duplicate operational instructions here.
|
|
|
12
12
|
iframe embeds, Metro tunnels, mobile assets, and mobile session cleanup.
|
|
13
13
|
- Use `$run-cloud-sandboxes` for microVM sandboxes, command execution, files,
|
|
14
14
|
snapshots, images, SSH, secrets, public ports, desktop automation, resource
|
|
15
|
-
sizing, and sandbox
|
|
15
|
+
sizing, sandbox cleanup, and investigating a broken sandbox.
|
|
16
16
|
- Use both focused skills when a workflow genuinely combines mobile sessions
|
|
17
17
|
and sandboxes. Follow each skill's authentication, security, metering, and
|
|
18
18
|
cleanup guardrails.
|
|
19
19
|
|
|
20
|
+
Symptom phrasing ("is down", "stuck", "hung", "not responding", "why did it
|
|
21
|
+
stop") names no resource, so route on the id in hand: a sandbox id or tag goes to
|
|
22
|
+
`$run-cloud-sandboxes`, a session id to `$run-cloud-ios-simulator`. Each says
|
|
23
|
+
what its own signals do and do not mean, which is the part that cannot be
|
|
24
|
+
guessed from the outside.
|
|
25
|
+
|
|
20
26
|
If the request is ambiguous, infer the product from the resource noun and
|
|
21
27
|
desired outcome. Ask only when choosing the wrong product would materially
|
|
22
28
|
change the work.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: run-cloud-sandboxes
|
|
3
|
-
description: Operate run.cloud microVM sandboxes with the CLI or TypeScript SDK. Use for creating isolated compute, running commands, moving files, managing snapshots or images, exposing ports, using SSH or desktop automation, attaching secrets, sizing resources, or cleaning up sandboxes.
|
|
3
|
+
description: Operate and debug run.cloud microVM sandboxes with the CLI or TypeScript SDK. Use for creating isolated compute, running commands, moving files, managing snapshots or images, exposing ports, using SSH or desktop automation, attaching secrets, sizing resources, or cleaning up sandboxes. Also use to investigate a sandbox that is down, stuck, hung, unreachable, or not responding, to find out why one stopped or was destroyed, or to trace a sandbox from a log line by tag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operate run.cloud Sandboxes
|
|
@@ -274,6 +274,55 @@ Key names do not match the env vars, and `sops` needs
|
|
|
274
274
|
see the Secrets section of the root `CLAUDE.md`/`AGENTS.md` before concluding
|
|
275
275
|
you lack access.
|
|
276
276
|
|
|
277
|
+
## What the Signals Mean When a Sandbox Misbehaves
|
|
278
|
+
|
|
279
|
+
`state` describes the microVM lease, not the workload inside it. A sandbox whose
|
|
280
|
+
process died an hour ago still reads `running`, so `running` on its own never
|
|
281
|
+
means healthy.
|
|
282
|
+
|
|
283
|
+
The console log is one file per sandbox carrying boot output, everything `exec`
|
|
284
|
+
printed, and lifecycle markers, interleaved in the order they happened.
|
|
285
|
+
`runcloud sandbox logs <id>` serves it, and it survives destroy: the host renames
|
|
286
|
+
the file rather than deleting it, so a post-mortem read works on a sandbox that
|
|
287
|
+
no longer exists.
|
|
288
|
+
|
|
289
|
+
Two limits that change what you can conclude from it:
|
|
290
|
+
|
|
291
|
+
- It is capped at 10 MiB and trimmed to the newest 5 MiB, so on a chatty workload
|
|
292
|
+
early output is gone, not merely paged out. An empty-looking start is a trim,
|
|
293
|
+
not proof the process printed nothing.
|
|
294
|
+
- `systemd-run` output goes to the journal, not the console, so those units never
|
|
295
|
+
appear here at all. Read `journalctl -u <unit>` inside the sandbox instead.
|
|
296
|
+
|
|
297
|
+
### Lifecycle markers
|
|
298
|
+
|
|
299
|
+
The host writes a marker into the console on every transition:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
--- sandbox destroyed: timeout-sweep at 2026-08-05T11:02:11Z ---
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The events are `created`, `paused`, `resumed`, `stopped`, and `destroyed`. The
|
|
306
|
+
token after the colon is the control plane's actor, written verbatim so it can be
|
|
307
|
+
matched rather than parsed; the sentence around it is not a contract. Only stops
|
|
308
|
+
carry a reason, so `created` and `resumed` appear without one.
|
|
309
|
+
|
|
310
|
+
| actor | what it means |
|
|
311
|
+
| --- | --- |
|
|
312
|
+
| `timeout-sweep` | hit its `--timeout` lifetime cap. Not a crash |
|
|
313
|
+
| `idle-sweep` | idle-paused after `--idle-pause` seconds. Resumable, and not a failure |
|
|
314
|
+
| `retention-sweep` | destroyed after its 48h parked window expired |
|
|
315
|
+
| `pause` `resume` `stop` `archive` `restore` | a caller requested exactly this |
|
|
316
|
+
| `api` `api-force` | a caller destroyed it. `api-force` bypassed a wedged guest |
|
|
317
|
+
| `health-sweep` `host-reconcile` `reap-sweep` | the platform reclaimed it |
|
|
318
|
+
| `boot-failure` `placement-timeout` `request-timeout` `scheduler` | it never started. Capacity or image, not the workload |
|
|
319
|
+
| `image-built` `image-build-failure` | an async image build finished or failed |
|
|
320
|
+
| `startup-recover` `volume-lease-invalid` `replica-fork-resume-failure` | platform recovery paths |
|
|
321
|
+
|
|
322
|
+
A sandbox that failed before it ever reached a host has no console, because the
|
|
323
|
+
file is created at boot. Those carry a `Last error` row on
|
|
324
|
+
`runcloud sandbox get` instead, which is then the only record of the cause.
|
|
325
|
+
|
|
277
326
|
## Guardrails
|
|
278
327
|
|
|
279
328
|
- Destroy every sandbox created during a task unless the user explicitly asks
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Run Cloud Sandboxes"
|
|
3
|
-
short_description: "Operate secure remote microVM sandboxes"
|
|
4
|
-
default_prompt: "Use $run-cloud-sandboxes to create, operate, and clean up run.cloud sandboxes."
|
|
3
|
+
short_description: "Operate and debug secure remote microVM sandboxes"
|
|
4
|
+
default_prompt: "Use $run-cloud-sandboxes to create, operate, debug, and clean up run.cloud sandboxes."
|
package/runcloud-0.1.117.tgz
DELETED
|
Binary file
|