@themoltnet/pi-runtime 0.8.0 → 0.10.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 CHANGED
@@ -49,6 +49,8 @@ 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
  }
@@ -446,8 +448,8 @@ export declare interface ExecutePiTaskOptions {
446
448
  runtimeProfileContext?: readonly ContextRef[];
447
449
  /**
448
450
  * Runtime profile id, used to resolve the tool-policy allow-set at session
449
- * start. Required together with a non-`off` `toolEnforcement` for the
450
- * `tool_call` gate to run.
451
+ * start. Required with `toolEnforcement`; the resolved mode determines
452
+ * whether a `tool_call` gate is installed.
451
453
  */
452
454
  runtimeProfileId?: string;
453
455
  /** Tool-policy enforcement mode for the selected runtime profile. */
@@ -604,21 +606,18 @@ export declare function filterModelVisibleTools(tools: readonly ToolDefinition[]
604
606
  */
605
607
  export declare function findMainWorktree(startPath?: string): string;
606
608
 
607
- /**
608
- * The gate's verdict:
609
- * - `{ allow: true }` — let the tool run.
610
- * - `{ allow: false, reason }` — block it (enforce mode).
611
- * - `{ audit, ... }` — would-block, but proceed and record it (watch mode).
612
- */
613
609
  export declare type GateDecision = {
614
610
  allow: true;
611
+ reasonCode: ToolPolicyDecisionReason;
615
612
  matchedShellCommands?: MatchedShellCommand[];
616
613
  } | {
617
614
  allow: false;
615
+ reasonCode: ToolPolicyDecisionReason;
618
616
  reason: string;
619
617
  missing?: string[];
620
618
  missingShellCommands?: MissingShellCommand[];
621
619
  } | {
620
+ reasonCode: ToolPolicyDecisionReason;
622
621
  audit: string;
623
622
  missing?: string[];
624
623
  missingShellCommands?: MissingShellCommand[];
@@ -850,6 +849,12 @@ export declare interface MoltNetToolsConfig {
850
849
  * entry creation behaves as before (env-derived diary, no auto-tags).
851
850
  */
852
851
  getTaskContext?(): MoltNetTaskContext | null;
852
+ /** Records recoverable task-provenance failures without ending the task. */
853
+ onTaskProvenanceEvent?(event: 'task.provenance.entry_denied', details: {
854
+ taskId: string;
855
+ diaryId: string;
856
+ error: string;
857
+ }): void | Promise<void>;
853
858
  }
854
859
 
855
860
  export declare function normalizeRetryTriageResult(value: unknown): PiRetryTriageResult;
@@ -1106,8 +1111,9 @@ export declare function resolveHostExecBaseEnv(guestCredentialMode: GuestCredent
1106
1111
  /**
1107
1112
  * Resolve the session's tool policy at start-up.
1108
1113
  *
1109
- * `off` short-circuits without a network call. Otherwise the allowed-tool set
1110
- * is fetched from the API. If that fetch fails, the mode decides the fallback:
1114
+ * The latest allowed-tool set and enforcement mode are fetched from the API,
1115
+ * including when the daemon's cached profile says `off`. If that fetch fails,
1116
+ * the cached mode decides the fallback:
1111
1117
  * `enforce` **fails closed** (empty allow-set → every non-`off` tool is
1112
1118
  * blocked); `watch` fails open (empty allow-set → every tool is audited but
1113
1119
  * allowed).
@@ -1291,6 +1297,10 @@ export declare interface SessionToolPolicy {
1291
1297
  enforcement: ToolEnforcement;
1292
1298
  allowedTools: ReadonlySet<string>;
1293
1299
  allowedShellCommands: readonly ShellCommandRule[];
1300
+ /** Latest effective policy hash resolved for this Pi session. */
1301
+ executionPolicySnapshotHash?: string;
1302
+ /** Latest runtime profile revision resolved with the session policy. */
1303
+ executionRuntimeProfileRevision?: number;
1294
1304
  /**
1295
1305
  * `true` when the allow-set is a **degraded fallback** — the allowed-tools
1296
1306
  * fetch failed or timed out and this policy is the fail-closed/fail-open
@@ -1298,7 +1308,7 @@ export declare interface SessionToolPolicy {
1298
1308
  * empty-but-resolved policy (e.g. a profile with no bound tools) has
1299
1309
  * `degraded: false`. Surfaced in every audit/block log so an operator can tell
1300
1310
  * "blocked because the policy is empty" from "blocked because we couldn't read
1301
- * the policy". `off` and successful resolutions are never degraded.
1311
+ * the policy". Successful resolutions are never degraded.
1302
1312
  */
1303
1313
  degraded: boolean;
1304
1314
  }
@@ -1354,10 +1364,35 @@ declare const ToolEnforcementSchema = Type.Union(toolEnforcementLiterals, {
1354
1364
  'Runtime tool-policy enforcement mode: off (inert), watch (audit only), enforce (block disallowed tools, fail-closed).',
1355
1365
  });
1356
1366
 
1367
+ /** Correlation and claim evidence repeated on every tool-policy decision. */
1368
+ export declare interface ToolPolicyDecisionContext {
1369
+ taskId: string;
1370
+ attemptN: number;
1371
+ teamId: string;
1372
+ claimantAgentId?: string;
1373
+ leaseId?: string;
1374
+ proposerKind?: 'agent' | 'human';
1375
+ proposerId?: string;
1376
+ claimRuntimeProfileId?: string;
1377
+ executionRuntimeProfileId?: string;
1378
+ claimRuntimeProfileRevision?: number;
1379
+ claimPolicySnapshotHash?: string;
1380
+ claimedExecutorFingerprint?: string;
1381
+ }
1382
+
1383
+ /**
1384
+ * The gate's verdict:
1385
+ * - `{ allow: true }` — let the tool run.
1386
+ * - `{ allow: false, reason }` — block it (enforce mode).
1387
+ * - `{ audit, ... }` — would-block, but proceed and record it (watch mode).
1388
+ */
1389
+ 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';
1390
+
1357
1391
  export declare interface ToolPolicyExtensionDeps {
1358
1392
  policy: SessionToolPolicy;
1359
1393
  analyzer: ShellCommandAnalyzer;
1360
1394
  logger: ToolPolicyLogger;
1395
+ context?: ToolPolicyDecisionContext;
1361
1396
  }
1362
1397
 
1363
1398
  /**
package/dist/index.js CHANGED
@@ -678,13 +678,27 @@ function createMoltNetTools(config) {
678
678
  } else targetDiaryId = params.diaryId ?? envDiaryId;
679
679
  const userTags = params.tags ?? [];
680
680
  const mergedTags = autoTags.length ? [...autoTags, ...userTags.filter((t) => !autoTags.includes(t))] : userTags;
681
- const entry = await agent.entries.create(targetDiaryId, {
682
- title: params.title,
683
- content: params.content,
684
- tags: mergedTags,
685
- importance: params.importance ?? 5,
686
- ...params.entryType ? { entryType: params.entryType } : {}
687
- });
681
+ let entry;
682
+ try {
683
+ entry = await agent.entries.create(targetDiaryId, {
684
+ title: params.title,
685
+ content: params.content,
686
+ tags: mergedTags,
687
+ importance: params.importance ?? 5,
688
+ ...params.entryType ? { entryType: params.entryType } : {}
689
+ });
690
+ } catch (error) {
691
+ const message = error instanceof Error ? error.message : String(error);
692
+ if (taskCtx && /\b403\b|forbidden|not authorized|permission/i.test(message)) {
693
+ await config.onTaskProvenanceEvent?.("task.provenance.entry_denied", {
694
+ taskId: taskCtx.taskId,
695
+ diaryId: targetDiaryId,
696
+ error: message
697
+ });
698
+ throw new Error(`entries_create: the task provenance diary denied this entry. The diary may have moved to another team or its grants may have changed. The task and runtime session remain active; ask a diary manager to restore write access, then retry. (${message})`, { cause: error });
699
+ }
700
+ throw error;
701
+ }
688
702
  return {
689
703
  content: [{
690
704
  type: "text",
@@ -1925,6 +1939,11 @@ Type$1.Object({ id: UuidSchema });
1925
1939
  Type$1.Object({
1926
1940
  publicKey: PublicKeySchema,
1927
1941
  fingerprint: FingerprintSchema,
1942
+ proof: Type$1.String({
1943
+ minLength: 1,
1944
+ maxLength: 256
1945
+ }),
1946
+ credentialType: Type$1.Literal("oauth2"),
1928
1947
  agentName: Type$1.String({
1929
1948
  minLength: 1,
1930
1949
  maxLength: 34
@@ -2134,6 +2153,23 @@ var DiaryGrantResponseSchema = Type$1.Object({
2134
2153
  });
2135
2154
  Type$1.Object({ grants: Type$1.Array(DiaryGrantResponseSchema) });
2136
2155
  Type$1.Object({ revoked: Type$1.Boolean() });
2156
+ var TaskGrantRoleSchema = Type$1.Union([Type$1.Literal("writer"), Type$1.Literal("manager")]);
2157
+ Type$1.Object({
2158
+ subjectId: UuidSchema,
2159
+ subjectNs: GrantSubjectNsSchema,
2160
+ role: TaskGrantRoleSchema
2161
+ });
2162
+ Type$1.Object({
2163
+ subjectId: UuidSchema,
2164
+ subjectNs: GrantSubjectNsSchema,
2165
+ role: TaskGrantRoleSchema
2166
+ });
2167
+ var TaskGrantResponseSchema = Type$1.Object({
2168
+ subjectId: UuidSchema,
2169
+ subjectNs: GrantSubjectNsSchema,
2170
+ role: TaskGrantRoleSchema
2171
+ });
2172
+ Type$1.Object({ grants: Type$1.Array(TaskGrantResponseSchema) });
2137
2173
  Type$1.Object({ "x-moltnet-team-id": Type$1.String({
2138
2174
  format: "uuid",
2139
2175
  description: "Team ID (UUID) that will own the resource. Required."
@@ -2198,7 +2234,6 @@ var ProblemCodeSchema = Type$1.Union([
2198
2234
  Type$1.Literal("VALIDATION_FAILED"),
2199
2235
  Type$1.Literal("INVALID_CHALLENGE"),
2200
2236
  Type$1.Literal("INVALID_SIGNATURE"),
2201
- Type$1.Literal("VOUCHER_LIMIT"),
2202
2237
  Type$1.Literal("RATE_LIMIT_EXCEEDED"),
2203
2238
  Type$1.Literal("SERIALIZATION_EXHAUSTED"),
2204
2239
  Type$1.Literal("SIGNING_REQUEST_EXPIRED"),
@@ -5420,16 +5455,25 @@ function createGondolinBashOps(vm, localCwd, guestWorkspace) {
5420
5455
  * capability-aware allow-set) is tracked as future work.
5421
5456
  */
5422
5457
  function decideToolCall(input) {
5423
- if (input.enforcement === "off") return { allow: true };
5424
- if (input.toolName.startsWith("submit_") || input.toolName === "subagent") return { allow: true };
5458
+ if (input.enforcement === "off") return {
5459
+ allow: true,
5460
+ reasonCode: "policy_off"
5461
+ };
5462
+ if (input.toolName.startsWith("submit_") || input.toolName === "subagent") return {
5463
+ allow: true,
5464
+ reasonCode: "executor_protocol_tool"
5465
+ };
5425
5466
  const resolved = resolveNames(input);
5426
- if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell command could not be statically authorized", "unresolvable shell command (watch)");
5467
+ if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell_command_unresolvable", "shell command could not be statically authorized", "unresolvable shell command (watch)");
5427
5468
  const arbitraryCode = [...new Set(resolved.tools.filter((tool) => tool.risk === "arbitrary-code").map((tool) => tool.name))];
5428
- 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);
5469
+ 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);
5429
5470
  if (input.toolName !== "bash") {
5430
5471
  const missing = resolved.tools.map((tool) => tool.name).filter((name) => !input.allowedTools.has(name));
5431
- if (missing.length === 0) return { allow: true };
5432
- return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
5472
+ if (missing.length === 0) return {
5473
+ allow: true,
5474
+ reasonCode: "policy_allowed"
5475
+ };
5476
+ return fenced(input.enforcement, "tool_not_permitted", `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
5433
5477
  }
5434
5478
  const matchedShellCommands = [];
5435
5479
  const missingShellCommands = resolved.tools.filter((tool) => {
@@ -5439,7 +5483,7 @@ function decideToolCall(input) {
5439
5483
  matchedShellCommands.push(toMatchedShellCommand(tool.name, matched.argvPrefix));
5440
5484
  return false;
5441
5485
  }).map(toMissingShellCommand);
5442
- 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 }) => ({
5486
+ 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 }) => ({
5443
5487
  executable,
5444
5488
  argvFingerprint: argvPrefixFingerprint,
5445
5489
  argvLength: argvPrefixLength,
@@ -5447,10 +5491,14 @@ function decideToolCall(input) {
5447
5491
  })));
5448
5492
  if (missingShellCommands.length === 0) return matchedShellCommands.length > 0 ? {
5449
5493
  allow: true,
5494
+ reasonCode: "shell_command_prefix_allowed",
5450
5495
  matchedShellCommands
5451
- } : { allow: true };
5496
+ } : {
5497
+ allow: true,
5498
+ reasonCode: "policy_allowed"
5499
+ };
5452
5500
  const missing = [...new Set(missingShellCommands.map(({ executable }) => executable))];
5453
- return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
5501
+ return fenced(input.enforcement, "tool_not_permitted", `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
5454
5502
  }
5455
5503
  function fingerprintArgv(argv) {
5456
5504
  return `sha256:${createHash$1("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
@@ -5477,14 +5525,16 @@ function matchesArgvPrefix(argv, prefix) {
5477
5525
  * Shared enforce/watch branch: block in `enforce`, audit-and-allow in `watch`.
5478
5526
  * `enforcement` is never `off` here (short-circuited by the caller).
5479
5527
  */
5480
- function fenced(enforcement, blockReason, auditReason, missing, missingShellCommands) {
5528
+ function fenced(enforcement, reasonCode, blockReason, auditReason, missing, missingShellCommands) {
5481
5529
  if (enforcement === "enforce") return {
5482
5530
  allow: false,
5531
+ reasonCode,
5483
5532
  reason: blockReason,
5484
5533
  ...missing ? { missing } : {},
5485
5534
  ...missingShellCommands?.length ? { missingShellCommands } : {}
5486
5535
  };
5487
5536
  return {
5537
+ reasonCode,
5488
5538
  audit: auditReason,
5489
5539
  ...missing ? { missing } : {},
5490
5540
  ...missingShellCommands?.length ? { missingShellCommands } : {}
@@ -5527,8 +5577,9 @@ function resolveNames(input) {
5527
5577
  /**
5528
5578
  * Resolve the session's tool policy at start-up.
5529
5579
  *
5530
- * `off` short-circuits without a network call. Otherwise the allowed-tool set
5531
- * is fetched from the API. If that fetch fails, the mode decides the fallback:
5580
+ * The latest allowed-tool set and enforcement mode are fetched from the API,
5581
+ * including when the daemon's cached profile says `off`. If that fetch fails,
5582
+ * the cached mode decides the fallback:
5532
5583
  * `enforce` **fails closed** (empty allow-set → every non-`off` tool is
5533
5584
  * blocked); `watch` fails open (empty allow-set → every tool is audited but
5534
5585
  * allowed).
@@ -5539,12 +5590,6 @@ function resolveNames(input) {
5539
5590
  * session, stable enforcement for the run) accepted over re-fetching per call.
5540
5591
  */
5541
5592
  async function resolveSessionToolPolicy(input) {
5542
- if (input.enforcement === "off") return {
5543
- enforcement: "off",
5544
- allowedTools: /* @__PURE__ */ new Set(),
5545
- allowedShellCommands: [],
5546
- degraded: false
5547
- };
5548
5593
  const timeoutMs = input.timeoutMs ?? 5e3;
5549
5594
  try {
5550
5595
  const resolved = await withTimeout$1(input.agent.runtimeProfiles.allowedTools(input.profileId, { teamId: input.teamId }), timeoutMs);
@@ -5560,6 +5605,8 @@ async function resolveSessionToolPolicy(input) {
5560
5605
  if (rule.argvPrefix.length < 2 || rule.argvPrefix.length > 8 || rule.argvPrefix.some((token) => !token)) throw new Error("runtime returned an invalid shell command rule");
5561
5606
  return { argvPrefix: rule.argvPrefix };
5562
5607
  }),
5608
+ executionPolicySnapshotHash: resolved.policySnapshotHash,
5609
+ executionRuntimeProfileRevision: resolved.runtimeProfileRevision,
5563
5610
  degraded: false
5564
5611
  };
5565
5612
  } catch (err) {
@@ -5632,34 +5679,46 @@ function decideForEvent(event, policy, analyze) {
5632
5679
  * `off`. Register it in a session's `extensionFactories`.
5633
5680
  */
5634
5681
  function createToolPolicyExtension(deps) {
5682
+ if (deps.context?.claimPolicySnapshotHash && deps.policy.executionPolicySnapshotHash && deps.context.claimPolicySnapshotHash !== deps.policy.executionPolicySnapshotHash) deps.logger.info({
5683
+ ...decisionContext(deps),
5684
+ decision: "continue",
5685
+ reason: "claim_execution_policy_drift"
5686
+ }, "tool_policy.snapshot_drift");
5635
5687
  return (pi) => {
5636
5688
  if (deps.policy.enforcement === "off") return;
5637
5689
  pi.on("tool_call", (event) => {
5638
5690
  const decision = decideForEvent(event, deps.policy, (command) => deps.analyzer.analyze(command));
5639
5691
  if ("allow" in decision && decision.allow) {
5640
- if (decision.matchedShellCommands?.length) deps.logger.debug({
5692
+ deps.logger.info({
5693
+ ...decisionContext(deps),
5641
5694
  toolName: event.toolName,
5642
5695
  toolCallId: event.toolCallId,
5643
- matchedShellCommands: decision.matchedShellCommands
5644
- }, "tool_policy.shell_command_allowed");
5696
+ decision: "allowed",
5697
+ reason: decision.reasonCode,
5698
+ ...decision.matchedShellCommands?.length ? { shellFingerprints: decision.matchedShellCommands } : {}
5699
+ }, "tool_policy.allowed");
5645
5700
  return;
5646
5701
  }
5647
5702
  if ("audit" in decision) {
5648
5703
  deps.logger.info({
5704
+ ...decisionContext(deps),
5649
5705
  toolName: event.toolName,
5650
5706
  toolCallId: event.toolCallId,
5651
- degraded: deps.policy.degraded,
5652
- decision
5707
+ decision: "audit",
5708
+ reason: decision.reasonCode,
5709
+ ...decision.missing?.length ? { missingExecutables: decision.missing } : {},
5710
+ ...decision.missingShellCommands?.length ? { shellFingerprints: decision.missingShellCommands } : {}
5653
5711
  }, "tool_policy.audit");
5654
5712
  return;
5655
5713
  }
5656
5714
  deps.logger.warn({
5715
+ ...decisionContext(deps),
5657
5716
  toolName: event.toolName,
5658
5717
  toolCallId: event.toolCallId,
5659
- degraded: deps.policy.degraded,
5660
- reason: decision.reason,
5661
- missingExecutables: decision.missing,
5662
- missingShellCommands: decision.missingShellCommands
5718
+ decision: "blocked",
5719
+ reason: decision.reasonCode,
5720
+ ...decision.missing?.length ? { missingExecutables: decision.missing } : {},
5721
+ ...decision.missingShellCommands?.length ? { shellFingerprints: decision.missingShellCommands } : {}
5663
5722
  }, "tool_policy.blocked");
5664
5723
  return {
5665
5724
  block: true,
@@ -5668,6 +5727,15 @@ function createToolPolicyExtension(deps) {
5668
5727
  });
5669
5728
  };
5670
5729
  }
5730
+ function decisionContext(deps) {
5731
+ return {
5732
+ ...deps.context ?? {},
5733
+ enforcement: deps.policy.enforcement,
5734
+ degraded: deps.policy.degraded,
5735
+ executionPolicySnapshotHash: deps.policy.executionPolicySnapshotHash,
5736
+ executionRuntimeProfileRevision: deps.policy.executionRuntimeProfileRevision
5737
+ };
5738
+ }
5671
5739
  //#endregion
5672
5740
  //#region src/runtime/capability-discovery.ts
5673
5741
  var GuestExecutableProbeError = class extends Error {
@@ -7625,6 +7693,11 @@ async function executePiTask(claimedTask, reporter, opts) {
7625
7693
  attemptN,
7626
7694
  diaryId,
7627
7695
  correlationId: task.correlationId ?? null
7696
+ }),
7697
+ onTaskProvenanceEvent: (event, details) => emit("info", {
7698
+ event,
7699
+ severity: "warn",
7700
+ ...details
7628
7701
  })
7629
7702
  });
7630
7703
  const piAuthDir = resolvePiCodingAgentDir();
@@ -7647,28 +7720,34 @@ async function executePiTask(claimedTask, reporter, opts) {
7647
7720
  ...obj
7648
7721
  }))
7649
7722
  };
7650
- if (opts.runtimeProfileId && opts.toolEnforcement && opts.toolEnforcement !== "off") {
7651
- const [analyzer, policy] = await Promise.all([ShellCommandAnalyzer.create(), resolveSessionToolPolicy({
7723
+ const toolPolicyDecisionContext = buildToolPolicyDecisionContext(claimedTask, opts.runtimeProfileId);
7724
+ if (opts.runtimeProfileId && opts.toolEnforcement) {
7725
+ const policy = await resolveSessionToolPolicy({
7652
7726
  agent: moltnetAgent,
7653
7727
  profileId: opts.runtimeProfileId,
7654
7728
  teamId: taskTeamId,
7655
7729
  runtimeKind: opts.runtimeDefinition?.runtimeKind ?? "gondolin_pi",
7656
7730
  enforcement: opts.toolEnforcement,
7657
7731
  logger: toolPolicyLogger
7658
- })]);
7659
- verifiedGuestExecutables = (await discoverGuestExecutables(managed.vm, [...policy.allowedShellCommands.map(({ argvPrefix }) => argvPrefix[0]), ...policy.enforcement === "watch" ? resolvedVmTemplate?.executables ?? [] : []], { signal: reporter.cancelSignal })).available;
7660
- const availableExecutables = new Set(verifiedGuestExecutables);
7661
- const allowedShellCommands = policy.allowedShellCommands.filter(({ argvPrefix }) => availableExecutables.has(argvPrefix[0]));
7662
- unavailableRuntimeShellCommands = policy.allowedShellCommands.filter(({ argvPrefix }) => !availableExecutables.has(argvPrefix[0]));
7663
- resolvedToolPolicy = {
7664
- ...policy,
7665
- allowedShellCommands
7666
- };
7667
- toolPolicyExtensions.push(createToolPolicyExtension({
7668
- policy: resolvedToolPolicy,
7669
- analyzer,
7670
- logger: toolPolicyLogger
7671
- }));
7732
+ });
7733
+ resolvedToolPolicy = policy;
7734
+ if (policy.enforcement !== "off") {
7735
+ const analyzer = await ShellCommandAnalyzer.create();
7736
+ verifiedGuestExecutables = (await discoverGuestExecutables(managed.vm, [...policy.allowedShellCommands.map(({ argvPrefix }) => argvPrefix[0]), ...policy.enforcement === "watch" ? resolvedVmTemplate?.executables ?? [] : []], { signal: reporter.cancelSignal })).available;
7737
+ const availableExecutables = new Set(verifiedGuestExecutables);
7738
+ const allowedShellCommands = policy.allowedShellCommands.filter(({ argvPrefix }) => availableExecutables.has(argvPrefix[0]));
7739
+ unavailableRuntimeShellCommands = policy.allowedShellCommands.filter(({ argvPrefix }) => !availableExecutables.has(argvPrefix[0]));
7740
+ resolvedToolPolicy = {
7741
+ ...policy,
7742
+ allowedShellCommands
7743
+ };
7744
+ toolPolicyExtensions.push(createToolPolicyExtension({
7745
+ policy: resolvedToolPolicy,
7746
+ analyzer,
7747
+ logger: toolPolicyLogger,
7748
+ context: toolPolicyDecisionContext
7749
+ }));
7750
+ }
7672
7751
  }
7673
7752
  const runtimeToolContext = {
7674
7753
  agent: moltnetAgent,
@@ -8048,6 +8127,29 @@ async function executePiTask(claimedTask, reporter, opts) {
8048
8127
  });
8049
8128
  }
8050
8129
  }
8130
+ function buildToolPolicyDecisionContext(claimedTask, executionRuntimeProfileId) {
8131
+ const { task, attemptN, claimAuthority } = claimedTask;
8132
+ const proposer = task.proposedByAgentId ? {
8133
+ proposerKind: "agent",
8134
+ proposerId: task.proposedByAgentId
8135
+ } : task.proposedByHumanId ? {
8136
+ proposerKind: "human",
8137
+ proposerId: task.proposedByHumanId
8138
+ } : {};
8139
+ return {
8140
+ taskId: task.id,
8141
+ attemptN,
8142
+ teamId: task.teamId,
8143
+ ...proposer,
8144
+ ...claimAuthority?.claimantAgentId ? { claimantAgentId: claimAuthority.claimantAgentId } : {},
8145
+ ...claimAuthority?.leaseId ? { leaseId: claimAuthority.leaseId } : {},
8146
+ ...claimAuthority?.runtimeProfileId ? { claimRuntimeProfileId: claimAuthority.runtimeProfileId } : {},
8147
+ ...executionRuntimeProfileId ? { executionRuntimeProfileId } : {},
8148
+ ...typeof claimAuthority?.runtimeProfileRevision === "number" ? { claimRuntimeProfileRevision: claimAuthority.runtimeProfileRevision } : {},
8149
+ ...claimAuthority?.policySnapshotHash ? { claimPolicySnapshotHash: claimAuthority.policySnapshotHash } : {},
8150
+ ...claimAuthority?.executorFingerprint ? { claimedExecutorFingerprint: claimAuthority.executorFingerprint } : {}
8151
+ };
8152
+ }
8051
8153
  function createSessionTurnState() {
8052
8154
  return {
8053
8155
  assistantText: "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-runtime",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Composable MoltNet runtime kernel for Pi agents in Gondolin VMs",
@@ -31,9 +31,9 @@
31
31
  "@opentelemetry/api": "^1.9.0",
32
32
  "multiformats": "^13.3.0",
33
33
  "typebox": "^1.2.8",
34
- "@themoltnet/agent-runtime": "0.41.3",
34
+ "@themoltnet/agent-runtime": "0.43.0",
35
35
  "@themoltnet/os-keyring": "0.2.0",
36
- "@themoltnet/sdk": "0.132.0",
36
+ "@themoltnet/sdk": "0.134.0",
37
37
  "@themoltnet/shell-command-analyzer": "0.3.0"
38
38
  },
39
39
  "peerDependencies": {
@@ -52,8 +52,8 @@
52
52
  "vite-plugin-dts": "^4.5.4",
53
53
  "vitest": "^3.0.0",
54
54
  "@moltnet/crypto-service": "0.1.0",
55
- "@moltnet/tasks": "0.1.0",
56
- "@moltnet/models": "0.1.0"
55
+ "@moltnet/models": "0.1.0",
56
+ "@moltnet/tasks": "0.1.0"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=24"