@themoltnet/pi-runtime 0.12.0 → 0.12.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/dist/index.d.ts CHANGED
@@ -21,7 +21,6 @@ import { FindOperations } from '@earendil-works/pi-coding-agent';
21
21
  import { GONDOLIN_BASE_EXECUTABLES } from '@themoltnet/sandbox-gondolin';
22
22
  import { GrepToolDetails } from '@earendil-works/pi-coding-agent';
23
23
  import { GrepToolInput } from '@earendil-works/pi-coding-agent';
24
- import { GuestCredentialMode } from '@themoltnet/sandbox-gondolin';
25
24
  import { GuestEnvironmentBoundaryError } from '@themoltnet/sandbox-gondolin';
26
25
  import { HostCapabilityContribution } from '@themoltnet/agent-runtime';
27
26
  import { HostCapabilityEvidenceLogger } from '@themoltnet/agent-runtime';
@@ -34,7 +33,6 @@ import { ManagedVm } from '@themoltnet/sandbox-gondolin';
34
33
  import { Model } from '@earendil-works/pi-ai';
35
34
  import { ModelRegistry } from '@earendil-works/pi-coding-agent';
36
35
  import { prepareBrokeredHttpSecrets } from '@themoltnet/sandbox-gondolin';
37
- import { ProviderAuthSource } from '@themoltnet/sandbox-gondolin';
38
36
  import { Readable } from 'node:stream';
39
37
  import { ReadOperations } from '@earendil-works/pi-coding-agent';
40
38
  import { ResumeCommand } from '@themoltnet/sandbox-gondolin';
@@ -272,7 +270,10 @@ declare type ContextRef = {
272
270
  content: string;
273
271
  };
274
272
 
275
- export declare function createGondolinBashOps(vm: VM, localCwd: string, guestWorkspace: string): BashOperations;
273
+ export declare function createGondolinBashOps(vm: VM, localCwd: string, guestWorkspace: string, config: {
274
+ lifecycle: GondolinToolLifecycle;
275
+ retireVm: (retirement: GondolinVmRetirement) => Promise<void>;
276
+ }): BashOperations;
276
277
 
277
278
  export declare function createGondolinEditOps(vm: VM, localCwd: string, guestWorkspace: string): EditOperations;
278
279
 
@@ -286,8 +287,14 @@ export declare function createGondolinToolDefinitions(config: {
286
287
  vm: VM;
287
288
  cwdPath: string;
288
289
  guestWorkspace: string;
290
+ lifecycle: GondolinToolLifecycle;
291
+ retireVm: (retirement: GondolinVmRetirement) => Promise<void>;
289
292
  }): ToolDefinition[];
290
293
 
294
+ export declare function createGondolinToolLifecycle(config?: {
295
+ onRetired?: (retirement: GondolinVmRetirement) => void;
296
+ }): GondolinToolLifecycle;
297
+
291
298
  export declare function createGondolinWriteOps(vm: VM, localCwd: string, guestWorkspace: string): WriteOperations;
292
299
 
293
300
  /**
@@ -527,8 +534,6 @@ export declare interface ExecutePiTaskOptions {
527
534
  agentName: string;
528
535
  /** Already-authenticated host-side Agent. Daemon callers always supply it. */
529
536
  moltnetAgent?: Agent;
530
- /** Explicit trust boundary for MoltNet credentials inside the guest. */
531
- guestCredentialMode?: GuestCredentialMode;
532
537
  /**
533
538
  * Host root that owns `.moltnet/<agentName>/`.
534
539
  *
@@ -786,14 +791,34 @@ export declare interface GondolinTemplateResolveContext {
786
791
  onProgress?: (message: string) => void;
787
792
  }
788
793
 
794
+ export declare interface GondolinToolLifecycle {
795
+ assertActive(): void;
796
+ getRetirement(): GondolinVmRetirement | null;
797
+ markRetired(retirement: GondolinVmRetirement): void;
798
+ }
799
+
800
+ export declare class GondolinVmRetiredError extends Error {
801
+ readonly retirement: GondolinVmRetirement;
802
+ readonly code = "sandbox_retired";
803
+ constructor(retirement: GondolinVmRetirement, options?: ErrorOptions);
804
+ }
805
+
806
+ export declare interface GondolinVmRetirement {
807
+ backendRetired: boolean;
808
+ reason: 'backend-retired' | 'backend-retirement-failed';
809
+ trigger: GondolinVmRetirementTrigger;
810
+ }
811
+
812
+ export declare type GondolinVmRetirementTrigger = 'cancellation' | 'timeout';
813
+
814
+ export declare function guardGondolinToolDefinitions(tools: readonly ToolDefinition[], lifecycle: GondolinToolLifecycle): ToolDefinition[];
815
+
789
816
  export declare const GUEST_ALLOWED_SIGNERS_PATH = "/home/agent/.config/moltnet/allowed_signers";
790
817
 
791
818
  export declare const GUEST_GITCONFIG_PATH = "/home/agent/.config/moltnet/gitconfig";
792
819
 
793
820
  export declare const GUEST_SIGNER_SOCKET = "/run/moltnet/signer.sock";
794
821
 
795
- export { GuestCredentialMode }
796
-
797
822
  export { GuestEnvironmentBoundaryError }
798
823
 
799
824
  /**
@@ -989,9 +1014,6 @@ export declare function normalizeRetryTriageResult(value: unknown): PiRetryTriag
989
1014
 
990
1015
  export declare const PI_EXECUTOR_MANIFEST_VERSION: "moltnet:executor-manifest:v1";
991
1016
 
992
- /** Guest path where Pi expects its auth blob. */
993
- export declare const PI_GUEST_AUTH_PATH = "/home/agent/.pi/agent/auth.json";
994
-
995
1017
  export declare const PI_RUNTIME_DEFINITION_VERSION = "moltnet:pi-runtime:v1";
