@themoltnet/pi-runtime 0.7.3 → 0.9.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/dist/index.d.ts +85 -12
- package/dist/index.js +290 -89
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -49,10 +49,24 @@ export declare interface AllowedToolsClient {
|
|
|
49
49
|
argvPrefix: string[];
|
|
50
50
|
}>;
|
|
51
51
|
runtimeKind: string;
|
|
52
|
+
policySnapshotHash?: string;
|
|
53
|
+
runtimeProfileRevision?: number;
|
|
52
54
|
}>;
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
export declare function assertGuestEnvironmentBoundary(options: {
|
|
59
|
+
guestCredentialMode: GuestCredentialMode;
|
|
60
|
+
forwardEnv?: readonly string[];
|
|
61
|
+
sandboxEnv?: Readonly<Record<string, string>>;
|
|
62
|
+
}): void;
|
|
63
|
+
|
|
64
|
+
/** @deprecated Prefer assertGuestEnvironmentBoundary for mode-aware checks. */
|
|
65
|
+
export declare function assertHostAuthenticatedGuestEnvironment(options: {
|
|
66
|
+
forwardEnv?: readonly string[];
|
|
67
|
+
sandboxEnv?: Readonly<Record<string, string>>;
|
|
68
|
+
}): void;
|
|
69
|
+
|
|
56
70
|
/**
|
|
57
71
|
* Construct an `AgentSession`. By default it is in-memory; callers may opt
|
|
58
72
|
* parent sessions into daemon-owned file persistence via `sessionPersistence`.
|
|
@@ -390,6 +404,10 @@ export declare function executePiTask(claimedTask: ClaimedTask, reporter: TaskRe
|
|
|
390
404
|
export declare interface ExecutePiTaskOptions {
|
|
391
405
|
/** MoltNet agent whose credentials the VM boots with. */
|
|
392
406
|
agentName: string;
|
|
407
|
+
/** Already-authenticated host-side Agent. Daemon callers always supply it. */
|
|
408
|
+
moltnetAgent?: Agent;
|
|
409
|
+
/** Explicit trust boundary for MoltNet credentials inside the guest. */
|
|
410
|
+
guestCredentialMode?: GuestCredentialMode;
|
|
393
411
|
/**
|
|
394
412
|
* Host root that owns `.moltnet/<agentName>/`.
|
|
395
413
|
*
|
|
@@ -430,8 +448,8 @@ export declare interface ExecutePiTaskOptions {
|
|
|
430
448
|
runtimeProfileContext?: readonly ContextRef[];
|
|
431
449
|
/**
|
|
432
450
|
* Runtime profile id, used to resolve the tool-policy allow-set at session
|
|
433
|
-
* start. Required
|
|
434
|
-
* `tool_call` gate
|
|
451
|
+
* start. Required with `toolEnforcement`; the resolved mode determines
|
|
452
|
+
* whether a `tool_call` gate is installed.
|
|
435
453
|
*/
|
|
436
454
|
runtimeProfileId?: string;
|
|
437
455
|
/** Tool-policy enforcement mode for the selected runtime profile. */
|
|
@@ -447,6 +465,8 @@ export declare interface ExecutePiTaskOptions {
|
|
|
447
465
|
promptExtras?: Record<string, unknown>;
|
|
448
466
|
/** Snapshot progress callback; defaults to stderr logging. */
|
|
449
467
|
onSnapshotProgress?: (message: string) => void;
|
|
468
|
+
/** Structured VM credential-boundary diagnostics. */
|
|
469
|
+
onVmDiagnostic?: (diagnostic: VmDiagnostic) => void;
|
|
450
470
|
/**
|
|
451
471
|
* Optional pre-resolved checkpoint path. If omitted, `ensureSnapshot` is
|
|
452
472
|
* invoked. Useful for batch execution where the caller wants to cache
|
|
@@ -586,21 +606,18 @@ export declare function filterModelVisibleTools(tools: readonly ToolDefinition[]
|
|
|
586
606
|
*/
|
|
587
607
|
export declare function findMainWorktree(startPath?: string): string;
|
|
588
608
|
|
|
589
|
-
/**
|
|
590
|
-
* The gate's verdict:
|
|
591
|
-
* - `{ allow: true }` — let the tool run.
|
|
592
|
-
* - `{ allow: false, reason }` — block it (enforce mode).
|
|
593
|
-
* - `{ audit, ... }` — would-block, but proceed and record it (watch mode).
|
|
594
|
-
*/
|
|
595
609
|
export declare type GateDecision = {
|
|
596
610
|
allow: true;
|
|
611
|
+
reasonCode: ToolPolicyDecisionReason;
|
|
597
612
|
matchedShellCommands?: MatchedShellCommand[];
|
|
598
613
|
} | {
|
|
599
614
|
allow: false;
|
|
615
|
+
reasonCode: ToolPolicyDecisionReason;
|
|
600
616
|
reason: string;
|
|
601
617
|
missing?: string[];
|
|
602
618
|
missingShellCommands?: MissingShellCommand[];
|
|
603
619
|
} | {
|
|
620
|
+
reasonCode: ToolPolicyDecisionReason;
|
|
604
621
|
audit: string;
|
|
605
622
|
missing?: string[];
|
|
606
623
|
missingShellCommands?: MissingShellCommand[];
|
|
@@ -642,6 +659,13 @@ export declare interface GondolinTemplateResolveContext {
|
|
|
642
659
|
onProgress?: (message: string) => void;
|
|
643
660
|
}
|
|
644
661
|
|
|
662
|
+
export declare type GuestCredentialMode = 'guest-config' | 'host-authenticated';
|
|
663
|
+
|
|
664
|
+
export declare class GuestEnvironmentBoundaryError extends Error {
|
|
665
|
+
readonly refusedNames: readonly string[];
|
|
666
|
+
constructor(refusedNames: readonly string[]);
|
|
667
|
+
}
|
|
668
|
+
|
|
645
669
|
/**
|
|
646
670
|
* Baseline env keys forwarded to host-exec child processes.
|
|
647
671
|
* Callers can extend this set at sandbox startup via `MoltNetToolsConfig.hostExecBaseEnv`.
|
|
@@ -703,7 +727,7 @@ export declare function isResolvedPathInsideRoot(path: string, root: string): bo
|
|
|
703
727
|
|
|
704
728
|
export declare function isToolVisible(name: string, policy?: ModelVisibleToolPolicy): boolean;
|
|
705
729
|
|
|
706
|
-
export declare function loadCredentials(agentDir: string): VmCredentials;
|
|
730
|
+
export declare function loadCredentials(agentDir: string, mode?: GuestCredentialMode, onDiagnostic?: (diagnostic: VmDiagnostic) => void): VmCredentials;
|
|
707
731
|
|
|
708
732
|
export declare interface ManagedVm {
|
|
709
733
|
vm: VM;
|
|
@@ -1076,11 +1100,14 @@ export declare interface ResolvedGondolinTemplate {
|
|
|
1076
1100
|
resumeCommands: readonly ResumeCommand[];
|
|
1077
1101
|
}
|
|
1078
1102
|
|
|
1103
|
+
export declare function resolveHostExecBaseEnv(guestCredentialMode: GuestCredentialMode, agentEnv: Readonly<Record<string, string | undefined>>): Set<string>;
|
|
1104
|
+
|
|
1079
1105
|
/**
|
|
1080
1106
|
* Resolve the session's tool policy at start-up.
|
|
1081
1107
|
*
|
|
1082
|
-
*
|
|
1083
|
-
*
|
|
1108
|
+
* The latest allowed-tool set and enforcement mode are fetched from the API,
|
|
1109
|
+
* including when the daemon's cached profile says `off`. If that fetch fails,
|
|
1110
|
+
* the cached mode decides the fallback:
|
|
1084
1111
|
* `enforce` **fails closed** (empty allow-set → every non-`off` tool is
|
|
1085
1112
|
* blocked); `watch` fails open (empty allow-set → every tool is audited but
|
|
1086
1113
|
* allowed).
|
|
@@ -1264,6 +1291,10 @@ export declare interface SessionToolPolicy {
|
|
|
1264
1291
|
enforcement: ToolEnforcement;
|
|
1265
1292
|
allowedTools: ReadonlySet<string>;
|
|
1266
1293
|
allowedShellCommands: readonly ShellCommandRule[];
|
|
1294
|
+
/** Latest effective policy hash resolved for this Pi session. */
|
|
1295
|
+
executionPolicySnapshotHash?: string;
|
|
1296
|
+
/** Latest runtime profile revision resolved with the session policy. */
|
|
1297
|
+
executionRuntimeProfileRevision?: number;
|
|
1267
1298
|
/**
|
|
1268
1299
|
* `true` when the allow-set is a **degraded fallback** — the allowed-tools
|
|
1269
1300
|
* fetch failed or timed out and this policy is the fail-closed/fail-open
|
|
@@ -1271,7 +1302,7 @@ export declare interface SessionToolPolicy {
|
|
|
1271
1302
|
* empty-but-resolved policy (e.g. a profile with no bound tools) has
|
|
1272
1303
|
* `degraded: false`. Surfaced in every audit/block log so an operator can tell
|
|
1273
1304
|
* "blocked because the policy is empty" from "blocked because we couldn't read
|
|
1274
|
-
* the policy".
|
|
1305
|
+
* the policy". Successful resolutions are never degraded.
|
|
1275
1306
|
*/
|
|
1276
1307
|
degraded: boolean;
|
|
1277
1308
|
}
|
|
@@ -1327,10 +1358,35 @@ declare const ToolEnforcementSchema = Type.Union(toolEnforcementLiterals, {
|
|
|
1327
1358
|
'Runtime tool-policy enforcement mode: off (inert), watch (audit only), enforce (block disallowed tools, fail-closed).',
|
|
1328
1359
|
});
|
|
1329
1360
|
|
|
1361
|
+
/** Correlation and claim evidence repeated on every tool-policy decision. */
|
|
1362
|
+
export declare interface ToolPolicyDecisionContext {
|
|
1363
|
+
taskId: string;
|
|
1364
|
+
attemptN: number;
|
|
1365
|
+
teamId: string;
|
|
1366
|
+
claimantAgentId?: string;
|
|
1367
|
+
leaseId?: string;
|
|
1368
|
+
proposerKind?: 'agent' | 'human';
|
|
1369
|
+
proposerId?: string;
|
|
1370
|
+
claimRuntimeProfileId?: string;
|
|
1371
|
+
executionRuntimeProfileId?: string;
|
|
1372
|
+
claimRuntimeProfileRevision?: number;
|
|
1373
|
+
claimPolicySnapshotHash?: string;
|
|
1374
|
+
claimedExecutorFingerprint?: string;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* The gate's verdict:
|
|
1379
|
+
* - `{ allow: true }` — let the tool run.
|
|
1380
|
+
* - `{ allow: false, reason }` — block it (enforce mode).
|
|
1381
|
+
* - `{ audit, ... }` — would-block, but proceed and record it (watch mode).
|
|
1382
|
+
*/
|
|
1383
|
+
export declare type ToolPolicyDecisionReason = 'policy_off' | 'executor_protocol_tool' | 'policy_allowed' | 'shell_command_prefix_allowed' | 'shell_command_unresolvable' | 'arbitrary_code_interpreter' | 'shell_output_redirection_requires_broad_permission' | 'tool_not_permitted';
|
|
1384
|
+
|
|
1330
1385
|
export declare interface ToolPolicyExtensionDeps {
|
|
1331
1386
|
policy: SessionToolPolicy;
|
|
1332
1387
|
analyzer: ShellCommandAnalyzer;
|
|
1333
1388
|
logger: ToolPolicyLogger;
|
|
1389
|
+
context?: ToolPolicyDecisionContext;
|
|
1334
1390
|
}
|
|
1335
1391
|
|
|
1336
1392
|
/**
|
|
@@ -1366,6 +1422,12 @@ export declare interface VmConfig {
|
|
|
1366
1422
|
checkpointPath: string;
|
|
1367
1423
|
/** MoltNet agent name (used to resolve credentials). */
|
|
1368
1424
|
agentName: string;
|
|
1425
|
+
/**
|
|
1426
|
+
* Trust boundary for guest credentials. `guest-config` injects the complete
|
|
1427
|
+
* legacy agent directory. `host-authenticated` never reads or injects it and
|
|
1428
|
+
* relies exclusively on the supplied host-side Agent for MoltNet operations.
|
|
1429
|
+
*/
|
|
1430
|
+
guestCredentialMode?: GuestCredentialMode;
|
|
1369
1431
|
/**
|
|
1370
1432
|
* Host root that owns `.moltnet/<agentName>/`.
|
|
1371
1433
|
*
|
|
@@ -1389,12 +1451,16 @@ export declare interface VmConfig {
|
|
|
1389
1451
|
* into the guest without storing secret values in the profile.
|
|
1390
1452
|
*/
|
|
1391
1453
|
forwardEnv?: string[];
|
|
1454
|
+
/** Structured credential-boundary diagnostics for daemon loggers. */
|
|
1455
|
+
onDiagnostic?: (diagnostic: VmDiagnostic) => void;
|
|
1392
1456
|
/** Abort resume/setup work, closing any live VM owned by resumeVm. */
|
|
1393
1457
|
signal?: AbortSignal;
|
|
1394
1458
|
}
|
|
1395
1459
|
|
|
1396
1460
|
export declare interface VmCredentials {
|
|
1461
|
+
/** Empty in host-authenticated mode; retained as strings for API stability. */
|
|
1397
1462
|
moltnetJson: string;
|
|
1463
|
+
/** Empty in host-authenticated mode; retained as strings for API stability. */
|
|
1398
1464
|
agentEnvRaw: string;
|
|
1399
1465
|
/**
|
|
1400
1466
|
* Pi OAuth/API-key auth blob. Null when neither `~/.pi/agent/auth.json`
|
|
@@ -1415,6 +1481,13 @@ export declare interface VmCredentials {
|
|
|
1415
1481
|
githubAppPemFilename: string | null;
|
|
1416
1482
|
}
|
|
1417
1483
|
|
|
1484
|
+
export declare interface VmDiagnostic {
|
|
1485
|
+
event: 'vm.credentials.mode' | 'vm.credentials.github_key_missing';
|
|
1486
|
+
level: 'info' | 'warning';
|
|
1487
|
+
message: string;
|
|
1488
|
+
credentialMode: GuestCredentialMode;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1418
1491
|
/**
|
|
1419
1492
|
* Subset of `@earendil-works/gondolin`'s `VmFs` we actually use. We
|
|
1420
1493
|
* narrow the dependency surface so unit tests can hand in a
|
package/dist/index.js
CHANGED
|
@@ -1925,6 +1925,11 @@ Type$1.Object({ id: UuidSchema });
|
|
|
1925
1925
|
Type$1.Object({
|
|
1926
1926
|
publicKey: PublicKeySchema,
|
|
1927
1927
|
fingerprint: FingerprintSchema,
|
|
1928
|
+
proof: Type$1.String({
|
|
1929
|
+
minLength: 1,
|
|
1930
|
+
maxLength: 256
|
|
1931
|
+
}),
|
|
1932
|
+
credentialType: Type$1.Literal("oauth2"),
|
|
1928
1933
|
agentName: Type$1.String({
|
|
1929
1934
|
minLength: 1,
|
|
1930
1935
|
maxLength: 34
|
|
@@ -2198,7 +2203,6 @@ var ProblemCodeSchema = Type$1.Union([
|
|
|
2198
2203
|
Type$1.Literal("VALIDATION_FAILED"),
|
|
2199
2204
|
Type$1.Literal("INVALID_CHALLENGE"),
|
|
2200
2205
|
Type$1.Literal("INVALID_SIGNATURE"),
|
|
2201
|
-
Type$1.Literal("VOUCHER_LIMIT"),
|
|
2202
2206
|
Type$1.Literal("RATE_LIMIT_EXCEEDED"),
|
|
2203
2207
|
Type$1.Literal("SERIALIZATION_EXHAUSTED"),
|
|
2204
2208
|
Type$1.Literal("SIGNING_REQUEST_EXPIRED"),
|
|
@@ -4532,12 +4536,30 @@ function resolveVmAgentDir(config) {
|
|
|
4532
4536
|
const rootDir = config.agentRootDir ?? findMainWorktree();
|
|
4533
4537
|
return path.join(rootDir, ".moltnet", config.agentName);
|
|
4534
4538
|
}
|
|
4535
|
-
function loadCredentials(agentDir) {
|
|
4536
|
-
const
|
|
4537
|
-
const
|
|
4539
|
+
function loadCredentials(agentDir, mode = "guest-config", onDiagnostic) {
|
|
4540
|
+
const moltnetPath = path.join(agentDir, "moltnet.json");
|
|
4541
|
+
const agentEnvPath = path.join(agentDir, "env");
|
|
4538
4542
|
const piAgentDir = resolvePiCodingAgentDir();
|
|
4539
4543
|
const piAuthPath = path.join(piAgentDir, "auth.json");
|
|
4540
4544
|
const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
|
|
4545
|
+
if (mode === "host-authenticated") return {
|
|
4546
|
+
moltnetJson: "",
|
|
4547
|
+
agentEnvRaw: "",
|
|
4548
|
+
piAuthJson,
|
|
4549
|
+
agentEnv: {},
|
|
4550
|
+
gitconfig: null,
|
|
4551
|
+
sshPrivateKey: null,
|
|
4552
|
+
sshPublicKey: null,
|
|
4553
|
+
allowedSigners: null,
|
|
4554
|
+
githubAppPem: null,
|
|
4555
|
+
githubAppPemFilename: null
|
|
4556
|
+
};
|
|
4557
|
+
const hasMoltnetJson = existsSync(moltnetPath);
|
|
4558
|
+
const hasAgentEnv = existsSync(agentEnvPath);
|
|
4559
|
+
if (!hasMoltnetJson || !hasAgentEnv) throw new Error(`Guest credential mode requires both ${moltnetPath} and ${agentEnvPath}`);
|
|
4560
|
+
const moltnetJson = readFileSync(moltnetPath, "utf8");
|
|
4561
|
+
const agentEnvRaw = readFileSync(agentEnvPath, "utf8");
|
|
4562
|
+
if (moltnetJson.trim() === "") throw new Error(`Agent configuration is empty: ${moltnetPath}`);
|
|
4541
4563
|
const gitconfigPath = path.join(agentDir, "gitconfig");
|
|
4542
4564
|
const gitconfig = existsSync(gitconfigPath) ? readFileSync(gitconfigPath, "utf8") : null;
|
|
4543
4565
|
const sshDir = path.join(agentDir, "ssh");
|
|
@@ -4546,9 +4568,13 @@ function loadCredentials(agentDir) {
|
|
|
4546
4568
|
const allowedSigners = existsSync(path.join(sshDir, "allowed_signers")) ? readFileSync(path.join(sshDir, "allowed_signers"), "utf8") : null;
|
|
4547
4569
|
let githubAppPem = null;
|
|
4548
4570
|
let githubAppPemFilename = null;
|
|
4549
|
-
const pemPath = JSON.parse(moltnetJson)
|
|
4550
|
-
if (pemPath) if (!existsSync(pemPath))
|
|
4551
|
-
|
|
4571
|
+
const pemPath = (moltnetJson ? JSON.parse(moltnetJson) : null)?.github?.private_key_path;
|
|
4572
|
+
if (pemPath) if (!existsSync(pemPath)) onDiagnostic?.({
|
|
4573
|
+
event: "vm.credentials.github_key_missing",
|
|
4574
|
+
level: "warning",
|
|
4575
|
+
credentialMode: mode,
|
|
4576
|
+
message: `github.private_key_path not found at ${pemPath}; moltnet github token will fail inside the guest`
|
|
4577
|
+
});
|
|
4552
4578
|
else {
|
|
4553
4579
|
githubAppPem = readFileSync(pemPath, "utf8");
|
|
4554
4580
|
githubAppPemFilename = path.basename(pemPath);
|
|
@@ -4594,6 +4620,70 @@ var BASE_ALLOWED_HOSTS = [
|
|
|
4594
4620
|
"storage.googleapis.com",
|
|
4595
4621
|
"*.googlesource.com"
|
|
4596
4622
|
];
|
|
4623
|
+
var DEFAULT_MOLTNET_API_URL = "https://api.themolt.net";
|
|
4624
|
+
/**
|
|
4625
|
+
* Host environment names that may intentionally cross into a
|
|
4626
|
+
* host-authenticated guest. This local list is the authority boundary;
|
|
4627
|
+
* server-supplied runtime-profile `requiredEnv` cannot widen it.
|
|
4628
|
+
*/
|
|
4629
|
+
var HOST_AUTHENTICATED_GUEST_ENV_ALLOWLIST = new Set([
|
|
4630
|
+
"ANTHROPIC_API_KEY",
|
|
4631
|
+
"OPENAI_API_KEY",
|
|
4632
|
+
"OPENAI_BASE_URL",
|
|
4633
|
+
"AZURE_OPENAI_API_KEY",
|
|
4634
|
+
"AZURE_OPENAI_ENDPOINT",
|
|
4635
|
+
"AZURE_OPENAI_API_VERSION",
|
|
4636
|
+
"GOOGLE_API_KEY",
|
|
4637
|
+
"GEMINI_API_KEY",
|
|
4638
|
+
"MISTRAL_API_KEY",
|
|
4639
|
+
"GROQ_API_KEY",
|
|
4640
|
+
"OPENROUTER_API_KEY",
|
|
4641
|
+
"XAI_API_KEY",
|
|
4642
|
+
"CEREBRAS_API_KEY",
|
|
4643
|
+
"DEEPSEEK_API_KEY",
|
|
4644
|
+
"OLLAMA_API_KEY",
|
|
4645
|
+
"OLLAMA_BASE_URL",
|
|
4646
|
+
"AWS_ACCESS_KEY_ID",
|
|
4647
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
4648
|
+
"AWS_SESSION_TOKEN",
|
|
4649
|
+
"AWS_REGION",
|
|
4650
|
+
"AWS_DEFAULT_REGION",
|
|
4651
|
+
"GITHUB_TOKEN",
|
|
4652
|
+
"GH_TOKEN",
|
|
4653
|
+
"LINEAR_API_KEY"
|
|
4654
|
+
]);
|
|
4655
|
+
var RESERVED_GUEST_ENVIRONMENT_NAMES = new Set([
|
|
4656
|
+
"PATH",
|
|
4657
|
+
"HOME",
|
|
4658
|
+
"NODE_EXTRA_CA_CERTS",
|
|
4659
|
+
"MOLTNET_GUEST_WORKSPACE",
|
|
4660
|
+
"GIT_SSH",
|
|
4661
|
+
"GIT_SSH_COMMAND",
|
|
4662
|
+
"SSH_AUTH_SOCK"
|
|
4663
|
+
]);
|
|
4664
|
+
function isReservedGuestEnvironmentName(name) {
|
|
4665
|
+
return name.startsWith("MOLTNET_") || name.startsWith("GIT_CONFIG_") || RESERVED_GUEST_ENVIRONMENT_NAMES.has(name);
|
|
4666
|
+
}
|
|
4667
|
+
var GuestEnvironmentBoundaryError = class extends Error {
|
|
4668
|
+
constructor(refusedNames) {
|
|
4669
|
+
super(`Guest credential boundary refuses runtime-controlled environment variables: ${refusedNames.join(", ")}. Remove them from the runtime profile; MoltNet operations use the trusted host-side Agent.`);
|
|
4670
|
+
this.refusedNames = refusedNames;
|
|
4671
|
+
this.name = "GuestEnvironmentBoundaryError";
|
|
4672
|
+
}
|
|
4673
|
+
};
|
|
4674
|
+
function assertGuestEnvironmentBoundary(options) {
|
|
4675
|
+
const refusedForwardEnv = (options.forwardEnv ?? []).filter((name) => isReservedGuestEnvironmentName(name) || options.guestCredentialMode === "host-authenticated" && !HOST_AUTHENTICATED_GUEST_ENV_ALLOWLIST.has(name));
|
|
4676
|
+
const refusedSandboxEnv = Object.keys(options.sandboxEnv ?? {}).filter(isReservedGuestEnvironmentName);
|
|
4677
|
+
const refused = [...new Set([...refusedForwardEnv, ...refusedSandboxEnv])].sort();
|
|
4678
|
+
if (refused.length > 0) throw new GuestEnvironmentBoundaryError(refused);
|
|
4679
|
+
}
|
|
4680
|
+
/** @deprecated Prefer assertGuestEnvironmentBoundary for mode-aware checks. */
|
|
4681
|
+
function assertHostAuthenticatedGuestEnvironment(options) {
|
|
4682
|
+
assertGuestEnvironmentBoundary({
|
|
4683
|
+
guestCredentialMode: "host-authenticated",
|
|
4684
|
+
...options
|
|
4685
|
+
});
|
|
4686
|
+
}
|
|
4597
4687
|
/**
|
|
4598
4688
|
* Return whether two Gondolin hostname globs can match at least one common
|
|
4599
4689
|
* string. Each `*` is an arbitrary substring, so this walks the product of the
|
|
@@ -4665,10 +4755,22 @@ async function resumeVm(config) {
|
|
|
4665
4755
|
throwIfAborted(config.signal, "VM resume");
|
|
4666
4756
|
const agentDir = resolveVmAgentDir(config);
|
|
4667
4757
|
const guestWorkspace = path.resolve(config.mountPath);
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4758
|
+
const guestCredentialMode = config.guestCredentialMode ?? "guest-config";
|
|
4759
|
+
if (guestCredentialMode === "guest-config" && !existsSync(agentDir)) throw new Error(`Agent directory not found: ${agentDir}. Run: moltnet register --name ${config.agentName}`);
|
|
4760
|
+
assertGuestEnvironmentBoundary({
|
|
4761
|
+
guestCredentialMode,
|
|
4762
|
+
forwardEnv: config.forwardEnv,
|
|
4763
|
+
sandboxEnv: config.sandboxConfig?.env
|
|
4764
|
+
});
|
|
4765
|
+
config.onDiagnostic?.({
|
|
4766
|
+
event: "vm.credentials.mode",
|
|
4767
|
+
level: "info",
|
|
4768
|
+
credentialMode: guestCredentialMode,
|
|
4769
|
+
message: guestCredentialMode === "host-authenticated" ? "MoltNet agent files and non-allowlisted host environment variables are withheld from the guest" : "The complete MoltNet agent configuration is available to the guest"
|
|
4770
|
+
});
|
|
4771
|
+
const creds = loadCredentials(agentDir, guestCredentialMode, config.onDiagnostic);
|
|
4772
|
+
const configuredApiUrl = creds.moltnetJson ? JSON.parse(creds.moltnetJson).endpoints.api : void 0;
|
|
4773
|
+
const apiHost = new URL(configuredApiUrl ?? process.env.MOLTNET_API_URL ?? DEFAULT_MOLTNET_API_URL).hostname;
|
|
4672
4774
|
const runtimeAllowedHosts = config.sandboxConfig?.network?.allowedHosts ?? [];
|
|
4673
4775
|
const runtimeAllowedInternalHosts = config.sandboxConfig?.network?.allowedInternalHosts ?? [];
|
|
4674
4776
|
const protectedExternalHosts = [...new Set([
|
|
@@ -4689,7 +4791,7 @@ async function resumeVm(config) {
|
|
|
4689
4791
|
else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `${vmAgentDir}/${path.basename(v)}`;
|
|
4690
4792
|
else vmAgentEnv[k] = v;
|
|
4691
4793
|
}
|
|
4692
|
-
vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
|
|
4794
|
+
if (creds.moltnetJson) vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
|
|
4693
4795
|
const vfsConfig = resolveVfsShadowConfig(config.sandboxConfig);
|
|
4694
4796
|
let workspaceProvider = new RealFSProvider(config.mountPath);
|
|
4695
4797
|
workspaceProvider = new ShadowProvider(workspaceProvider, {
|
|
@@ -4705,6 +4807,11 @@ async function resumeVm(config) {
|
|
|
4705
4807
|
writeMode: vfsConfig.mode
|
|
4706
4808
|
});
|
|
4707
4809
|
}
|
|
4810
|
+
if (guestCredentialMode === "host-authenticated") workspaceProvider = new ShadowProvider(workspaceProvider, {
|
|
4811
|
+
shouldShadow: ({ path: shadowPath }) => shadowPath.split("/").includes(".moltnet"),
|
|
4812
|
+
denySymlinkBypass: true,
|
|
4813
|
+
writeMode: "deny"
|
|
4814
|
+
});
|
|
4708
4815
|
const forwardedEnv = {};
|
|
4709
4816
|
for (const name of config.forwardEnv ?? []) {
|
|
4710
4817
|
const value = process.env[name];
|
|
@@ -4777,44 +4884,47 @@ async function resumeVm(config) {
|
|
|
4777
4884
|
if (lastErr) throw lastErr instanceof Error ? lastErr : new Error(nonErrorMessage(lastErr));
|
|
4778
4885
|
}
|
|
4779
4886
|
const vmSshDir = `${vmAgentDir}/ssh`;
|
|
4780
|
-
|
|
4887
|
+
const hasAgentFiles = guestCredentialMode === "guest-config";
|
|
4888
|
+
await vm.exec(hasAgentFiles ? `mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent` : "mkdir -p /home/agent/.pi/agent", { signal: config.signal });
|
|
4781
4889
|
if (creds.piAuthJson !== null) await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, {
|
|
4782
4890
|
mode: 384,
|
|
4783
4891
|
signal: config.signal
|
|
4784
4892
|
});
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4893
|
+
if (hasAgentFiles) {
|
|
4894
|
+
const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
|
|
4895
|
+
await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, {
|
|
4896
|
+
mode: 384,
|
|
4897
|
+
signal: config.signal
|
|
4898
|
+
});
|
|
4899
|
+
await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, {
|
|
4900
|
+
mode: 384,
|
|
4901
|
+
signal: config.signal
|
|
4902
|
+
});
|
|
4903
|
+
if (creds.gitconfig) {
|
|
4904
|
+
const vmGitconfig = rewriteGitconfigPaths(creds.gitconfig, vmSshDir, vmAgentDir);
|
|
4905
|
+
await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, {
|
|
4906
|
+
mode: 420,
|
|
4907
|
+
signal: config.signal
|
|
4908
|
+
});
|
|
4909
|
+
}
|
|
4910
|
+
if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, {
|
|
4911
|
+
mode: 384,
|
|
4912
|
+
signal: config.signal
|
|
4913
|
+
});
|
|
4914
|
+
if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, {
|
|
4797
4915
|
mode: 420,
|
|
4798
4916
|
signal: config.signal
|
|
4799
4917
|
});
|
|
4918
|
+
if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, {
|
|
4919
|
+
mode: 420,
|
|
4920
|
+
signal: config.signal
|
|
4921
|
+
});
|
|
4922
|
+
if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, {
|
|
4923
|
+
mode: 384,
|
|
4924
|
+
signal: config.signal
|
|
4925
|
+
});
|
|
4800
4926
|
}
|
|
4801
|
-
|
|
4802
|
-
mode: 384,
|
|
4803
|
-
signal: config.signal
|
|
4804
|
-
});
|
|
4805
|
-
if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, {
|
|
4806
|
-
mode: 420,
|
|
4807
|
-
signal: config.signal
|
|
4808
|
-
});
|
|
4809
|
-
if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, {
|
|
4810
|
-
mode: 420,
|
|
4811
|
-
signal: config.signal
|
|
4812
|
-
});
|
|
4813
|
-
if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, {
|
|
4814
|
-
mode: 384,
|
|
4815
|
-
signal: config.signal
|
|
4816
|
-
});
|
|
4817
|
-
await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet", { signal: config.signal });
|
|
4927
|
+
await vm.exec(hasAgentFiles ? "chown -R agent:agent /home/agent/.pi /home/agent/.moltnet" : "chown -R agent:agent /home/agent/.pi", { signal: config.signal });
|
|
4818
4928
|
return {
|
|
4819
4929
|
vm,
|
|
4820
4930
|
credentials: creds,
|
|
@@ -5314,16 +5424,25 @@ function createGondolinBashOps(vm, localCwd, guestWorkspace) {
|
|
|
5314
5424
|
* capability-aware allow-set) is tracked as future work.
|
|
5315
5425
|
*/
|
|
5316
5426
|
function decideToolCall(input) {
|
|
5317
|
-
if (input.enforcement === "off") return {
|
|
5318
|
-
|
|
5427
|
+
if (input.enforcement === "off") return {
|
|
5428
|
+
allow: true,
|
|
5429
|
+
reasonCode: "policy_off"
|
|
5430
|
+
};
|
|
5431
|
+
if (input.toolName.startsWith("submit_") || input.toolName === "subagent") return {
|
|
5432
|
+
allow: true,
|
|
5433
|
+
reasonCode: "executor_protocol_tool"
|
|
5434
|
+
};
|
|
5319
5435
|
const resolved = resolveNames(input);
|
|
5320
|
-
if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell command could not be statically authorized", "unresolvable shell command (watch)");
|
|
5436
|
+
if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell_command_unresolvable", "shell command could not be statically authorized", "unresolvable shell command (watch)");
|
|
5321
5437
|
const arbitraryCode = [...new Set(resolved.tools.filter((tool) => tool.risk === "arbitrary-code").map((tool) => tool.name))];
|
|
5322
|
-
if (arbitraryCode.length > 0) return fenced(input.enforcement, `arbitrary-code interpreter not authorizable by tool policy: ${arbitraryCode.join(", ")}`, `would block — arbitrary-code interpreter (watch): ${arbitraryCode.join(", ")}`, arbitraryCode);
|
|
5438
|
+
if (arbitraryCode.length > 0) return fenced(input.enforcement, "arbitrary_code_interpreter", `arbitrary-code interpreter not authorizable by tool policy: ${arbitraryCode.join(", ")}`, `would block — arbitrary-code interpreter (watch): ${arbitraryCode.join(", ")}`, arbitraryCode);
|
|
5323
5439
|
if (input.toolName !== "bash") {
|
|
5324
5440
|
const missing = resolved.tools.map((tool) => tool.name).filter((name) => !input.allowedTools.has(name));
|
|
5325
|
-
if (missing.length === 0) return {
|
|
5326
|
-
|
|
5441
|
+
if (missing.length === 0) return {
|
|
5442
|
+
allow: true,
|
|
5443
|
+
reasonCode: "policy_allowed"
|
|
5444
|
+
};
|
|
5445
|
+
return fenced(input.enforcement, "tool_not_permitted", `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
|
|
5327
5446
|
}
|
|
5328
5447
|
const matchedShellCommands = [];
|
|
5329
5448
|
const missingShellCommands = resolved.tools.filter((tool) => {
|
|
@@ -5333,7 +5452,7 @@ function decideToolCall(input) {
|
|
|
5333
5452
|
matchedShellCommands.push(toMatchedShellCommand(tool.name, matched.argvPrefix));
|
|
5334
5453
|
return false;
|
|
5335
5454
|
}).map(toMissingShellCommand);
|
|
5336
|
-
if (missingShellCommands.length === 0 && resolved.hasOutputRedirection && matchedShellCommands.length > 0) return fenced(input.enforcement, "shell output redirection requires broad executable permission", "would block shell output redirection under scoped command policy", [...new Set(matchedShellCommands.map(({ executable }) => executable))], matchedShellCommands.map(({ executable, argvPrefixFingerprint, argvPrefixLength }) => ({
|
|
5455
|
+
if (missingShellCommands.length === 0 && resolved.hasOutputRedirection && matchedShellCommands.length > 0) return fenced(input.enforcement, "shell_output_redirection_requires_broad_permission", "shell output redirection requires broad executable permission", "would block shell output redirection under scoped command policy", [...new Set(matchedShellCommands.map(({ executable }) => executable))], matchedShellCommands.map(({ executable, argvPrefixFingerprint, argvPrefixLength }) => ({
|
|
5337
5456
|
executable,
|
|
5338
5457
|
argvFingerprint: argvPrefixFingerprint,
|
|
5339
5458
|
argvLength: argvPrefixLength,
|
|
@@ -5341,10 +5460,14 @@ function decideToolCall(input) {
|
|
|
5341
5460
|
})));
|
|
5342
5461
|
if (missingShellCommands.length === 0) return matchedShellCommands.length > 0 ? {
|
|
5343
5462
|
allow: true,
|
|
5463
|
+
reasonCode: "shell_command_prefix_allowed",
|
|
5344
5464
|
matchedShellCommands
|
|
5345
|
-
} : {
|
|
5465
|
+
} : {
|
|
5466
|
+
allow: true,
|
|
5467
|
+
reasonCode: "policy_allowed"
|
|
5468
|
+
};
|
|
5346
5469
|
const missing = [...new Set(missingShellCommands.map(({ executable }) => executable))];
|
|
5347
|
-
return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
|
|
5470
|
+
return fenced(input.enforcement, "tool_not_permitted", `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
|
|
5348
5471
|
}
|
|
5349
5472
|
function fingerprintArgv(argv) {
|
|
5350
5473
|
return `sha256:${createHash$1("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
|
|
@@ -5371,14 +5494,16 @@ function matchesArgvPrefix(argv, prefix) {
|
|
|
5371
5494
|
* Shared enforce/watch branch: block in `enforce`, audit-and-allow in `watch`.
|
|
5372
5495
|
* `enforcement` is never `off` here (short-circuited by the caller).
|
|
5373
5496
|
*/
|
|
5374
|
-
function fenced(enforcement, blockReason, auditReason, missing, missingShellCommands) {
|
|
5497
|
+
function fenced(enforcement, reasonCode, blockReason, auditReason, missing, missingShellCommands) {
|
|
5375
5498
|
if (enforcement === "enforce") return {
|
|
5376
5499
|
allow: false,
|
|
5500
|
+
reasonCode,
|
|
5377
5501
|
reason: blockReason,
|
|
5378
5502
|
...missing ? { missing } : {},
|
|
5379
5503
|
...missingShellCommands?.length ? { missingShellCommands } : {}
|
|
5380
5504
|
};
|
|
5381
5505
|
return {
|
|
5506
|
+
reasonCode,
|
|
5382
5507
|
audit: auditReason,
|
|
5383
5508
|
...missing ? { missing } : {},
|
|
5384
5509
|
...missingShellCommands?.length ? { missingShellCommands } : {}
|
|
@@ -5421,8 +5546,9 @@ function resolveNames(input) {
|
|
|
5421
5546
|
/**
|
|
5422
5547
|
* Resolve the session's tool policy at start-up.
|
|
5423
5548
|
*
|
|
5424
|
-
*
|
|
5425
|
-
*
|
|
5549
|
+
* The latest allowed-tool set and enforcement mode are fetched from the API,
|
|
5550
|
+
* including when the daemon's cached profile says `off`. If that fetch fails,
|
|
5551
|
+
* the cached mode decides the fallback:
|
|
5426
5552
|
* `enforce` **fails closed** (empty allow-set → every non-`off` tool is
|
|
5427
5553
|
* blocked); `watch` fails open (empty allow-set → every tool is audited but
|
|
5428
5554
|
* allowed).
|
|
@@ -5433,12 +5559,6 @@ function resolveNames(input) {
|
|
|
5433
5559
|
* session, stable enforcement for the run) accepted over re-fetching per call.
|
|
5434
5560
|
*/
|
|
5435
5561
|
async function resolveSessionToolPolicy(input) {
|
|
5436
|
-
if (input.enforcement === "off") return {
|
|
5437
|
-
enforcement: "off",
|
|
5438
|
-
allowedTools: /* @__PURE__ */ new Set(),
|
|
5439
|
-
allowedShellCommands: [],
|
|
5440
|
-
degraded: false
|
|
5441
|
-
};
|
|
5442
5562
|
const timeoutMs = input.timeoutMs ?? 5e3;
|
|
5443
5563
|
try {
|
|
5444
5564
|
const resolved = await withTimeout$1(input.agent.runtimeProfiles.allowedTools(input.profileId, { teamId: input.teamId }), timeoutMs);
|
|
@@ -5454,6 +5574,8 @@ async function resolveSessionToolPolicy(input) {
|
|
|
5454
5574
|
if (rule.argvPrefix.length < 2 || rule.argvPrefix.length > 8 || rule.argvPrefix.some((token) => !token)) throw new Error("runtime returned an invalid shell command rule");
|
|
5455
5575
|
return { argvPrefix: rule.argvPrefix };
|
|
5456
5576
|
}),
|
|
5577
|
+
executionPolicySnapshotHash: resolved.policySnapshotHash,
|
|
5578
|
+
executionRuntimeProfileRevision: resolved.runtimeProfileRevision,
|
|
5457
5579
|
degraded: false
|
|
5458
5580
|
};
|
|
5459
5581
|
} catch (err) {
|
|
@@ -5526,34 +5648,46 @@ function decideForEvent(event, policy, analyze) {
|
|
|
5526
5648
|
* `off`. Register it in a session's `extensionFactories`.
|
|
5527
5649
|
*/
|
|
5528
5650
|
function createToolPolicyExtension(deps) {
|
|
5651
|
+
if (deps.context?.claimPolicySnapshotHash && deps.policy.executionPolicySnapshotHash && deps.context.claimPolicySnapshotHash !== deps.policy.executionPolicySnapshotHash) deps.logger.info({
|
|
5652
|
+
...decisionContext(deps),
|
|
5653
|
+
decision: "continue",
|
|
5654
|
+
reason: "claim_execution_policy_drift"
|
|
5655
|
+
}, "tool_policy.snapshot_drift");
|
|
5529
5656
|
return (pi) => {
|
|
5530
5657
|
if (deps.policy.enforcement === "off") return;
|
|
5531
5658
|
pi.on("tool_call", (event) => {
|
|
5532
5659
|
const decision = decideForEvent(event, deps.policy, (command) => deps.analyzer.analyze(command));
|
|
5533
5660
|
if ("allow" in decision && decision.allow) {
|
|
5534
|
-
|
|
5661
|
+
deps.logger.info({
|
|
5662
|
+
...decisionContext(deps),
|
|
5535
5663
|
toolName: event.toolName,
|
|
5536
5664
|
toolCallId: event.toolCallId,
|
|
5537
|
-
|
|
5538
|
-
|
|
5665
|
+
decision: "allowed",
|
|
5666
|
+
reason: decision.reasonCode,
|
|
5667
|
+
...decision.matchedShellCommands?.length ? { shellFingerprints: decision.matchedShellCommands } : {}
|
|
5668
|
+
}, "tool_policy.allowed");
|
|
5539
5669
|
return;
|
|
5540
5670
|
}
|
|
5541
5671
|
if ("audit" in decision) {
|
|
5542
5672
|
deps.logger.info({
|
|
5673
|
+
...decisionContext(deps),
|
|
5543
5674
|
toolName: event.toolName,
|
|
5544
5675
|
toolCallId: event.toolCallId,
|
|
5545
|
-
|
|
5546
|
-
decision
|
|
5676
|
+
decision: "audit",
|
|
5677
|
+
reason: decision.reasonCode,
|
|
5678
|
+
...decision.missing?.length ? { missingExecutables: decision.missing } : {},
|
|
5679
|
+
...decision.missingShellCommands?.length ? { shellFingerprints: decision.missingShellCommands } : {}
|
|
5547
5680
|
}, "tool_policy.audit");
|
|
5548
5681
|
return;
|
|
5549
5682
|
}
|
|
5550
5683
|
deps.logger.warn({
|
|
5684
|
+
...decisionContext(deps),
|
|
5551
5685
|
toolName: event.toolName,
|
|
5552
5686
|
toolCallId: event.toolCallId,
|
|
5553
|
-
|
|
5554
|
-
reason: decision.
|
|
5555
|
-
missingExecutables: decision.missing,
|
|
5556
|
-
missingShellCommands: decision.missingShellCommands
|
|
5687
|
+
decision: "blocked",
|
|
5688
|
+
reason: decision.reasonCode,
|
|
5689
|
+
...decision.missing?.length ? { missingExecutables: decision.missing } : {},
|
|
5690
|
+
...decision.missingShellCommands?.length ? { shellFingerprints: decision.missingShellCommands } : {}
|
|
5557
5691
|
}, "tool_policy.blocked");
|
|
5558
5692
|
return {
|
|
5559
5693
|
block: true,
|
|
@@ -5562,6 +5696,15 @@ function createToolPolicyExtension(deps) {
|
|
|
5562
5696
|
});
|
|
5563
5697
|
};
|
|
5564
5698
|
}
|
|
5699
|
+
function decisionContext(deps) {
|
|
5700
|
+
return {
|
|
5701
|
+
...deps.context ?? {},
|
|
5702
|
+
enforcement: deps.policy.enforcement,
|
|
5703
|
+
degraded: deps.policy.degraded,
|
|
5704
|
+
executionPolicySnapshotHash: deps.policy.executionPolicySnapshotHash,
|
|
5705
|
+
executionRuntimeProfileRevision: deps.policy.executionRuntimeProfileRevision
|
|
5706
|
+
};
|
|
5707
|
+
}
|
|
5565
5708
|
//#endregion
|
|
5566
5709
|
//#region src/runtime/capability-discovery.ts
|
|
5567
5710
|
var GuestExecutableProbeError = class extends Error {
|
|
@@ -7104,6 +7247,19 @@ var GONDOLIN_TOOL_NAMES = [
|
|
|
7104
7247
|
"find",
|
|
7105
7248
|
"grep"
|
|
7106
7249
|
];
|
|
7250
|
+
var HOST_AUTHENTICATED_HOST_EXEC_REFUSED_ENV = new Set([
|
|
7251
|
+
"GIT_CONFIG_GLOBAL",
|
|
7252
|
+
"MOLTNET_CREDENTIALS_PATH",
|
|
7253
|
+
"SSH_AUTH_SOCK"
|
|
7254
|
+
]);
|
|
7255
|
+
function resolveHostExecBaseEnv(guestCredentialMode, agentEnv) {
|
|
7256
|
+
const names = new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(agentEnv)]);
|
|
7257
|
+
if (guestCredentialMode === "host-authenticated") {
|
|
7258
|
+
for (const name of HOST_AUTHENTICATED_HOST_EXEC_REFUSED_ENV) names.delete(name);
|
|
7259
|
+
for (const name of names) if (name.startsWith("MOLTNET_")) names.delete(name);
|
|
7260
|
+
}
|
|
7261
|
+
return names;
|
|
7262
|
+
}
|
|
7107
7263
|
var noopTurnEventHandler = () => {};
|
|
7108
7264
|
async function openVmWorkspaceFileForRead(config) {
|
|
7109
7265
|
const localPath = isAbsolute(config.filePath) ? config.filePath : resolve(config.cwdPath, config.filePath);
|
|
@@ -7135,6 +7291,16 @@ function createGondolinToolDefinitions(config) {
|
|
|
7135
7291
|
}
|
|
7136
7292
|
];
|
|
7137
7293
|
}
|
|
7294
|
+
function createMoltNetAgentResolver(input) {
|
|
7295
|
+
let resolved;
|
|
7296
|
+
return () => {
|
|
7297
|
+
if (!resolved) resolved = (input.moltnetAgent ? Promise.resolve(input.moltnetAgent) : (input.connectAgent ?? ((configDir) => connect({ configDir })))(input.configDir)).catch((error) => {
|
|
7298
|
+
resolved = void 0;
|
|
7299
|
+
throw error;
|
|
7300
|
+
});
|
|
7301
|
+
return resolved;
|
|
7302
|
+
};
|
|
7303
|
+
}
|
|
7138
7304
|
/**
|
|
7139
7305
|
* Factory that builds a pi-specific `executeTask` function suitable for
|
|
7140
7306
|
* injection into `AgentRuntime`. The returned function caches the resolved
|
|
@@ -7318,11 +7484,13 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7318
7484
|
checkpointPath,
|
|
7319
7485
|
agentName: opts.agentName,
|
|
7320
7486
|
agentRootDir,
|
|
7487
|
+
guestCredentialMode: opts.guestCredentialMode,
|
|
7321
7488
|
mountPath,
|
|
7322
7489
|
workspaceMode: preparedWorkspace.mode,
|
|
7323
7490
|
extraAllowedHosts: opts.extraAllowedHosts,
|
|
7324
7491
|
sandboxConfig: effectiveSandboxConfig,
|
|
7325
7492
|
forwardEnv: opts.forwardEnv,
|
|
7493
|
+
onDiagnostic: opts.onVmDiagnostic,
|
|
7326
7494
|
signal: reporter.cancelSignal
|
|
7327
7495
|
}));
|
|
7328
7496
|
} catch (err) {
|
|
@@ -7339,6 +7507,10 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7339
7507
|
activateAgentEnv(managed.credentials.agentEnv, agentRootDir);
|
|
7340
7508
|
const activeWorkspace = preparedWorkspace;
|
|
7341
7509
|
const activeManaged = managed;
|
|
7510
|
+
const getMoltNetAgent = createMoltNetAgentResolver({
|
|
7511
|
+
moltnetAgent: opts.moltnetAgent,
|
|
7512
|
+
configDir: managed.agentDir
|
|
7513
|
+
});
|
|
7342
7514
|
await emit("info", {
|
|
7343
7515
|
event: "execute_start",
|
|
7344
7516
|
correlationId: task.correlationId ?? null,
|
|
@@ -7353,7 +7525,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7353
7525
|
let resolvedPriorContext;
|
|
7354
7526
|
const continueFrom = task.input?.continueFrom;
|
|
7355
7527
|
if (task.taskType === FREEFORM_TYPE && continueFrom) try {
|
|
7356
|
-
const resolved = await resolvePriorContext(await
|
|
7528
|
+
const resolved = await resolvePriorContext(await getMoltNetAgent(), continueFrom);
|
|
7357
7529
|
if (resolved) {
|
|
7358
7530
|
resolvedPriorContext = resolved;
|
|
7359
7531
|
await emit("info", {
|
|
@@ -7468,7 +7640,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7468
7640
|
});
|
|
7469
7641
|
const submitTools = submitToolDefs;
|
|
7470
7642
|
try {
|
|
7471
|
-
const moltnetAgent = await
|
|
7643
|
+
const moltnetAgent = await getMoltNetAgent();
|
|
7472
7644
|
const moltnetTools = createMoltNetTools({
|
|
7473
7645
|
getAgent: () => moltnetAgent,
|
|
7474
7646
|
getDiaryId: () => diaryId,
|
|
@@ -7482,7 +7654,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7482
7654
|
guestWorkspace: activeManaged.guestWorkspace,
|
|
7483
7655
|
filePath
|
|
7484
7656
|
}),
|
|
7485
|
-
hostExecBaseEnv:
|
|
7657
|
+
hostExecBaseEnv: resolveHostExecBaseEnv(opts.guestCredentialMode ?? "guest-config", managed.credentials.agentEnv),
|
|
7486
7658
|
hostExecAutoApprove: opts.hostExecAutoApprove ?? opts.sandboxConfig?.hostExec?.autoApprove ?? false,
|
|
7487
7659
|
getTaskContext: () => ({
|
|
7488
7660
|
taskId: task.id,
|
|
@@ -7512,28 +7684,34 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7512
7684
|
...obj
|
|
7513
7685
|
}))
|
|
7514
7686
|
};
|
|
7515
|
-
|
|
7516
|
-
|
|
7687
|
+
const toolPolicyDecisionContext = buildToolPolicyDecisionContext(claimedTask, opts.runtimeProfileId);
|
|
7688
|
+
if (opts.runtimeProfileId && opts.toolEnforcement) {
|
|
7689
|
+
const policy = await resolveSessionToolPolicy({
|
|
7517
7690
|
agent: moltnetAgent,
|
|
7518
7691
|
profileId: opts.runtimeProfileId,
|
|
7519
7692
|
teamId: taskTeamId,
|
|
7520
7693
|
runtimeKind: opts.runtimeDefinition?.runtimeKind ?? "gondolin_pi",
|
|
7521
7694
|
enforcement: opts.toolEnforcement,
|
|
7522
7695
|
logger: toolPolicyLogger
|
|
7523
|
-
})
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
allowedShellCommands
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7696
|
+
});
|
|
7697
|
+
resolvedToolPolicy = policy;
|
|
7698
|
+
if (policy.enforcement !== "off") {
|
|
7699
|
+
const analyzer = await ShellCommandAnalyzer.create();
|
|
7700
|
+
verifiedGuestExecutables = (await discoverGuestExecutables(managed.vm, [...policy.allowedShellCommands.map(({ argvPrefix }) => argvPrefix[0]), ...policy.enforcement === "watch" ? resolvedVmTemplate?.executables ?? [] : []], { signal: reporter.cancelSignal })).available;
|
|
7701
|
+
const availableExecutables = new Set(verifiedGuestExecutables);
|
|
7702
|
+
const allowedShellCommands = policy.allowedShellCommands.filter(({ argvPrefix }) => availableExecutables.has(argvPrefix[0]));
|
|
7703
|
+
unavailableRuntimeShellCommands = policy.allowedShellCommands.filter(({ argvPrefix }) => !availableExecutables.has(argvPrefix[0]));
|
|
7704
|
+
resolvedToolPolicy = {
|
|
7705
|
+
...policy,
|
|
7706
|
+
allowedShellCommands
|
|
7707
|
+
};
|
|
7708
|
+
toolPolicyExtensions.push(createToolPolicyExtension({
|
|
7709
|
+
policy: resolvedToolPolicy,
|
|
7710
|
+
analyzer,
|
|
7711
|
+
logger: toolPolicyLogger,
|
|
7712
|
+
context: toolPolicyDecisionContext
|
|
7713
|
+
}));
|
|
7714
|
+
}
|
|
7537
7715
|
}
|
|
7538
7716
|
const runtimeToolContext = {
|
|
7539
7717
|
agent: moltnetAgent,
|
|
@@ -7913,6 +8091,29 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
7913
8091
|
});
|
|
7914
8092
|
}
|
|
7915
8093
|
}
|
|
8094
|
+
function buildToolPolicyDecisionContext(claimedTask, executionRuntimeProfileId) {
|
|
8095
|
+
const { task, attemptN, claimAuthority } = claimedTask;
|
|
8096
|
+
const proposer = task.proposedByAgentId ? {
|
|
8097
|
+
proposerKind: "agent",
|
|
8098
|
+
proposerId: task.proposedByAgentId
|
|
8099
|
+
} : task.proposedByHumanId ? {
|
|
8100
|
+
proposerKind: "human",
|
|
8101
|
+
proposerId: task.proposedByHumanId
|
|
8102
|
+
} : {};
|
|
8103
|
+
return {
|
|
8104
|
+
taskId: task.id,
|
|
8105
|
+
attemptN,
|
|
8106
|
+
teamId: task.teamId,
|
|
8107
|
+
...proposer,
|
|
8108
|
+
...claimAuthority?.claimantAgentId ? { claimantAgentId: claimAuthority.claimantAgentId } : {},
|
|
8109
|
+
...claimAuthority?.leaseId ? { leaseId: claimAuthority.leaseId } : {},
|
|
8110
|
+
...claimAuthority?.runtimeProfileId ? { claimRuntimeProfileId: claimAuthority.runtimeProfileId } : {},
|
|
8111
|
+
...executionRuntimeProfileId ? { executionRuntimeProfileId } : {},
|
|
8112
|
+
...typeof claimAuthority?.runtimeProfileRevision === "number" ? { claimRuntimeProfileRevision: claimAuthority.runtimeProfileRevision } : {},
|
|
8113
|
+
...claimAuthority?.policySnapshotHash ? { claimPolicySnapshotHash: claimAuthority.policySnapshotHash } : {},
|
|
8114
|
+
...claimAuthority?.executorFingerprint ? { claimedExecutorFingerprint: claimAuthority.executorFingerprint } : {}
|
|
8115
|
+
};
|
|
8116
|
+
}
|
|
7916
8117
|
function createSessionTurnState() {
|
|
7917
8118
|
return {
|
|
7918
8119
|
assistantText: "",
|
|
@@ -8564,4 +8765,4 @@ function describeToolErrorMessage(result) {
|
|
|
8564
8765
|
}
|
|
8565
8766
|
}
|
|
8566
8767
|
//#endregion
|
|
8567
|
-
export { GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, HOST_EXEC_DEFAULT_BASE_ENV, MOLTNET_TOOL_NAMES, PI_EXECUTOR_MANIFEST_VERSION, PI_RUNTIME_DEFINITION_VERSION, activateAgentEnv, buildAgentSession, buildPiExecutorManifest, buildRuntimeKernel, buildWorkspaceMountInstructions, createGondolinBashOps, createGondolinEditOps, createGondolinFindOps, createGondolinLsOps, createGondolinReadOps, createGondolinToolDefinitions, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, createToolPolicyExtension, decideForEvent, decideToolCall, defineGondolinTemplate, definePiExtension, definePiRuntime, definePiTool, enabledPiToolNames, ensureSnapshot, executeGondolinGrep, executePiTask, filterModelVisibleTools, findMainWorktree, injectRuntimeContext as injectTaskContext, isKernelTool, isResolvedPathInsideRoot, isToolVisible, loadCredentials, materializePiExtensions, materializePiTools, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveSessionToolPolicy, resolveTaskWorktreePath, resumeVm, toGuestPath };
|
|
8768
|
+
export { GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, GuestEnvironmentBoundaryError, HOST_EXEC_DEFAULT_BASE_ENV, MOLTNET_TOOL_NAMES, PI_EXECUTOR_MANIFEST_VERSION, PI_RUNTIME_DEFINITION_VERSION, activateAgentEnv, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, buildAgentSession, buildPiExecutorManifest, buildRuntimeKernel, buildWorkspaceMountInstructions, createGondolinBashOps, createGondolinEditOps, createGondolinFindOps, createGondolinLsOps, createGondolinReadOps, createGondolinToolDefinitions, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, createToolPolicyExtension, decideForEvent, decideToolCall, defineGondolinTemplate, definePiExtension, definePiRuntime, definePiTool, enabledPiToolNames, ensureSnapshot, executeGondolinGrep, executePiTask, filterModelVisibleTools, findMainWorktree, injectRuntimeContext as injectTaskContext, isKernelTool, isResolvedPathInsideRoot, isToolVisible, loadCredentials, materializePiExtensions, materializePiTools, normalizeRetryTriageResult, 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.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Composable MoltNet runtime kernel for Pi agents in Gondolin VMs",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"@opentelemetry/api": "^1.9.0",
|
|
32
32
|
"multiformats": "^13.3.0",
|
|
33
33
|
"typebox": "^1.2.8",
|
|
34
|
-
"@themoltnet/agent-runtime": "0.
|
|
35
|
-
"@themoltnet/
|
|
36
|
-
"@themoltnet/
|
|
37
|
-
"@themoltnet/
|
|
34
|
+
"@themoltnet/agent-runtime": "0.42.0",
|
|
35
|
+
"@themoltnet/os-keyring": "0.2.0",
|
|
36
|
+
"@themoltnet/sdk": "0.133.0",
|
|
37
|
+
"@themoltnet/shell-command-analyzer": "0.3.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@earendil-works/pi-ai": "0.79.4",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"vite": "^8.0.0",
|
|
52
52
|
"vite-plugin-dts": "^4.5.4",
|
|
53
53
|
"vitest": "^3.0.0",
|
|
54
|
-
"@moltnet/crypto-service": "0.1.0",
|
|
55
54
|
"@moltnet/models": "0.1.0",
|
|
56
|
-
"@moltnet/tasks": "0.1.0"
|
|
55
|
+
"@moltnet/tasks": "0.1.0",
|
|
56
|
+
"@moltnet/crypto-service": "0.1.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=24"
|