@themoltnet/pi-runtime 0.2.0 → 0.4.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
@@ -43,6 +43,10 @@ export declare interface AllowedToolsClient {
43
43
  }) => Promise<{
44
44
  enforcement: ToolEnforcement;
45
45
  allowedTools: string[];
46
+ allowedShellCommands: Array<{
47
+ argvPrefix: string[];
48
+ }>;
49
+ runtimeKind: string;
46
50
  }>;
47
51
  };
48
52
  }
@@ -591,12 +595,16 @@ export declare function findMainWorktree(startPath?: string): string;
591
595
  */
592
596
  export declare type GateDecision = {
593
597
  allow: true;
598
+ matchedShellCommands?: MatchedShellCommand[];
594
599
  } | {
595
600
  allow: false;
596
601
  reason: string;
602
+ missing?: string[];
603
+ missingShellCommands?: MissingShellCommand[];
597
604
  } | {
598
605
  audit: string;
599
606
  missing?: string[];
607
+ missingShellCommands?: MissingShellCommand[];
600
608
  };
601
609
 
602
610
  export declare interface GateInput {
@@ -607,6 +615,8 @@ export declare interface GateInput {
607
615
  enforcement: ToolEnforcement;
608
616
  /** Names the policy allows (structured tool names + shell executable names). */
609
617
  allowedTools: ReadonlySet<string>;
618
+ /** Shell argv rules. Each rule matches the first N statically known tokens. */
619
+ allowedShellCommands: readonly ShellCommandRule[];
610
620
  /**
611
621
  * Synchronous shell analyzer (`ShellCommandAnalyzer.analyze`). Injected so the
612
622
  * decision stays pure and testable; the analyzer's async WASM init happens
@@ -694,6 +704,12 @@ export declare interface ManagedVm {
694
704
  agentDir: string;
695
705
  }
696
706
 
707
+ declare interface MatchedShellCommand {
708
+ executable: string;
709
+ argvPrefixFingerprint: string;
710
+ argvPrefixLength: number;
711
+ }
712
+
697
713
  export declare function materializePiExtensions(input: {
698
714
  runtime: PiRuntimeDefinition;
699
715
  context: PiToolContext;
@@ -714,6 +730,18 @@ export declare function materializePiTools(input: {
714
730
  };
715
731
  }): Promise<ToolDefinition[]>;
716
732
 
733
+ /**
734
+ * Literal-free metadata for a shell invocation that did not match policy.
735
+ * argv values are deliberately omitted; the fingerprint distinguishes
736
+ * invocations without placing literal arguments in the decision.
737
+ */
738
+ declare interface MissingShellCommand {
739
+ executable: string;
740
+ argvFingerprint: string;
741
+ argvLength: number;
742
+ dynamicTokenCount: number;
743
+ }
744
+
717
745
  export declare const MOLTNET_TOOL_NAMES: readonly ["moltnet_pack_get", "moltnet_pack_create", "moltnet_pack_provenance", "moltnet_pack_render", "moltnet_rendered_pack_list", "moltnet_rendered_pack_get", "moltnet_diary_tags", "moltnet_list_entries", "moltnet_get_entry", "moltnet_search_entries", "moltnet_create_entry", "moltnet_get_task", "moltnet_list_task_attempts", "moltnet_list_task_messages", "moltnet_upload_task_artifact", "moltnet_list_task_artifacts", "moltnet_download_task_artifact", "moltnet_review_session_errors", "moltnet_host_exec"];
718
746
 
719
747
  declare type MoltNetAgent = Awaited<ReturnType<typeof connect>>;
@@ -1045,6 +1073,8 @@ declare interface ResolveSessionToolPolicyInput {
1045
1073
  agent: AllowedToolsClient;
1046
1074
  profileId: string;
1047
1075
  teamId: string;
1076
+ /** Runtime kind supplied by the trusted daemon adapter for this session. */
1077
+ runtimeKind: string;
1048
1078
  /**
1049
1079
  * The profile's enforcement mode, already known to the daemon from the
1050
1080
  * resolved runtime profile. Used to decide fail-open vs fail-closed when the
@@ -1183,6 +1213,7 @@ export declare interface SandboxConfig {
1183
1213
  export declare interface SessionToolPolicy {
1184
1214
  enforcement: ToolEnforcement;
1185
1215
  allowedTools: ReadonlySet<string>;
1216
+ allowedShellCommands: readonly ShellCommandRule[];
1186
1217
  /**
1187
1218
  * `true` when the allow-set is a **degraded fallback** — the allowed-tools
1188
1219
  * fetch failed or timed out and this policy is the fail-closed/fail-open
@@ -1195,6 +1226,10 @@ export declare interface SessionToolPolicy {
1195
1226
  degraded: boolean;
1196
1227
  }
1197
1228
 
1229
+ declare interface ShellCommandRule {
1230
+ argvPrefix: readonly [string, string, ...string[]];
1231
+ }
1232
+
1198
1233
  /** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
1199
1234
  export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
1200
1235
 
package/dist/index.js CHANGED
@@ -10046,7 +10046,11 @@ var ProblemDetailsSchema = _Object_({
10046
10046
  }),
10047
10047
  code: ProblemCodeSchema,
10048
10048
  detail: Optional(String$1()),
10049
- instance: Optional(String$1())
10049
+ instance: Optional(String$1()),
10050
+ retryAfter: Optional(Integer({
10051
+ minimum: 0,
10052
+ description: "Non-negative delay in seconds before retrying, matching the Retry-After response header when present."
10053
+ }))
10050
10054
  }, {
10051
10055
  $id: "ProblemDetails",
10052
10056
  additionalProperties: true
@@ -14991,6 +14995,10 @@ var TaskAttempt = _Object_({
14991
14995
  taskId: Uuid,
14992
14996
  attemptN: Number$1({ minimum: 1 }),
14993
14997
  claimedByAgentId: Uuid,
14998
+ leaseId: Union([Uuid, Null()]),
14999
+ runtimeProfileId: Union([Uuid, Null()]),
15000
+ runtimeProfileRevision: Union([Integer({ minimum: 1 }), Null()]),
15001
+ policySnapshotHash: Union([String$1({ pattern: "^sha256:[0-9a-f]{64}$" }), Null()]),
14994
15002
  runtimeId: Union([Uuid, Null()]),
14995
15003
  claimedAt: IsoTimestamp,
14996
15004
  startedAt: Union([IsoTimestamp, Null()]),
@@ -30549,6 +30557,10 @@ var FIND_EXEC_FLAGS = new Set([
30549
30557
  "-okdir"
30550
30558
  ]);
30551
30559
  var ESCAPE_FLAG_SPECS = new Map([
30560
+ ["env", {
30561
+ separate: ["-S", "--split-string"],
30562
+ inline: [{ flag: "--split-string" }]
30563
+ }],
30552
30564
  ["tar", { inline: [{ flag: "--to-command" }, {
30553
30565
  flag: "--checkpoint-action",
30554
30566
  valuePrefix: "exec="
@@ -30611,6 +30623,7 @@ function resolveDefaultBashWasm() {
30611
30623
  var DEFAULT_MAX_COMMAND_LENGTH = 1e5;
30612
30624
  var ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
30613
30625
  var GLOB_RE = /[*?[\]]/;
30626
+ var WHITESPACE_RE = /\s/u;
30614
30627
  var SUBSTITUTION_TYPES = ["command_substitution", "process_substitution"];
30615
30628
  /** Guards against pathological nesting of escape-flag values. */
30616
30629
  var MAX_ESCAPE_DEPTH = 3;
@@ -30620,11 +30633,22 @@ function baseName(raw) {
30620
30633
  const slash = raw.lastIndexOf("/");
30621
30634
  return slash === -1 ? raw : raw.slice(slash + 1);
30622
30635
  }
30636
+ /** Build the public argv vector for one resolved invocation. */
30637
+ function invocationFor(words) {
30638
+ const head = words[0]?.name;
30639
+ if (head === null || head === void 0) return null;
30640
+ const name = baseName(head);
30641
+ return {
30642
+ name,
30643
+ argv: [name, ...words.slice(1).map((word) => word.name)]
30644
+ };
30645
+ }
30623
30646
  /** Classify one AST node into a {@link Word}. */
30624
30647
  function classifyToken(node) {
30625
30648
  const raw = node.text;
30626
30649
  switch (node.type) {
30627
- case "word": return {
30650
+ case "word":
30651
+ case "number": return {
30628
30652
  raw,
30629
30653
  name: raw.includes("\\") ? null : raw
30630
30654
  };
@@ -30736,7 +30760,12 @@ function advancePastWrapper(words, start, spec) {
30736
30760
  * span (up to the `;`/`+` terminator).
30737
30761
  */
30738
30762
  function resolveFind(words, wrapperDepth) {
30739
- const executables = ["find"];
30763
+ const find = invocationFor(words);
30764
+ if (!find) return {
30765
+ ok: false,
30766
+ reason: "non-literal find invocation"
30767
+ };
30768
+ const invocations = [find];
30740
30769
  const escapes = [];
30741
30770
  for (let i = 1; i < words.length; i++) {
30742
30771
  if (!FIND_EXEC_FLAGS.has(words[i].raw)) continue;
@@ -30753,13 +30782,13 @@ function resolveFind(words, wrapperDepth) {
30753
30782
  };
30754
30783
  const inner = resolveWords(cmd, wrapperDepth);
30755
30784
  if (!inner.ok) return inner;
30756
- executables.push(...inner.executables);
30785
+ invocations.push(...inner.invocations);
30757
30786
  escapes.push(...inner.escapes);
30758
30787
  i = k - 1;
30759
30788
  }
30760
30789
  return {
30761
30790
  ok: true,
30762
- executables,
30791
+ invocations,
30763
30792
  escapes
30764
30793
  };
30765
30794
  }
@@ -30772,7 +30801,7 @@ function resolveWords(words, wrapperDepth = 0) {
30772
30801
  const head = words[0];
30773
30802
  if (!head) return {
30774
30803
  ok: true,
30775
- executables: [],
30804
+ invocations: [],
30776
30805
  escapes: []
30777
30806
  };
30778
30807
  if (head.name === null) return {
@@ -30780,11 +30809,20 @@ function resolveWords(words, wrapperDepth = 0) {
30780
30809
  reason: `non-literal command name: ${head.raw}`
30781
30810
  };
30782
30811
  const name = head.name;
30812
+ if (WHITESPACE_RE.test(name)) return {
30813
+ ok: false,
30814
+ reason: "literal command name contains whitespace"
30815
+ };
30783
30816
  if (GLOB_RE.test(name)) return {
30784
30817
  ok: false,
30785
- reason: `command name contains a glob: ${name}`
30818
+ reason: "command name contains a glob"
30786
30819
  };
30787
30820
  const exe = baseName(name);
30821
+ const invocation = invocationFor(words);
30822
+ if (!invocation) return {
30823
+ ok: false,
30824
+ reason: `non-literal command name: ${head.raw}`
30825
+ };
30788
30826
  if (exe === "eval") return {
30789
30827
  ok: false,
30790
30828
  reason: "eval executes a dynamically built command"
@@ -30797,26 +30835,27 @@ function resolveWords(words, wrapperDepth = 0) {
30797
30835
  reason: "wrapper nesting too deep"
30798
30836
  };
30799
30837
  const advance = advancePastWrapper(words, 1, spec);
30838
+ const wrapperEscapes = escapeFlagCommands(exe, words);
30800
30839
  if (advance.kind === "deny") return {
30801
30840
  ok: false,
30802
30841
  reason: advance.reason
30803
30842
  };
30804
30843
  if (advance.kind === "none") return {
30805
30844
  ok: true,
30806
- executables: [exe],
30807
- escapes: []
30845
+ invocations: [invocation],
30846
+ escapes: wrapperEscapes
30808
30847
  };
30809
30848
  const inner = resolveWords(words.slice(advance.index), wrapperDepth + 1);
30810
30849
  if (!inner.ok) return inner;
30811
30850
  return {
30812
30851
  ok: true,
30813
- executables: [exe, ...inner.executables],
30814
- escapes: inner.escapes
30852
+ invocations: [invocation, ...inner.invocations],
30853
+ escapes: [...wrapperEscapes, ...inner.escapes]
30815
30854
  };
30816
30855
  }
30817
30856
  return {
30818
30857
  ok: true,
30819
- executables: [exe],
30858
+ invocations: [invocation],
30820
30859
  escapes: escapeFlagCommands(exe, words)
30821
30860
  };
30822
30861
  }
@@ -30965,6 +31004,10 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
30965
31004
  try {
30966
31005
  const root = tree.rootNode;
30967
31006
  const ast = root.toString();
31007
+ const hasOutputRedirection = root.descendantsOfType("file_redirect").some((redirect) => {
31008
+ const text = redirect?.text.trimStart() ?? "";
31009
+ return /^(?:\d+)?(?:>|>>|>\||&>|&>>|<>)/u.test(text);
31010
+ });
30968
31011
  if (root.hasError) return {
30969
31012
  ok: false,
30970
31013
  command,
@@ -30995,12 +31038,16 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
30995
31038
  ast
30996
31039
  };
30997
31040
  const raw = node.text;
30998
- for (const name of resolution.executables) tools.push({
30999
- name,
31000
- risk: classifyRisk(name),
31001
- capabilities: gtfobinsFunctions(name),
31002
- raw
31003
- });
31041
+ for (const invocation of resolution.invocations) {
31042
+ const { name } = invocation;
31043
+ tools.push({
31044
+ name,
31045
+ argv: invocation.argv,
31046
+ risk: classifyRisk(name),
31047
+ capabilities: gtfobinsFunctions(name),
31048
+ raw
31049
+ });
31050
+ }
31004
31051
  for (const sub of resolution.escapes) {
31005
31052
  if (depth >= MAX_ESCAPE_DEPTH) return {
31006
31053
  ok: false,
@@ -31012,7 +31059,7 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
31012
31059
  if (!nested.ok) return {
31013
31060
  ok: false,
31014
31061
  command,
31015
- reason: `in escape flag (${sub}): ${nested.reason}`,
31062
+ reason: `unresolvable command in escape flag: ${nested.reason}`,
31016
31063
  ast
31017
31064
  };
31018
31065
  tools.push(...nested.tools);
@@ -31028,7 +31075,8 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
31028
31075
  ok: true,
31029
31076
  command,
31030
31077
  tools,
31031
- ast
31078
+ ast,
31079
+ hasOutputRedirection
31032
31080
  };
31033
31081
  } finally {
31034
31082
  tree.delete();
@@ -32389,26 +32437,72 @@ function createGondolinBashOps(vm, localCwd, guestWorkspace) {
32389
32437
  function decideToolCall(input) {
32390
32438
  if (input.enforcement === "off") return { allow: true };
32391
32439
  const resolved = resolveNames(input);
32392
- if (resolved.kind === "unresolvable") return fenced(input.enforcement, `shell command could not be statically authorized: ${resolved.reason}`, `unresolvable shell command (watch): ${resolved.reason}`);
32440
+ if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell command could not be statically authorized", "unresolvable shell command (watch)");
32393
32441
  const arbitraryCode = [...new Set(resolved.tools.filter((tool) => tool.risk === "arbitrary-code").map((tool) => tool.name))];
32394
32442
  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);
32395
- const missing = [...new Set(resolved.tools.map((tool) => tool.name))].filter((name) => !input.allowedTools.has(name));
32396
- if (missing.length === 0) return { allow: true };
32397
- return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
32443
+ if (input.toolName !== "bash") {
32444
+ const missing = resolved.tools.map((tool) => tool.name).filter((name) => !input.allowedTools.has(name));
32445
+ if (missing.length === 0) return { allow: true };
32446
+ return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
32447
+ }
32448
+ const matchedShellCommands = [];
32449
+ const missingShellCommands = resolved.tools.filter((tool) => {
32450
+ if (input.allowedTools.has(tool.name)) return false;
32451
+ const matched = input.allowedShellCommands.find((rule) => matchesArgvPrefix(tool.argv, rule.argvPrefix));
32452
+ if (!matched) return true;
32453
+ matchedShellCommands.push(toMatchedShellCommand(tool.name, matched.argvPrefix));
32454
+ return false;
32455
+ }).map(toMissingShellCommand);
32456
+ 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 }) => ({
32457
+ executable,
32458
+ argvFingerprint: argvPrefixFingerprint,
32459
+ argvLength: argvPrefixLength,
32460
+ dynamicTokenCount: 0
32461
+ })));
32462
+ if (missingShellCommands.length === 0) return matchedShellCommands.length > 0 ? {
32463
+ allow: true,
32464
+ matchedShellCommands
32465
+ } : { allow: true };
32466
+ const missing = [...new Set(missingShellCommands.map(({ executable }) => executable))];
32467
+ return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
32468
+ }
32469
+ function fingerprintArgv(argv) {
32470
+ return `sha256:${createHash$1("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
32471
+ }
32472
+ function toMatchedShellCommand(executable, argvPrefix) {
32473
+ return {
32474
+ executable,
32475
+ argvPrefixFingerprint: fingerprintArgv(argvPrefix),
32476
+ argvPrefixLength: argvPrefix.length
32477
+ };
32478
+ }
32479
+ function toMissingShellCommand(tool) {
32480
+ return {
32481
+ executable: tool.name,
32482
+ argvFingerprint: fingerprintArgv(tool.argv),
32483
+ argvLength: tool.argv.length,
32484
+ dynamicTokenCount: tool.argv.filter((token) => token === null).length
32485
+ };
32486
+ }
32487
+ function matchesArgvPrefix(argv, prefix) {
32488
+ return argv.length >= prefix.length && prefix.every((token, index) => argv[index] !== null && argv[index] === token);
32398
32489
  }
32399
32490
  /**
32400
32491
  * Shared enforce/watch branch: block in `enforce`, audit-and-allow in `watch`.
32401
32492
  * `enforcement` is never `off` here (short-circuited by the caller).
32402
32493
  */
32403
- function fenced(enforcement, blockReason, auditReason, missing) {
32494
+ function fenced(enforcement, blockReason, auditReason, missing, missingShellCommands) {
32404
32495
  if (enforcement === "enforce") return {
32405
32496
  allow: false,
32406
- reason: blockReason
32497
+ reason: blockReason,
32498
+ ...missing ? { missing } : {},
32499
+ ...missingShellCommands?.length ? { missingShellCommands } : {}
32407
32500
  };
32408
- return missing ? {
32501
+ return {
32409
32502
  audit: auditReason,
32410
- missing
32411
- } : { audit: auditReason };
32503
+ ...missing ? { missing } : {},
32504
+ ...missingShellCommands?.length ? { missingShellCommands } : {}
32505
+ };
32412
32506
  }
32413
32507
  /**
32414
32508
  * The authorization targets for a tool call. For structured tools it is the tool
@@ -32421,6 +32515,7 @@ function resolveNames(input) {
32421
32515
  kind: "names",
32422
32516
  tools: [{
32423
32517
  name: input.toolName,
32518
+ argv: [input.toolName],
32424
32519
  risk: "unknown"
32425
32520
  }]
32426
32521
  };
@@ -32434,8 +32529,10 @@ function resolveNames(input) {
32434
32529
  kind: "names",
32435
32530
  tools: analysis.tools.map((tool) => ({
32436
32531
  name: tool.name,
32532
+ argv: tool.argv,
32437
32533
  risk: tool.risk
32438
- }))
32534
+ })),
32535
+ hasOutputRedirection: analysis.hasOutputRedirection === true
32439
32536
  } : {
32440
32537
  kind: "unresolvable",
32441
32538
  reason: analysis.reason
@@ -32463,14 +32560,24 @@ async function resolveSessionToolPolicy(input) {
32463
32560
  if (input.enforcement === "off") return {
32464
32561
  enforcement: "off",
32465
32562
  allowedTools: /* @__PURE__ */ new Set(),
32563
+ allowedShellCommands: [],
32466
32564
  degraded: false
32467
32565
  };
32468
32566
  const timeoutMs = input.timeoutMs ?? 5e3;
32469
32567
  try {
32470
32568
  const resolved = await withTimeout$1(input.agent.runtimeProfiles.allowedTools(input.profileId, { teamId: input.teamId }), timeoutMs);
32569
+ const shellCommands = resolved.allowedShellCommands ?? [];
32570
+ if (resolved.runtimeKind !== input.runtimeKind) throw new RuntimeKindMismatchError({
32571
+ expectedRuntimeKind: input.runtimeKind,
32572
+ receivedRuntimeKind: resolved.runtimeKind
32573
+ });
32471
32574
  return {
32472
32575
  enforcement: resolved.enforcement,
32473
32576
  allowedTools: new Set(resolved.allowedTools),
32577
+ allowedShellCommands: shellCommands.map((rule) => {
32578
+ if (rule.argvPrefix.length < 2 || rule.argvPrefix.length > 8 || rule.argvPrefix.some((token) => !token)) throw new Error("runtime returned an invalid shell command rule");
32579
+ return { argvPrefix: rule.argvPrefix };
32580
+ }),
32474
32581
  degraded: false
32475
32582
  };
32476
32583
  } catch (err) {
@@ -32485,6 +32592,7 @@ async function resolveSessionToolPolicy(input) {
32485
32592
  return {
32486
32593
  enforcement: input.enforcement,
32487
32594
  allowedTools: /* @__PURE__ */ new Set(),
32595
+ allowedShellCommands: [],
32488
32596
  degraded: true
32489
32597
  };
32490
32598
  }
@@ -32496,6 +32604,12 @@ var ToolPolicyResolveTimeoutError = class extends Error {
32496
32604
  this.name = "ToolPolicyResolveTimeoutError";
32497
32605
  }
32498
32606
  };
32607
+ var RuntimeKindMismatchError = class extends Error {
32608
+ constructor(input) {
32609
+ super(`runtime kind mismatch: expected ${input.expectedRuntimeKind}, received ${input.receivedRuntimeKind}`);
32610
+ this.name = "RuntimeKindMismatchError";
32611
+ }
32612
+ };
32499
32613
  /**
32500
32614
  * Reject with {@link ToolPolicyResolveTimeoutError} if `promise` does not settle
32501
32615
  * within `timeoutMs`. A non-positive `timeoutMs` disables the deadline. The
@@ -32526,6 +32640,7 @@ function decideForEvent(event, policy, analyze) {
32526
32640
  command,
32527
32641
  enforcement: policy.enforcement,
32528
32642
  allowedTools: policy.allowedTools,
32643
+ allowedShellCommands: policy.allowedShellCommands,
32529
32644
  analyze
32530
32645
  });
32531
32646
  }
@@ -32539,7 +32654,14 @@ function createToolPolicyExtension(deps) {
32539
32654
  if (deps.policy.enforcement === "off") return;
32540
32655
  pi.on("tool_call", (event) => {
32541
32656
  const decision = decideForEvent(event, deps.policy, (command) => deps.analyzer.analyze(command));
32542
- if ("allow" in decision && decision.allow) return;
32657
+ if ("allow" in decision && decision.allow) {
32658
+ if (decision.matchedShellCommands?.length) deps.logger.debug({
32659
+ toolName: event.toolName,
32660
+ toolCallId: event.toolCallId,
32661
+ matchedShellCommands: decision.matchedShellCommands
32662
+ }, "tool_policy.shell_command_allowed");
32663
+ return;
32664
+ }
32543
32665
  if ("audit" in decision) {
32544
32666
  deps.logger.info({
32545
32667
  toolName: event.toolName,
@@ -32553,7 +32675,9 @@ function createToolPolicyExtension(deps) {
32553
32675
  toolName: event.toolName,
32554
32676
  toolCallId: event.toolCallId,
32555
32677
  degraded: deps.policy.degraded,
32556
- reason: decision.reason
32678
+ reason: decision.reason,
32679
+ missingExecutables: decision.missing,
32680
+ missingShellCommands: decision.missingShellCommands
32557
32681
  }, "tool_policy.blocked");
32558
32682
  return {
32559
32683
  block: true,
@@ -34196,6 +34320,7 @@ async function executePiTask(claimedTask, reporter, opts) {
34196
34320
  agent: moltnetAgent,
34197
34321
  profileId: opts.runtimeProfileId,
34198
34322
  teamId: taskTeamId,
34323
+ runtimeKind: opts.runtimeDefinition?.runtimeKind ?? "gondolin_pi",
34199
34324
  enforcement: opts.toolEnforcement,
34200
34325
  logger: toolPolicyLogger
34201
34326
  })]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Composable MoltNet runtime kernel for Pi agents in Gondolin VMs",
@@ -26,9 +26,9 @@
26
26
  "@earendil-works/gondolin": "^0.9.1",
27
27
  "@opentelemetry/api": "^1.9.0",
28
28
  "typebox": "^1.2.8",
29
- "@themoltnet/agent-runtime": "0.38.0",
30
- "@themoltnet/sdk": "0.128.0",
31
- "@themoltnet/shell-command-analyzer": "0.2.0"
29
+ "@themoltnet/agent-runtime": "0.39.0",
30
+ "@themoltnet/shell-command-analyzer": "0.3.0",
31
+ "@themoltnet/sdk": "0.128.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@earendil-works/pi-ai": "0.74.0",
@@ -45,9 +45,9 @@
45
45
  "vite": "^8.0.0",
46
46
  "vite-plugin-dts": "^4.5.4",
47
47
  "vitest": "^3.0.0",
48
+ "@moltnet/tasks": "0.1.0",
48
49
  "@moltnet/crypto-service": "0.1.0",
49
- "@moltnet/models": "0.1.0",
50
- "@moltnet/tasks": "0.1.0"
50
+ "@moltnet/models": "0.1.0"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=22"