996
1018
 
997
1019
  export declare interface PiBrokeredHttpSecretContribution {
@@ -1092,13 +1114,6 @@ export declare interface PiOtelOptions {
1092
1114
  onSessionContextChange?: (context: Context | undefined) => void;
1093
1115
  }
1094
1116
 
1095
- /**
1096
- * Pi's provider authentication as a sandbox `ProviderAuthSource`. CI writes
1097
- * `auth.json` under `PI_CODING_AGENT_DIR`; local runs fall back to the
1098
- * canonical `~/.pi/agent` dir when the override is unset.
1099
- */
1100
- export declare function piProviderAuth(): ProviderAuthSource;
1101
-
1102
1117
  export declare type PiRetryTriage = (input: PiRetryTriageInput) => Promise<PiRetryTriageResult>;
1103
1118
 
1104
1119
  export declare type PiRetryTriageConfidence = RetryTriageConfidence;
@@ -1249,8 +1264,6 @@ declare interface PiWorkspaceSeedPlan {
1249
1264
 
1250
1265
  export { prepareBrokeredHttpSecrets }
1251
1266
 
1252
- export { ProviderAuthSource }
1253
-
1254
1267
  export declare interface ProviderErrorRetryEvent extends Record<string, unknown> {
1255
1268
  event: 'provider_error_retry';
1256
1269
  retry: number;
@@ -1283,7 +1296,7 @@ export declare interface ResolvedGondolinTemplate {
1283
1296
  resumeCommands: readonly ResumeCommand[];
1284
1297
  }
1285
1298
 
1286
- export declare function resolveHostExecBaseEnv(guestCredentialMode: GuestCredentialMode, agentEnv: Readonly<Record<string, string | undefined>>): Set<string>;
1299
+ export declare function resolveHostExecBaseEnv(agentEnv: Readonly<Record<string, string | undefined>>): Set<string>;
1287
1300
 
1288
1301
  /**
1289
1302
  * Resolve the session's tool policy at start-up.
@@ -1329,8 +1342,10 @@ export declare function resolveTaskWorktreePath(mainRepo: string, workspaceId: s
1329
1342
  export { ResumeCommand }
1330
1343
 
1331
1344
  /**
1332
- * Resume a Gondolin VM for a Pi session. Identical to the sandbox package's
1333
- * `resumeVm`, with Pi's provider auth supplied unless the caller overrides it.
1345
+ * Resume a Gondolin VM for a Pi session. The Pi coding-agent session and its
1346
+ * model calls run host-side (`createAgentSession` reads the host `~/.pi/agent`
1347
+ * auth), so the guest carries no provider auth — it only executes Gondolin
1348
+ * tools via `vm.exec`. This is a thin pass-through to the sandbox package.
1334
1349
  */
1335
1350
  export declare function resumeVm(config: VmConfig): Promise<ManagedVm>;
1336
1351
 
@@ -1369,12 +1384,6 @@ declare interface RuntimeInstructorSandbox {
1369
1384
  origin: string;
1370
1385
  operations: readonly string[];
1371
1386
  }[];
1372
- /**
1373
- * Guest credential mode. In `host-authenticated` the guest CLI carries no
1374
- * identity credentials, so REST-backed CLI paths (e.g. `moltnet entry
1375
- * create-signed`) cannot authenticate; host-side SDK tools must be used.
1376
- */
1377
- guestCredentialMode?: 'guest-config' | 'host-authenticated';
1378
1387
  }
1379
1388
 
1380
1389
  declare interface RuntimeInstructorToolPolicy {
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { createHash as createHash$1 } from "crypto";
4
4
  import { FREEFORM_TYPE, SUBMIT_OUTPUT_GATE_ID, buildTaskUserPrompt, createHostCapabilityRouter, defineHostCapability, getSubmitOutputContract, materializeTaskOutput, mergeRuntimeProfileContext, resolveTaskContext, taskTypeUsesSubagents, traceRuntimePhase, validateTaskOutput, validateTaskSubmission } from "@themoltnet/agent-runtime";
5
5
  import { Type } from "typebox";
6
6
  import { execFileSync } from "node:child_process";
7
- import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
7
+ import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, realpathSync, rmSync } from "node:fs";
8
8
  import { mkdir, realpath, stat } from "node:fs/promises";
9
9
  import path, { isAbsolute, join, relative, resolve, sep } from "node:path";
10
10
  import { pipeline } from "node:stream/promises";
@@ -15,7 +15,7 @@ import { base32 } from "multiformats/bases/base32";
15
15
  import { CID } from "multiformats/cid";
16
16
  import * as raw from "multiformats/codecs/raw";
17
17
  import { create } from "multiformats/hashes/digest";
18
- import { BrokeredHttpSecretBoundaryError, GONDOLIN_BASE_EXECUTABLES, GUEST_TASK_CONTEXT_MOUNT, GuestEnvironmentBoundaryError, activateAgentEnv, activateAgentEnv as activateAgentEnv$1, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, canonicalizeBrokeredHttpSecretDescriptor, ensureSnapshot, ensureSnapshot as ensureSnapshot$1, findMainWorktree, findMainWorktree as findMainWorktree$1, isResolvedPathInsideRoot, isResolvedPathInsideRoot as isResolvedPathInsideRoot$1, loadCredentials, prepareBrokeredHttpSecrets, resolveVfsShadowConfig, resumeVm as resumeVm$1 } from "@themoltnet/sandbox-gondolin";
18
+ import { BrokeredHttpSecretBoundaryError, GONDOLIN_BASE_EXECUTABLES, GUEST_TASK_CONTEXT_MOUNT, GuestEnvironmentBoundaryError, activateAgentEnv, activateAgentEnv as activateAgentEnv$1, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, canonicalizeBrokeredHttpSecretDescriptor, ensureSnapshot, ensureSnapshot as ensureSnapshot$1, execManagedCommand, findMainWorktree, findMainWorktree as findMainWorktree$1, isResolvedPathInsideRoot, isResolvedPathInsideRoot as isResolvedPathInsideRoot$1, loadCredentials, prepareBrokeredHttpSecrets, resolveVfsShadowConfig, resumeVm as resumeVm$1 } from "@themoltnet/sandbox-gondolin";
19
19
  import { SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
20
20
  import * as json from "multiformats/codecs/json";
21
21
  import { sha256 as sha256$1 } from "multiformats/hashes/sha2";
@@ -3458,6 +3458,37 @@ function assertRuntimeKind(value) {
3458
3458
  */
3459
3459
  var DEFAULT_GREP_LIMIT = 100;
3460
3460
  var GREP_MAX_FILE_SIZE = "2M";
3461
+ var GondolinVmRetiredError = class extends Error {
3462
+ code = "sandbox_retired";
3463
+ constructor(retirement, options) {
3464
+ super(`Sandbox VM retired after ${retirement.trigger}: ${retirement.reason}`, options);
3465
+ this.retirement = retirement;
3466
+ this.name = "GondolinVmRetiredError";
3467
+ }
3468
+ };
3469
+ function createGondolinToolLifecycle(config = {}) {
3470
+ let retirement = null;
3471
+ return {
3472
+ assertActive() {
3473
+ if (retirement) throw new GondolinVmRetiredError(retirement);
3474
+ },
3475
+ getRetirement: () => retirement,
3476
+ markRetired(next) {
3477
+ if (retirement) return;
3478
+ retirement = next;
3479
+ config.onRetired?.(next);
3480
+ }
3481
+ };
3482
+ }
3483
+ function guardGondolinToolDefinitions(tools, lifecycle) {
3484
+ return tools.map((tool) => ({
3485
+ ...tool,
3486
+ execute(...args) {
3487
+ lifecycle.assertActive();
3488
+ return tool.execute(...args);
3489
+ }
3490
+ }));
3491
+ }
3461
3492
  function shQuote(s) {
3462
3493
  return "'" + s.replace(/'/g, "'\\''") + "'";
3463
3494
  }
@@ -3806,38 +3837,45 @@ async function executeGondolinGrep(vm, localCwd, guestWorkspace, params, signal)
3806
3837
  details: Object.keys(details).length > 0 ? details : void 0
3807
3838
  };
3808
3839
  }
3809
- function createGondolinBashOps(vm, localCwd, guestWorkspace) {
3840
+ function createGondolinBashOps(vm, localCwd, guestWorkspace, config) {
3810
3841
  return { exec: async (command, cwd, { onData, signal, timeout, env }) => {
3811
- const guestCwd = toGuestPath(localCwd, cwd, guestWorkspace);
3812
- const ac = new AbortController();
3813
- const onAbort = () => ac.abort();
3814
- signal?.addEventListener("abort", onAbort, { once: true });
3815
- let timedOut = false;
3816
- const timer = timeout && timeout > 0 ? setTimeout(() => {
3817
- timedOut = true;
3818
- ac.abort();
3819
- }, timeout * 1e3) : void 0;
3820
- try {
3821
- const proc = vm.exec([
3822
- "/bin/sh",
3823
- "-lc",
3824
- command
3825
- ], {
3826
- cwd: guestCwd,
3827
- signal: ac.signal,
3828
- stdout: "pipe",
3829
- stderr: "pipe"
3830
- });
3831
- for await (const chunk of proc.output()) onData(typeof chunk.data === "string" ? Buffer.from(chunk.data, "utf8") : chunk.data);
3832
- return { exitCode: (await proc).exitCode };
3833
- } catch (err) {
3834
- if (signal?.aborted) throw new Error("aborted");
3835
- if (timedOut) throw new Error(`timeout:${timeout}`);
3836
- throw err;
3837
- } finally {
3838
- if (timer) clearTimeout(timer);
3839
- signal?.removeEventListener("abort", onAbort);
3842
+ config.lifecycle.assertActive();
3843
+ const result = await execManagedCommand(vm, command, {
3844
+ cwd: toGuestPath(localCwd, cwd, guestWorkspace),
3845
+ signal,
3846
+ timeoutMs: timeout && timeout > 0 ? timeout * 1e3 : void 0,
3847
+ onData
3848
+ });
3849
+ if (result.termination.status === "backend-retired") {
3850
+ const retirement = {
3851
+ backendRetired: true,
3852
+ reason: "backend-retired",
3853
+ trigger: result.timedOut ? "timeout" : "cancellation"
3854
+ };
3855
+ config.lifecycle.markRetired(retirement);
3856
+ try {
3857
+ await config.retireVm(retirement);
3858
+ } catch (error) {
3859
+ throw new GondolinVmRetiredError(retirement, { cause: error });
3860
+ }
3861
+ }
3862
+ if (result.termination.status === "recovery-required") {
3863
+ const retirement = {
3864
+ backendRetired: false,
3865
+ reason: result.termination.reason,
3866
+ trigger: result.timedOut ? "timeout" : "cancellation"
3867
+ };
3868
+ config.lifecycle.markRetired(retirement);
3869
+ try {
3870
+ await config.retireVm(retirement);
3871
+ } catch (error) {
3872
+ throw new GondolinVmRetiredError(retirement, { cause: error });
3873
+ }
3874
+ throw new GondolinVmRetiredError(retirement);
3840
3875
  }
3876
+ if (result.timedOut) throw new Error(`timeout:${timeout}`);
3877
+ if (result.cancelled) throw new Error("aborted");
3878
+ return { exitCode: result.exitCode };
3841
3879
  } };
3842
3880
  }
3843
3881
  //#endregion
@@ -4151,31 +4189,14 @@ function decisionContext(deps) {
4151
4189
  }
4152
4190
  //#endregion
4153
4191
  //#region src/vm.ts
4154
- /** Guest path where Pi expects its auth blob. */
4155
- var PI_GUEST_AUTH_PATH = "/home/agent/.pi/agent/auth.json";
4156
4192
  /**
4157
- * Pi's provider authentication as a sandbox `ProviderAuthSource`. CI writes
4158
- * `auth.json` under `PI_CODING_AGENT_DIR`; local runs fall back to the
4159
- * canonical `~/.pi/agent` dir when the override is unset.
4160
- */
4161
- function piProviderAuth() {
4162
- return {
4163
- guestPath: PI_GUEST_AUTH_PATH,
4164
- load: () => {
4165
- const authPath = path.join(resolvePiCodingAgentDir(), "auth.json");
4166
- return existsSync(authPath) ? readFileSync(authPath, "utf8") : null;
4167
- }
4168
- };
4169
- }
4170
- /**
4171
- * Resume a Gondolin VM for a Pi session. Identical to the sandbox package's
4172
- * `resumeVm`, with Pi's provider auth supplied unless the caller overrides it.
4193
+ * Resume a Gondolin VM for a Pi session. The Pi coding-agent session and its
4194
+ * model calls run host-side (`createAgentSession` reads the host `~/.pi/agent`
4195
+ * auth), so the guest carries no provider auth — it only executes Gondolin
4196
+ * tools via `vm.exec`. This is a thin pass-through to the sandbox package.
4173
4197
  */
4174
4198
  function resumeVm(config) {
4175
- return resumeVm$1({
4176
- providerAuth: piProviderAuth(),
4177
- ...config
4178
- });
4199
+ return resumeVm$1(config);
4179
4200
  }
4180
4201
  //#endregion
4181
4202
  //#region src/runtime/capability-discovery.ts
@@ -4702,20 +4723,9 @@ function buildCredentialInstructions(policy, sandbox) {
4702
4723
  }
4703
4724
  if (gitAvailable) {
4704
4725
  const signing = (sandbox?.hostCapabilities ?? []).find((capability) => capability.name === "agent-signing");
4705
- const signingGranted = !!signing && (!policy || policy.enforcement === "off" || policy.allowedTools.some((name) => name === "capability:agent-signing" || name.startsWith("capability:agent-signing:")));
4706
- const hostAuthenticated = sandbox?.guestCredentialMode === "host-authenticated";
4707
- if (signingGranted) {
4726
+ if (!!signing && (!policy || policy.enforcement === "off" || policy.allowedTools.some((name) => name === "capability:agent-signing" || name.startsWith("capability:agent-signing:")))) {
4708
4727
  lines.push("- Commit signing is brokered: `git commit -S` works normally through", " `SSH_AUTH_SOCK`; the signing key stays on the host. Do not look for,", " export, or recreate a private key in the guest.");
4709
- lines.push(...hostAuthenticated ? [
4710
- "- For a signed diary entry use the `moltnet_create_entry` tool with",
4711
- " `signed: true` (it signs on the trusted host). Do not use the",
4712
- " guest `moltnet entry create-signed` CLI: it has no guest identity",
4713
- " credentials and its REST calls will fail."
4714
- ] : [
4715
- "- For a signed diary entry use `moltnet_create_entry` with",
4716
- " `signed: true`, or the guest `moltnet entry create-signed` CLI",
4717
- " (brokered through `MOLTNET_SIGNER_URL`)."
4718
- ]);
4728
+ lines.push("- For a signed diary entry use the `moltnet_create_entry` tool with", " `signed: true` (it signs on the trusted host). Do not use the", " guest `moltnet entry create-signed` CLI: it has no guest identity", " credentials and its REST calls will fail.");
4719
4729
  } else if (signing) lines.push("- An `agent-signing` capability is declared but the active policy does", " not grant it. `git commit -S` and signed diary entries will be denied;", " create unsigned commits/entries unless the capability is granted.");
4720
4730
  else lines.push("- Local Git commands run inside the guest. No signing key or Git", " credential helper is injected; signing and authenticated push", " require an explicitly provided capability.");
4721
4731
  }
@@ -5781,12 +5791,10 @@ function createPolicyCheckedSigner(router, identity) {
5781
5791
  }
5782
5792
  };
5783
5793
  }
5784
- function resolveHostExecBaseEnv(guestCredentialMode, agentEnv) {
5794
+ function resolveHostExecBaseEnv(agentEnv) {
5785
5795
  const names = new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(agentEnv)]);
5786
- if (guestCredentialMode === "host-authenticated") {
5787
- for (const name of HOST_AUTHENTICATED_HOST_EXEC_REFUSED_ENV) names.delete(name);
5788
- for (const name of names) if (name.startsWith("MOLTNET_")) names.delete(name);
5789
- }
5796
+ for (const name of HOST_AUTHENTICATED_HOST_EXEC_REFUSED_ENV) names.delete(name);
5797
+ for (const name of names) if (name.startsWith("MOLTNET_")) names.delete(name);
5790
5798
  return names;
5791
5799
  }
5792
5800
  var noopTurnEventHandler = () => {};
@@ -5802,13 +5810,16 @@ async function openVmWorkspaceFileForRead(config) {
5802
5810
  };
5803
5811
  }
5804
5812
  function createGondolinToolDefinitions(config) {
5805
- const { vm, cwdPath, guestWorkspace } = config;
5813
+ const { vm, cwdPath, guestWorkspace, lifecycle, retireVm } = config;
5806
5814
  const grepTool = createGrepToolDefinition(cwdPath);
5807
- return [
5815
+ return guardGondolinToolDefinitions([
5808
5816
  createReadToolDefinition(cwdPath, { operations: createGondolinReadOps(vm, cwdPath, guestWorkspace) }),
5809
5817
  createWriteToolDefinition(cwdPath, { operations: createGondolinWriteOps(vm, cwdPath, guestWorkspace) }),
5810
5818
  createEditToolDefinition(cwdPath, { operations: createGondolinEditOps(vm, cwdPath, guestWorkspace) }),
5811
- createBashToolDefinition(cwdPath, { operations: createGondolinBashOps(vm, cwdPath, guestWorkspace) }),
5819
+ createBashToolDefinition(cwdPath, { operations: createGondolinBashOps(vm, cwdPath, guestWorkspace, {
5820
+ lifecycle,
5821
+ retireVm
5822
+ }) }),
5812
5823
  createLsToolDefinition(cwdPath, { operations: createGondolinLsOps(vm, cwdPath, guestWorkspace) }),
5813
5824
  createFindToolDefinition(cwdPath, { operations: createGondolinFindOps(vm, cwdPath, guestWorkspace) }),
5814
5825
  {
@@ -5818,7 +5829,52 @@ function createGondolinToolDefinitions(config) {
5818
5829
  return executeGondolinGrep(vm, cwdPath, guestWorkspace, params, signal);
5819
5830
  }
5820
5831
  }
5821
- ];
5832
+ ], lifecycle);
5833
+ }
5834
+ async function retireManagedGondolinVm(config) {
5835
+ const { managed, retirement, secretEnvNames, release } = config;
5836
+ const recoveryErrors = [];
5837
+ for (const guestEnv of secretEnvNames) try {
5838
+ managed.secretManager.revokeSecret(guestEnv);
5839
+ } catch (error) {
5840
+ recoveryErrors.push(error);
5841
+ }
5842
+ try {
5843
+ if (!retirement.backendRetired) await managed.vm.close();
5844
+ } catch (error) {
5845
+ recoveryErrors.push(error);
5846
+ } finally {
5847
+ release();
5848
+ }
5849
+ if (recoveryErrors.length > 0) throw new AggregateError(recoveryErrors, "Sandbox VM retirement recovery failed");
5850
+ }
5851
+ function createGondolinRetirementCoordinator(config = {}) {
5852
+ let retirement = null;
5853
+ let abortHandler = null;
5854
+ return {
5855
+ lifecycle: createGondolinToolLifecycle({ onRetired(next) {
5856
+ retirement = next;
5857
+ config.onRetired?.(next);
5858
+ abortHandler?.(next);
5859
+ } }),
5860
+ bindAbortHandler(handler) {
5861
+ abortHandler = handler;
5862
+ if (retirement) handler(retirement);
5863
+ },
5864
+ getRetirement: () => retirement
5865
+ };
5866
+ }
5867
+ function guardGondolinExtensionFactories(factories, lifecycle) {
5868
+ return factories.map((factory) => (pi) => {
5869
+ factory(new Proxy(pi, { get(target, property, receiver) {
5870
+ if (property === "registerTool") return (tool) => {
5871
+ const [guardedTool] = guardGondolinToolDefinitions([tool], lifecycle);
5872
+ if (guardedTool) target.registerTool(guardedTool);
5873
+ };
5874
+ const value = Reflect.get(target, property, receiver);
5875
+ return typeof value === "function" ? value.bind(target) : value;
5876
+ } }));
5877
+ });
5822
5878
  }
5823
5879
  /** Resolve one attempt's host-only HTTP credentials before VM resume. */
5824
5880
  async function resolveAttemptBrokeredHttpSecrets(input) {
@@ -6079,7 +6135,6 @@ async function executePiTask(claimedTask, reporter, opts) {
6079
6135
  checkpointPath,
6080
6136
  agentName: opts.agentName,
6081
6137
  agentRootDir,
6082
- guestCredentialMode: opts.guestCredentialMode,
6083
6138
  mountPath,
6084
6139
  workspaceMode: preparedWorkspace.mode,
6085
6140
  extraAllowedHosts: opts.extraAllowedHosts,
@@ -6107,6 +6162,23 @@ async function executePiTask(claimedTask, reporter, opts) {
6107
6162
  activateAgentEnv$1(managed.credentials.agentEnv, agentRootDir);
6108
6163
  const activeWorkspace = preparedWorkspace;
6109
6164
  const activeManaged = managed;
6165
+ const sandboxRetirementEvents = [];
6166
+ const sandboxRetirementCoordinator = createGondolinRetirementCoordinator({ onRetired(retirement) {
6167
+ const details = {
6168
+ event: "sandbox_vm_retired",
6169
+ taskId: task.id,
6170
+ attemptN,
6171
+ vmId: activeManaged.vm.id,
6172
+ outcome: retirement.backendRetired ? "retired" : "retirement_failed",
6173
+ ...retirement
6174
+ };
6175
+ process.stderr.write(`${JSON.stringify(details)}\n`);
6176
+ sandboxRetirementEvents.push(emitError("sandbox_retirement", `Sandbox VM retired after ${retirement.trigger}`, details).catch((error) => {
6177
+ const message = error instanceof Error ? error.message : String(error);
6178
+ process.stderr.write(`[sandbox] failed to emit retirement event: ${message}\n`);
6179
+ }));
6180
+ } });
6181
+ const gondolinLifecycle = sandboxRetirementCoordinator.lifecycle;
6110
6182
  const getMoltNetAgent = createMoltNetAgentResolver({
6111
6183
  moltnetAgent: opts.moltnetAgent,
6112
6184
  configDir: managed.agentDir
@@ -6222,7 +6294,20 @@ async function executePiTask(claimedTask, reporter, opts) {
6222
6294
  const gondolinCustomTools = createGondolinToolDefinitions({
6223
6295
  vm: managed.vm,
6224
6296
  cwdPath,
6225
- guestWorkspace: managed.guestWorkspace
6297
+ guestWorkspace: managed.guestWorkspace,
6298
+ lifecycle: gondolinLifecycle,
6299
+ retireVm: async (retirement) => {
6300
+ const recoveringManaged = managed;
6301
+ if (!recoveringManaged) return;
6302
+ await retireManagedGondolinVm({
6303
+ managed: recoveringManaged,
6304
+ retirement,
6305
+ secretEnvNames: brokeredSecretEnvNames,
6306
+ release: () => {
6307
+ if (managed === recoveringManaged) managed = null;
6308
+ }
6309
+ });
6310
+ }
6226
6311
  });
6227
6312
  const submitCompletion = createSubmitCompletionCoordinator({
6228
6313
  onDrained: () => session?.abort(),
@@ -6249,13 +6334,16 @@ async function executePiTask(claimedTask, reporter, opts) {
6249
6334
  getSessionErrors: () => [],
6250
6335
  clearSessionErrors: () => {},
6251
6336
  getHostCwd: () => cwdPath,
6252
- openWorkspaceFileForRead: (filePath) => openVmWorkspaceFileForRead({
6253
- vm: activeManaged.vm,
6254
- cwdPath,
6255
- guestWorkspace: activeManaged.guestWorkspace,
6256
- filePath
6257
- }),
6258
- hostExecBaseEnv: resolveHostExecBaseEnv(opts.guestCredentialMode ?? "guest-config", managed.credentials.agentEnv),
6337
+ openWorkspaceFileForRead: (filePath) => {
6338
+ gondolinLifecycle.assertActive();
6339
+ return openVmWorkspaceFileForRead({
6340
+ vm: activeManaged.vm,
6341
+ cwdPath,
6342
+ guestWorkspace: activeManaged.guestWorkspace,
6343
+ filePath
6344
+ });
6345
+ },
6346
+ hostExecBaseEnv: resolveHostExecBaseEnv(managed.credentials.agentEnv),
6259
6347
  hostExecAutoApprove: opts.hostExecAutoApprove ?? opts.sandboxConfig?.hostExec?.autoApprove ?? false,
6260
6348
  getTaskContext: () => ({
6261
6349
  taskId: task.id,
@@ -6334,30 +6422,30 @@ async function executePiTask(claimedTask, reporter, opts) {
6334
6422
  cwdPath,
6335
6423
  guestWorkspace: managed.guestWorkspace
6336
6424
  };
6337
- const runtimeParentTools = opts.runtimeDefinition ? await materializePiTools({
6425
+ const runtimeParentTools = opts.runtimeDefinition ? guardGondolinToolDefinitions(await materializePiTools({
6338
6426
  runtime: opts.runtimeDefinition,
6339
6427
  context: runtimeToolContext,
6340
6428
  target: "parent",
6341
6429
  policy: resolvedToolPolicy
6342
- }) : [];
6343
- const runtimeSubagentTools = opts.runtimeDefinition ? await materializePiTools({
6430
+ }), gondolinLifecycle) : [];
6431
+ const runtimeSubagentTools = opts.runtimeDefinition ? guardGondolinToolDefinitions(await materializePiTools({
6344
6432
  runtime: opts.runtimeDefinition,
6345
6433
  context: runtimeToolContext,
6346
6434
  target: "subagent",
6347
6435
  policy: resolvedToolPolicy
6348
- }) : [];
6349
- const runtimeParentExtensions = opts.runtimeDefinition ? await materializePiExtensions({
6436
+ }), gondolinLifecycle) : [];
6437
+ const runtimeParentExtensions = opts.runtimeDefinition ? guardGondolinExtensionFactories(await materializePiExtensions({
6350
6438
  runtime: opts.runtimeDefinition,
6351
6439
  context: runtimeToolContext,
6352
6440
  target: "parent",
6353
6441
  policy: resolvedToolPolicy
6354
- }) : [];
6355
- const runtimeSubagentExtensions = opts.runtimeDefinition ? await materializePiExtensions({
6442
+ }), gondolinLifecycle) : [];
6443
+ const runtimeSubagentExtensions = opts.runtimeDefinition ? guardGondolinExtensionFactories(await materializePiExtensions({
6356
6444
  runtime: opts.runtimeDefinition,
6357
6445
  context: runtimeToolContext,
6358
6446
  target: "subagent",
6359
6447
  policy: resolvedToolPolicy
6360
- }) : [];
6448
+ }), gondolinLifecycle) : [];
6361
6449
  const visibleBaseTools = filterModelVisibleTools([...gondolinCustomTools, ...moltnetTools], resolvedToolPolicy);
6362
6450
  const taskHasSubagents = taskTypeUsesSubagents(task.taskType);
6363
6451
  const visibleParentToolNames = modelVisiblePiToolNames({
@@ -6410,7 +6498,6 @@ async function executePiTask(claimedTask, reporter, opts) {
6410
6498
  allowedHosts: [...effectiveSandboxConfig?.network?.allowedHosts ?? [], ...opts.extraAllowedHosts ?? []],
6411
6499
  allowedInternalHosts: effectiveSandboxConfig?.network?.allowedInternalHosts ?? [],
6412
6500
  brokeredSecretEnvNames,
6413
- guestCredentialMode: opts.guestCredentialMode ?? "guest-config",
6414
6501
  ...capabilityRouter && { hostCapabilities: capabilityRouter.manifest }
6415
6502
  },
6416
6503
  toolPolicy: capabilityProjection.instructorPolicy
@@ -6449,12 +6536,12 @@ async function executePiTask(claimedTask, reporter, opts) {
6449
6536
  });
6450
6537
  parentSubagentTools.push(subagentHandle.tool);
6451
6538
  }
6452
- const parentTools = [
6539
+ const parentTools = guardGondolinToolDefinitions([
6453
6540
  ...visibleBaseTools,
6454
6541
  ...runtimeParentTools,
6455
6542
  ...submitTools,
6456
6543
  ...parentSubagentTools
6457
- ];
6544
+ ], gondolinLifecycle);
6458
6545
  session = await traceRuntimePhase("moltnet.execution.session.create", {
6459
6546
  "gen_ai.provider.name": opts.provider,
6460
6547
  "gen_ai.request.model": opts.model
@@ -6547,6 +6634,9 @@ async function executePiTask(claimedTask, reporter, opts) {
6547
6634
  message
6548
6635
  }));
6549
6636
  };
6637
+ sandboxRetirementCoordinator.bindAbortHandler((retirement) => {
6638
+ triggerCapAbort("sandbox_retired", `Sandbox VM retired after ${retirement.trigger}: ${retirement.reason}.`);
6639
+ });
6550
6640
  session.subscribe(makeSessionEventHandler({
6551
6641
  state: turnState,
6552
6642
  usage,
@@ -6616,7 +6706,7 @@ async function executePiTask(claimedTask, reporter, opts) {
6616
6706
  event: "subagent_summary",
6617
6707
  callCount: subagentHandle.getCallCount()
6618
6708
  });
6619
- await Promise.all(recordingPromise);
6709
+ await Promise.all([...recordingPromise, ...sandboxRetirementEvents]);
6620
6710
  const cancelled = reporter.cancelSignal.aborted;
6621
6711
  let parsedOutput = null;
6622
6712
  let parsedOutputCid = null;
@@ -7387,4 +7477,4 @@ function describeToolErrorMessage(result) {
7387
7477
  }
7388
7478
  }
7389
7479
  //#endregion
7390
- export { BrokeredHttpSecretBoundaryError, DEFAULT_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS, GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, GUEST_ALLOWED_SIGNERS_PATH, GUEST_GITCONFIG_PATH, GUEST_SIGNER_SOCKET, GuestEnvironmentBoundaryError, HOST_EXEC_DEFAULT_BASE_ENV, MOLTNET_TOOL_NAMES, PI_EXECUTOR_MANIFEST_VERSION, PI_GUEST_AUTH_PATH, PI_RUNTIME_DEFINITION_VERSION, PiBrokeredHttpSecretResolutionError, activateAgentEnv, agentSigningCapability, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, buildAgentSession, buildPiExecutorManifest, buildRuntimeKernel, buildWorkspaceMountInstructions, createGondolinBashOps, createGondolinEditOps, createGondolinFindOps, createGondolinLsOps, createGondolinReadOps, createGondolinToolDefinitions, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, createToolPolicyExtension, decideForEvent, decideToolCall, defineGondolinTemplate, definePiBrokeredHttpSecret, definePiExtension, definePiRuntime, definePiTool, enabledPiToolNames, ensureSnapshot, executeGondolinGrep, executePiTask, filterModelVisibleTools, findMainWorktree, injectRuntimeContext as injectTaskContext, isKernelTool, isResolvedPathInsideRoot, isToolVisible, loadCredentials, materializePiBrokeredHttpSecrets, materializePiExtensions, materializePiTools, normalizeRetryTriageResult, piProviderAuth, prepareBrokeredHttpSecrets, redactRetryTriageSecrets, resolveHostExecBaseEnv, resolveSessionToolPolicy, resolveTaskWorktreePath, resumeVm, toGuestPath };
7480
+ export { BrokeredHttpSecretBoundaryError, DEFAULT_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS, GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, GUEST_ALLOWED_SIGNERS_PATH, GUEST_GITCONFIG_PATH, GUEST_SIGNER_SOCKET, GondolinVmRetiredError, GuestEnvironmentBoundaryError, HOST_EXEC_DEFAULT_BASE_ENV, MOLTNET_TOOL_NAMES, PI_EXECUTOR_MANIFEST_VERSION, PI_RUNTIME_DEFINITION_VERSION, PiBrokeredHttpSecretResolutionError, activateAgentEnv, agentSigningCapability, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, buildAgentSession, buildPiExecutorManifest, buildRuntimeKernel, buildWorkspaceMountInstructions, createGondolinBashOps, createGondolinEditOps, createGondolinFindOps, createGondolinLsOps, createGondolinReadOps, createGondolinToolDefinitions, createGondolinToolLifecycle, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, createToolPolicyExtension, decideForEvent, decideToolCall, defineGondolinTemplate, definePiBrokeredHttpSecret, definePiExtension, definePiRuntime, definePiTool, enabledPiToolNames, ensureSnapshot, executeGondolinGrep, executePiTask, filterModelVisibleTools, findMainWorktree, guardGondolinToolDefinitions, injectRuntimeContext as injectTaskContext, isKernelTool, isResolvedPathInsideRoot, isToolVisible, loadCredentials, materializePiBrokeredHttpSecrets, materializePiExtensions, materializePiTools, normalizeRetryTriageResult, prepareBrokeredHttpSecrets, redactRetryTriageSecrets, resolveHostExecBaseEnv, resolveSessionToolPolicy, resolveTaskWorktreePath, resumeVm, toGuestPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-runtime",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Composable MoltNet runtime kernel for Pi agents in Gondolin VMs",
@@ -23,7 +23,7 @@
23
23
  "!dist/.dts"
24
24
  ],
25
25
  "dependencies": {
26
- "@earendil-works/gondolin": "^0.9.1",
26
+ "@earendil-works/gondolin": "^0.12.0",
27
27
  "@ipld/dag-cbor": "^9.2.5",
28
28
  "@noble/curves": "^2.0.0",
29
29
  "@noble/ed25519": "^2.0.0",
@@ -32,10 +32,10 @@
32
32
  "multiformats": "^13.3.0",
33
33
  "typebox": "^1.2.8",
34
34
  "@themoltnet/agent-runtime": "0.44.0",
35
- "@themoltnet/sandbox-gondolin": "0.3.0",
36
- "@themoltnet/sdk": "0.137.0",
37
35
  "@themoltnet/os-keyring": "0.2.0",
38
- "@themoltnet/shell-command-analyzer": "0.3.0"
36
+ "@themoltnet/sdk": "0.137.0",
37
+ "@themoltnet/shell-command-analyzer": "0.3.0",
38
+ "@themoltnet/sandbox-gondolin": "0.4.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@earendil-works/pi-ai": "0.79.4",
@@ -52,10 +52,10 @@
52
52
  "vite": "^8.0.0",
53
53
  "vite-plugin-dts": "^4.5.4",
54
54
  "vitest": "^3.0.0",
55
- "@moltnet/crypto-service": "0.1.0",
56
- "@moltnet/tasks": "0.1.0",
55
+ "@moltnet/runtime-profiles": "0.1.0",
57
56
  "@moltnet/models": "0.1.0",
58
- "@moltnet/runtime-profiles": "0.1.0"
57
+ "@moltnet/crypto-service": "0.1.0",
58
+ "@moltnet/tasks": "0.1.0"
59
59
  },
60
60
  "engines": {
61
61
  "node": ">=24"