@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 +46 -11
- package/dist/index.js +153 -51
- package/package.json +5 -5
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
|
|
450
|
-
* `tool_call` gate
|
|
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
|
-
*
|
|
1110
|
-
*
|
|
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".
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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 {
|
|
5424
|
-
|
|
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 {
|
|
5432
|
-
|
|
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
|
-
} : {
|
|
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
|
-
*
|
|
5531
|
-
*
|
|
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
|
-
|
|
5692
|
+
deps.logger.info({
|
|
5693
|
+
...decisionContext(deps),
|
|
5641
5694
|
toolName: event.toolName,
|
|
5642
5695
|
toolCallId: event.toolCallId,
|
|
5643
|
-
|
|
5644
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5660
|
-
reason: decision.
|
|
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
|
-
|
|
7651
|
-
|
|
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
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
allowedShellCommands
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
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.
|
|
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.
|
|
34
|
+
"@themoltnet/agent-runtime": "0.43.0",
|
|
35
35
|
"@themoltnet/os-keyring": "0.2.0",
|
|
36
|
-
"@themoltnet/sdk": "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/
|
|
56
|
-
"@moltnet/
|
|
55
|
+
"@moltnet/models": "0.1.0",
|
|
56
|
+
"@moltnet/tasks": "0.1.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=24"
|