@themoltnet/sandbox-gondolin 0.1.0
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/LICENSE +235 -0
- package/README.md +46 -0
- package/dist/index.d.ts +364 -0
- package/dist/index.js +867 -0
- package/package.json +63 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { MemoryProvider } from '@earendil-works/gondolin';
|
|
2
|
+
import { VirtualFileHandle } from '@earendil-works/gondolin';
|
|
3
|
+
import { VM } from '@earendil-works/gondolin';
|
|
4
|
+
|
|
5
|
+
export declare function abortableResource<T>(opts: AbortableResourceOptions<T>): Promise<T>;
|
|
6
|
+
|
|
7
|
+
declare interface AbortableResourceOptions<T> {
|
|
8
|
+
promise: PromiseLike<T>;
|
|
9
|
+
signal: AbortSignal | undefined;
|
|
10
|
+
label: string;
|
|
11
|
+
cleanup: (resource: T) => Promise<void> | void;
|
|
12
|
+
onCleanupError?: (err: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Apply agent env vars to the host process, mirroring `moltnet start`.
|
|
17
|
+
* Resolves relative paths (e.g. GIT_CONFIG_GLOBAL) against the repo root.
|
|
18
|
+
*/
|
|
19
|
+
export declare function activateAgentEnv(agentEnv: Record<string, string | undefined>, repoRoot: string): void;
|
|
20
|
+
|
|
21
|
+
export declare function assertGuestEnvironmentBoundary(options: {
|
|
22
|
+
guestCredentialMode: GuestCredentialMode;
|
|
23
|
+
forwardEnv?: readonly string[];
|
|
24
|
+
sandboxEnv?: Readonly<Record<string, string>>;
|
|
25
|
+
}): void;
|
|
26
|
+
|
|
27
|
+
/** @deprecated Prefer assertGuestEnvironmentBoundary for mode-aware checks. */
|
|
28
|
+
export declare function assertHostAuthenticatedGuestEnvironment(options: {
|
|
29
|
+
forwardEnv?: readonly string[];
|
|
30
|
+
sandboxEnv?: Readonly<Record<string, string>>;
|
|
31
|
+
}): void;
|
|
32
|
+
|
|
33
|
+
export declare class AutoParentMemoryProvider extends MemoryProvider {
|
|
34
|
+
private ensureParentDir;
|
|
35
|
+
mkdir(pathname: string, options?: object): Promise<void | string>;
|
|
36
|
+
mkdirSync(pathname: string, options?: object): void | string;
|
|
37
|
+
open(pathname: string, flags: string, mode?: number): Promise<VirtualFileHandle>;
|
|
38
|
+
openSync(pathname: string, flags: string, mode?: number): VirtualFileHandle;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export declare function delay(ms: number, signal: AbortSignal | undefined, label: string): Promise<void>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Ensure a cached snapshot exists, building one if needed.
|
|
45
|
+
* Returns the absolute path to the qcow2 checkpoint file.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ensureSnapshot(options?: EnsureSnapshotOptions): Promise<string>;
|
|
48
|
+
|
|
49
|
+
export declare interface EnsureSnapshotOptions {
|
|
50
|
+
config?: SnapshotConfig;
|
|
51
|
+
onProgress?: (message: string) => void;
|
|
52
|
+
/** Max number of old snapshots to keep (default 1). */
|
|
53
|
+
maxCached?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the main worktree root (where .moltnet/ lives — it's untracked,
|
|
58
|
+
* only exists in the main worktree, not in git worktrees).
|
|
59
|
+
*/
|
|
60
|
+
export declare function findMainWorktree(startPath?: string): string;
|
|
61
|
+
|
|
62
|
+
/** Commands guaranteed by the base Gondolin snapshot. */
|
|
63
|
+
export declare const GONDOLIN_BASE_EXECUTABLES: readonly string[];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Memory-backed VFS mount used by the daemon to inject task context
|
|
67
|
+
* (#943 slice 1.5). This is a separate top-level mount because Gondolin
|
|
68
|
+
* mounts can't nest. The agent's Gondolin-bound Read tool accepts paths
|
|
69
|
+
* under this prefix (see toGuestPath in tool-operations.ts).
|
|
70
|
+
*
|
|
71
|
+
* Why MemoryProvider rather than a path under the workspace mount:
|
|
72
|
+
* - Injected task context is ephemeral by intent: per-task-attempt input
|
|
73
|
+
* scoped to the VM lifetime. MemoryProvider models that exactly —
|
|
74
|
+
* in-memory, per-VM-instance, zero host artefacts, automatic
|
|
75
|
+
* cleanup on VM close.
|
|
76
|
+
* - Writing under the workspace mount fails in worktrees because we symlink
|
|
77
|
+
* `.moltnet/` to the main repo (so credentials are reachable from
|
|
78
|
+
* worktrees), and Gondolin's RealFSProvider correctly refuses to
|
|
79
|
+
* create paths whose ancestors' realpath escapes the mount root.
|
|
80
|
+
* That refusal is a deliberate sandbox-escape protection, not a
|
|
81
|
+
* bug. See diary semantic entry cd27d9d3-efdc-4aec-ac0d-5fd8ce258d1f
|
|
82
|
+
* and episodic 7affbfeb-18a2-4963-aeac-c177eb2afa2d for the full
|
|
83
|
+
* investigation and the alternatives we rejected.
|
|
84
|
+
*/
|
|
85
|
+
export declare const GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
|
|
86
|
+
|
|
87
|
+
/** @deprecated Use GUEST_TASK_CONTEXT_MOUNT. */
|
|
88
|
+
export declare const GUEST_TASK_SKILLS_MOUNT = "/moltnet-task-context";
|
|
89
|
+
|
|
90
|
+
export declare type GuestCredentialMode = 'guest-config' | 'host-authenticated';
|
|
91
|
+
|
|
92
|
+
export declare class GuestEnvironmentBoundaryError extends Error {
|
|
93
|
+
readonly refusedNames: readonly string[];
|
|
94
|
+
constructor(refusedNames: readonly string[]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check containment for already-resolved lexical or real paths.
|
|
99
|
+
*
|
|
100
|
+
* Callers that accept untrusted paths must resolve/realpath at their I/O
|
|
101
|
+
* boundary first; keeping the platform-specific relative-path rule here avoids
|
|
102
|
+
* subtly different `..` and absolute-path handling across runtime cleanup,
|
|
103
|
+
* session sync, and artifact staging.
|
|
104
|
+
*/
|
|
105
|
+
export declare function isResolvedPathInsideRoot(path: string, root: string): boolean;
|
|
106
|
+
|
|
107
|
+
export declare function loadCredentials(agentDir: string, mode?: GuestCredentialMode, onDiagnostic?: (diagnostic: VmDiagnostic) => void, providerAuth?: ProviderAuthSource): VmCredentials;
|
|
108
|
+
|
|
109
|
+
export declare interface ManagedVm {
|
|
110
|
+
vm: VM;
|
|
111
|
+
credentials: VmCredentials;
|
|
112
|
+
mountPath: string;
|
|
113
|
+
guestWorkspace: string;
|
|
114
|
+
agentDir: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export declare interface ProviderAuthSource {
|
|
118
|
+
/** Read the auth blob from the host, or return null when absent. */
|
|
119
|
+
load(): string | null;
|
|
120
|
+
/** Absolute guest path to write the blob to (mode 0600). */
|
|
121
|
+
guestPath: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export declare function resolveVfsShadowConfig(config: SandboxConfig | undefined): {
|
|
125
|
+
mode: 'none';
|
|
126
|
+
patterns: [];
|
|
127
|
+
} | {
|
|
128
|
+
mode: 'deny' | 'tmpfs';
|
|
129
|
+
patterns: string[];
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export declare function resolveVmAgentDir(config: {
|
|
133
|
+
agentName: string;
|
|
134
|
+
agentRootDir?: string;
|
|
135
|
+
}): string;
|
|
136
|
+
|
|
137
|
+
export declare interface ResumeCommand {
|
|
138
|
+
/** Shell command, same semantics as the string form. */
|
|
139
|
+
run: string;
|
|
140
|
+
/** Optional generic runtime predicate for whether this step should run. */
|
|
141
|
+
when?: ResumeCommandWhen;
|
|
142
|
+
/** Additional attempts on non-zero exit. Default 0. */
|
|
143
|
+
retries?: number;
|
|
144
|
+
/** Linear backoff between attempts in ms. Delay before attempt N+1 is
|
|
145
|
+
* `(N + 1) * retryBackoffMs` (so 2s, 4s, … with the default). */
|
|
146
|
+
retryBackoffMs?: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Structured form of a resume command with optional retry policy. */
|
|
150
|
+
export declare interface ResumeCommandWhen {
|
|
151
|
+
/**
|
|
152
|
+
* Effective workspace mode(s) that should run this command.
|
|
153
|
+
* Evaluated by the runtime from the mounted workspace shape rather than
|
|
154
|
+
* from task type semantics.
|
|
155
|
+
*/
|
|
156
|
+
workspaceMode?: ('shared_mount' | 'dedicated_worktree' | 'scratch_mount')[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resume a VM from a checkpoint, inject credentials, configure egress +
|
|
161
|
+
* TLS. Returns the managed VM handle.
|
|
162
|
+
*/
|
|
163
|
+
export declare function resumeVm(config: VmConfig): Promise<ManagedVm>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Rewrite host-absolute paths inside an agent gitconfig to VM-local
|
|
167
|
+
* equivalents before injecting it into the guest.
|
|
168
|
+
*
|
|
169
|
+
* Two rewrites:
|
|
170
|
+
* - `signingKey = <host path>` → `<vmSshDir>/id_ed25519`
|
|
171
|
+
* - `... credential-helper --credentials <host moltnet.json>`
|
|
172
|
+
* → `<vmAgentDir>/moltnet.json`
|
|
173
|
+
*
|
|
174
|
+
* The credential-helper line is generated host-side by `moltnet github setup`
|
|
175
|
+
* with a host-absolute `--credentials` path; inside the guest that path is
|
|
176
|
+
* invalid, so it must point at the VM-side moltnet.json. The `insteadOf`
|
|
177
|
+
* rewrite rule and every other line are workspace-independent and pass through
|
|
178
|
+
* unchanged. A gitconfig without a credential helper is rewritten only for
|
|
179
|
+
* `signingKey`.
|
|
180
|
+
*
|
|
181
|
+
* This is the single source of truth for git push auth in the guest: the
|
|
182
|
+
* injected gitconfig carries the tokenless mint-on-demand helper, so the VM
|
|
183
|
+
* no longer hand-rolls a credential-helper script or runs an imperative
|
|
184
|
+
* `git config --global ... insteadOf` against the guest $HOME.
|
|
185
|
+
*/
|
|
186
|
+
export declare function rewriteGitconfigPaths(gitconfig: string, vmSshDir: string, vmAgentDir: string): string;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
|
|
190
|
+
*
|
|
191
|
+
* Fields rewritten:
|
|
192
|
+
* ssh.private_key_path → <vmSshDir>/<basename of original>
|
|
193
|
+
* ssh.public_key_path → <vmSshDir>/<basename of original>
|
|
194
|
+
* git.config_path → <vmAgentDir>/gitconfig
|
|
195
|
+
* github.private_key_path → <vmAgentDir>/<pemFilename> (if present)
|
|
196
|
+
*
|
|
197
|
+
* All other fields are passed through unchanged.
|
|
198
|
+
* Throws if moltnetJson is not valid JSON — callers must not inject a broken
|
|
199
|
+
* moltnet.json into the guest.
|
|
200
|
+
*/
|
|
201
|
+
export declare function rewriteMoltnetJsonPaths(moltnetJson: string, vmAgentDir: string, vmSshDir: string, githubAppPemFilename: string | null): string;
|
|
202
|
+
|
|
203
|
+
export declare interface SandboxConfig {
|
|
204
|
+
/**
|
|
205
|
+
* Operator-owned snapshot build settings. Runtime profiles must never
|
|
206
|
+
* populate this field.
|
|
207
|
+
*/
|
|
208
|
+
snapshot?: {
|
|
209
|
+
/** Shell commands to run after the base setup. */
|
|
210
|
+
setupCommands?: string[];
|
|
211
|
+
/** Additional hosts to allow network access during build. */
|
|
212
|
+
allowedHosts?: string[];
|
|
213
|
+
/** Overlay disk size (default '3G'). */
|
|
214
|
+
overlaySize?: string;
|
|
215
|
+
};
|
|
216
|
+
/** Runtime network egress policy. Separate from snapshot build access. */
|
|
217
|
+
network?: {
|
|
218
|
+
/** Additional host patterns allowed while the VM is running.
|
|
219
|
+
* Internal and private address resolution remains blocked. */
|
|
220
|
+
allowedHosts?: string[];
|
|
221
|
+
/** Host patterns explicitly allowed to resolve to internal/private IPs. */
|
|
222
|
+
allowedInternalHosts?: string[];
|
|
223
|
+
};
|
|
224
|
+
/** Operator-owned shell commands to run every VM resume, after platform setup
|
|
225
|
+
* (TLS, DNS, git safe.directory, tmpfs node_modules) and before
|
|
226
|
+
* the agent session starts. Use for per-session bootstrap that
|
|
227
|
+
* doesn't belong baked into the snapshot.
|
|
228
|
+
*
|
|
229
|
+
* Not included in the snapshot cache key — changes here apply on
|
|
230
|
+
* every resume without triggering a snapshot rebuild. Each command
|
|
231
|
+
* runs in a fresh shell with `set -eu` and `set -o pipefail`; a
|
|
232
|
+
* non-zero exit (including from any segment of a pipeline) aborts
|
|
233
|
+
* resume with the failing command's stderr/stdout tail.
|
|
234
|
+
*
|
|
235
|
+
* Each entry is either a raw string (no retries) or an object
|
|
236
|
+
* `{ run, when?, retries?, retryBackoffMs? }`. `when` gates the
|
|
237
|
+
* command on generic runtime properties such as effective
|
|
238
|
+
* `workspaceMode`; this keeps sandbox policy decoupled from task
|
|
239
|
+
* types. `retries` is the number of ADDITIONAL attempts after the
|
|
240
|
+
* first failure (default 0 = no retry). Use for steps that hit the
|
|
241
|
+
* network and may legitimately race DHCP/registry availability on a
|
|
242
|
+
* fresh resume (e.g. `pnpm install`). The wrapped command must be
|
|
243
|
+
* idempotent. */
|
|
244
|
+
resumeCommands?: (string | ResumeCommand)[];
|
|
245
|
+
/** VFS shadow settings — hide host paths from the guest. */
|
|
246
|
+
vfs?: {
|
|
247
|
+
/** Paths (relative to workspace root) to shadow from the host mount. */
|
|
248
|
+
shadow?: string[];
|
|
249
|
+
/** What to do with writes to shadowed paths: 'deny' or 'tmpfs' (default 'tmpfs'). */
|
|
250
|
+
shadowMode?: 'deny' | 'tmpfs';
|
|
251
|
+
};
|
|
252
|
+
/** Environment variable overrides for the guest VM (applied on top of defaults). */
|
|
253
|
+
env?: Record<string, string>;
|
|
254
|
+
/** Host-side escape hatch policy. Applies only to `moltnet_host_exec`. */
|
|
255
|
+
hostExec?: {
|
|
256
|
+
/**
|
|
257
|
+
* `true` auto-approves every allowed executable. An array auto-approves
|
|
258
|
+
* only commands matching one of the executable/argument rules.
|
|
259
|
+
*/
|
|
260
|
+
autoApprove?: boolean | {
|
|
261
|
+
executable: string;
|
|
262
|
+
argsPrefix?: string[];
|
|
263
|
+
argsContains?: string[];
|
|
264
|
+
argsExcludes?: string[];
|
|
265
|
+
}[];
|
|
266
|
+
};
|
|
267
|
+
/** VM resource allocation. */
|
|
268
|
+
resources?: {
|
|
269
|
+
/** Memory size in qemu syntax (default '1G'). */
|
|
270
|
+
memory?: string;
|
|
271
|
+
/** CPU count (default 2). */
|
|
272
|
+
cpus?: number;
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export declare function shouldRunResumeCommand(entry: string | ResumeCommand, ctx: {
|
|
277
|
+
workspaceMode: 'shared_mount' | 'dedicated_worktree' | 'scratch_mount';
|
|
278
|
+
}): boolean;
|
|
279
|
+
|
|
280
|
+
export declare function shouldShadowNodeModulesPath(pathname: string): boolean;
|
|
281
|
+
|
|
282
|
+
/** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
|
|
283
|
+
export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
|
|
284
|
+
|
|
285
|
+
export declare function throwIfAborted(signal: AbortSignal | undefined, label: string): void;
|
|
286
|
+
|
|
287
|
+
export declare interface VmConfig {
|
|
288
|
+
/** Absolute path to the qcow2 checkpoint. */
|
|
289
|
+
checkpointPath: string;
|
|
290
|
+
/** MoltNet agent name (used to resolve credentials). */
|
|
291
|
+
agentName: string;
|
|
292
|
+
/**
|
|
293
|
+
* Trust boundary for guest credentials. `guest-config` injects the complete
|
|
294
|
+
* legacy agent directory. `host-authenticated` never reads or injects it and
|
|
295
|
+
* relies exclusively on the supplied host-side Agent for MoltNet operations.
|
|
296
|
+
*/
|
|
297
|
+
guestCredentialMode?: GuestCredentialMode;
|
|
298
|
+
/**
|
|
299
|
+
* Host root that owns `.moltnet/<agentName>/`.
|
|
300
|
+
*
|
|
301
|
+
* Defaults to the main git worktree for backwards compatibility. Daemon
|
|
302
|
+
* callers pass the sandbox root so non-git scratch/shared tasks can boot.
|
|
303
|
+
*/
|
|
304
|
+
agentRootDir?: string;
|
|
305
|
+
/** Host directory to mount into the VM. */
|
|
306
|
+
mountPath: string;
|
|
307
|
+
/** Effective workspace shape selected by the caller. */
|
|
308
|
+
workspaceMode?: 'shared_mount' | 'dedicated_worktree' | 'scratch_mount';
|
|
309
|
+
/** Additional hosts to allow in egress policy. */
|
|
310
|
+
extraAllowedHosts?: string[];
|
|
311
|
+
/** Full sandbox config (vfs shadows, env overrides). */
|
|
312
|
+
sandboxConfig?: SandboxConfig;
|
|
313
|
+
/**
|
|
314
|
+
* Host environment variable names to copy into the VM process.
|
|
315
|
+
*
|
|
316
|
+
* Runtime profiles use this for provider API keys: `requiredEnv` proves the
|
|
317
|
+
* daemon host has the secret, and this allowlist forwards only those names
|
|
318
|
+
* into the guest without storing secret values in the profile.
|
|
319
|
+
*/
|
|
320
|
+
forwardEnv?: string[];
|
|
321
|
+
/** Structured credential-boundary diagnostics for daemon loggers. */
|
|
322
|
+
onDiagnostic?: (diagnostic: VmDiagnostic) => void;
|
|
323
|
+
/** Abort resume/setup work, closing any live VM owned by resumeVm. */
|
|
324
|
+
signal?: AbortSignal;
|
|
325
|
+
/**
|
|
326
|
+
* Coding-agent provider authentication to place in the guest (for example
|
|
327
|
+
* Pi's `auth.json`). The sandbox knows nothing about the provider: the
|
|
328
|
+
* runtime above it supplies the loader and the guest path. When omitted,
|
|
329
|
+
* nothing is written and the guest relies on forwarded env providers.
|
|
330
|
+
*/
|
|
331
|
+
providerAuth?: ProviderAuthSource;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export declare interface VmCredentials {
|
|
335
|
+
/** Empty in host-authenticated mode; retained as strings for API stability. */
|
|
336
|
+
moltnetJson: string;
|
|
337
|
+
/** Empty in host-authenticated mode; retained as strings for API stability. */
|
|
338
|
+
agentEnvRaw: string;
|
|
339
|
+
/**
|
|
340
|
+
* Provider auth blob supplied by `VmConfig.providerAuth`, or null when the
|
|
341
|
+
* runtime supplied none or the host file is absent — the guest then relies
|
|
342
|
+
* on env-var providers (`ANTHROPIC_API_KEY`, etc.) carried via `agentEnv`
|
|
343
|
+
* and the forwarded host environment instead. CI uses this path.
|
|
344
|
+
*/
|
|
345
|
+
providerAuthJson: string | null;
|
|
346
|
+
agentEnv: Record<string, string | undefined>;
|
|
347
|
+
gitconfig: string | null;
|
|
348
|
+
sshPrivateKey: string | null;
|
|
349
|
+
sshPublicKey: string | null;
|
|
350
|
+
allowedSigners: string | null;
|
|
351
|
+
/** Raw PEM content of the GitHub App private key, or null if not configured. */
|
|
352
|
+
githubAppPem: string | null;
|
|
353
|
+
/** VM-local filename for the GitHub App PEM (basename of host path), or null. */
|
|
354
|
+
githubAppPemFilename: string | null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export declare interface VmDiagnostic {
|
|
358
|
+
event: 'vm.credentials.mode' | 'vm.credentials.github_key_missing';
|
|
359
|
+
level: 'info' | 'warning';
|
|
360
|
+
message: string;
|
|
361
|
+
credentialMode: GuestCredentialMode;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export { }
|