@themoltnet/pi-extension 0.35.5 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +193 -2
- package/dist/index.js +6150 -326
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AgentSession } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import { Api } from '@earendil-works/pi-ai';
|
|
3
3
|
import { BashOperations } from '@earendil-works/pi-coding-agent';
|
|
4
|
+
import { CommandAnalysis } from '@themoltnet/shell-command-analyzer';
|
|
4
5
|
import { connect } from '@themoltnet/sdk';
|
|
5
6
|
import { EditOperations } from '@earendil-works/pi-coding-agent';
|
|
6
7
|
import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
@@ -9,9 +10,11 @@ import { LoadSkillsResult } from '@earendil-works/pi-coding-agent';
|
|
|
9
10
|
import { Model } from '@earendil-works/pi-ai';
|
|
10
11
|
import { Readable } from 'node:stream';
|
|
11
12
|
import { ReadOperations } from '@earendil-works/pi-coding-agent';
|
|
13
|
+
import { ShellCommandAnalyzer } from '@themoltnet/shell-command-analyzer';
|
|
12
14
|
import { Skill } from '@earendil-works/pi-coding-agent';
|
|
13
15
|
import { Static } from 'typebox';
|
|
14
16
|
import { TObject } from 'typebox';
|
|
17
|
+
import { ToolCallEvent } from '@earendil-works/pi-coding-agent';
|
|
15
18
|
import { ToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
16
19
|
import { TSchema } from 'typebox';
|
|
17
20
|
import { Type } from 'typebox';
|
|
@@ -24,6 +27,18 @@ import { WriteOperations } from '@earendil-works/pi-coding-agent';
|
|
|
24
27
|
*/
|
|
25
28
|
export declare function activateAgentEnv(agentEnv: Record<string, string | undefined>, repoRoot: string): void;
|
|
26
29
|
|
|
30
|
+
/** Minimal shape of the SDK method the resolver needs (keeps deps testable). */
|
|
31
|
+
export declare interface AllowedToolsClient {
|
|
32
|
+
runtimeProfiles: {
|
|
33
|
+
allowedTools: (profileId: string, options: {
|
|
34
|
+
teamId: string;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
enforcement: ToolEnforcement;
|
|
37
|
+
allowedTools: string[];
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
/**
|
|
28
43
|
* Construct an `AgentSession`. By default it is in-memory; callers may opt
|
|
29
44
|
* parent sessions into daemon-owned file persistence via `sessionPersistence`.
|
|
@@ -63,6 +78,12 @@ declare interface BuildAgentSessionArgs {
|
|
|
63
78
|
otelSpanAttrs: Record<string, string | number | boolean>;
|
|
64
79
|
/** Agent name for `gen_ai.agent.name` on the root span. */
|
|
65
80
|
agentName: string;
|
|
81
|
+
/**
|
|
82
|
+
* Extra pi extension factories appended after the always-on telemetry (and
|
|
83
|
+
* model-options) extensions — e.g. the tool-policy `tool_call` gate. Each is a
|
|
84
|
+
* plain `(pi) => void` registrar, same shape as the OTel extension.
|
|
85
|
+
*/
|
|
86
|
+
extraExtensionFactories?: ((pi: ExtensionAPI) => void)[];
|
|
66
87
|
/**
|
|
67
88
|
* Parent sessions may persist their conversation history in a daemon-owned
|
|
68
89
|
* directory. Subagents should leave this unset and stay in-memory.
|
|
@@ -260,8 +281,58 @@ export declare interface CreateSubagentToolArgs {
|
|
|
260
281
|
* with whatever stubs they need.
|
|
261
282
|
*/
|
|
262
283
|
contractRegistry: SubagentContractRegistry;
|
|
284
|
+
/**
|
|
285
|
+
* Extra pi extension factories every subagent session registers — chiefly the
|
|
286
|
+
* tool-policy `tool_call` gate. A subagent runs in its own `AgentSession`, so
|
|
287
|
+
* without re-registering the gate here it would execute tools un-checked,
|
|
288
|
+
* escaping the parent's enforcement (#1348 B2). Empty/undefined when tool
|
|
289
|
+
* enforcement is `off`, so subagents then match the parent's un-gated
|
|
290
|
+
* behaviour. The factories are read-only closures, safe to share across the
|
|
291
|
+
* parent and every subagent session.
|
|
292
|
+
*/
|
|
293
|
+
extraExtensionFactories?: ((pi: ExtensionAPI) => void)[];
|
|
263
294
|
}
|
|
264
295
|
|
|
296
|
+
/**
|
|
297
|
+
* A pi extension factory that gates every `tool_call` against the resolved
|
|
298
|
+
* policy. Blocks in `enforce`, audits (and allows) in `watch`, and is a no-op in
|
|
299
|
+
* `off`. Register it in a session's `extensionFactories`.
|
|
300
|
+
*/
|
|
301
|
+
export declare function createToolPolicyExtension(deps: ToolPolicyExtensionDeps): (pi: ExtensionAPI) => void;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Map a pi `tool_call` event to a gate decision, extracting the shell command
|
|
305
|
+
* for `bash` and delegating to {@link decideToolCall}.
|
|
306
|
+
*/
|
|
307
|
+
export declare function decideForEvent(event: ToolCallEvent, policy: SessionToolPolicy, analyze: ShellCommandAnalyzer['analyze']): GateDecision;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Decide whether a tool call is permitted by the resolved policy.
|
|
311
|
+
*
|
|
312
|
+
* Fail-closed in `enforce` (audited-but-allowed in `watch`, no-op in `off`) for:
|
|
313
|
+
*
|
|
314
|
+
* 1. **Unresolvable commands** — a `bash` command whose executables cannot be
|
|
315
|
+
* statically resolved (command substitution, `eval`, non-literal command
|
|
316
|
+
* names, unparseable input).
|
|
317
|
+
* 2. **Arbitrary-code interpreters** — a `bash` command that invokes a shell or
|
|
318
|
+
* language interpreter (`bash -c`, `python`, `node`, `perl`, …; the
|
|
319
|
+
* analyzer's `arbitrary-code` risk tier). Being name-listed is NOT enough:
|
|
320
|
+
* we cannot statically see the code such an interpreter runs, so the policy's
|
|
321
|
+
* allow-set can't bound it. This is the interim conservative stance for
|
|
322
|
+
* issue #1348 — an operator who lists `bash` still cannot smuggle
|
|
323
|
+
* `bash -c "curl … | sh"` past `enforce`.
|
|
324
|
+
* 3. **Unlisted executables** — any resolved executable not in `allowedTools`.
|
|
325
|
+
*
|
|
326
|
+
* KNOWN LIMITATION (follow-up): the `escapable` risk tier (GTFOBins binaries
|
|
327
|
+
* like `find`, `tar`, `awk` that document shell-spawn / file-write techniques)
|
|
328
|
+
* is NOT blocked on the tier alone. The analyzer already re-analyzes the
|
|
329
|
+
* sub-commands it can see through documented escape flags (`find -exec`,
|
|
330
|
+
* `tar --to-command`, …), but techniques it cannot parse statically could still
|
|
331
|
+
* escape a name-based allow-set. Tightening `escapable` (e.g. an LLM judge or a
|
|
332
|
+
* capability-aware allow-set) is tracked as future work.
|
|
333
|
+
*/
|
|
334
|
+
export declare function decideToolCall(input: GateInput): GateDecision;
|
|
335
|
+
|
|
265
336
|
/**
|
|
266
337
|
* Ensure a cached snapshot exists, building one if needed.
|
|
267
338
|
* Returns the absolute path to the qcow2 checkpoint file.
|
|
@@ -324,6 +395,14 @@ export declare interface ExecutePiTaskOptions {
|
|
|
324
395
|
* claim. Task entries override profile entries with the same slug.
|
|
325
396
|
*/
|
|
326
397
|
runtimeProfileContext?: readonly ContextRef[];
|
|
398
|
+
/**
|
|
399
|
+
* Runtime profile id, used to resolve the tool-policy allow-set at session
|
|
400
|
+
* start. Required together with a non-`off` `toolEnforcement` for the
|
|
401
|
+
* `tool_call` gate to run.
|
|
402
|
+
*/
|
|
403
|
+
runtimeProfileId?: string;
|
|
404
|
+
/** Tool-policy enforcement mode for the selected runtime profile. */
|
|
405
|
+
toolEnforcement?: ToolEnforcement;
|
|
327
406
|
/**
|
|
328
407
|
* Forwarded to `buildTaskUserPrompt` for per-type builders. Static
|
|
329
408
|
* across tasks. Today no built-in builder needs per-task `extras` —
|
|
@@ -421,7 +500,7 @@ export declare interface ExecutePiTaskOptions {
|
|
|
421
500
|
* distinct from daemon attempt retry: the active session keeps its context and
|
|
422
501
|
* receives a short continuation prompt.
|
|
423
502
|
*
|
|
424
|
-
* Default `
|
|
503
|
+
* Default `4`. Set to `0` to disable.
|
|
425
504
|
*/
|
|
426
505
|
maxProviderErrorRetries?: number;
|
|
427
506
|
/** Base delay for same-session provider-error retries. Default `2000`. */
|
|
@@ -455,13 +534,52 @@ export declare interface ExecutePiTaskOptions {
|
|
|
455
534
|
* contracts. See #1106.
|
|
456
535
|
*/
|
|
457
536
|
subagentContractRegistry?: SubagentContractRegistry;
|
|
537
|
+
/**
|
|
538
|
+
* Structured logger for tool-policy resolution/gate events. The daemon passes
|
|
539
|
+
* its task-bound pino child so these lines carry taskId/attemptN and join the
|
|
540
|
+
* run's NDJSON stream. When omitted, tool-policy events fall back to raw
|
|
541
|
+
* NDJSON on stderr (single-process / test callers). See #1348.
|
|
542
|
+
*/
|
|
543
|
+
toolPolicyLogger?: ToolPolicyLogger;
|
|
458
544
|
}
|
|
459
545
|
|
|
460
546
|
/**
|
|
461
547
|
* Resolve the main worktree root (where .moltnet/ lives — it's untracked,
|
|
462
548
|
* only exists in the main worktree, not in git worktrees).
|
|
463
549
|
*/
|
|
464
|
-
export declare function findMainWorktree(): string;
|
|
550
|
+
export declare function findMainWorktree(startPath?: string): string;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* The gate's verdict:
|
|
554
|
+
* - `{ allow: true }` — let the tool run.
|
|
555
|
+
* - `{ allow: false, reason }` — block it (enforce mode).
|
|
556
|
+
* - `{ audit, ... }` — would-block, but proceed and record it (watch mode).
|
|
557
|
+
*/
|
|
558
|
+
export declare type GateDecision = {
|
|
559
|
+
allow: true;
|
|
560
|
+
} | {
|
|
561
|
+
allow: false;
|
|
562
|
+
reason: string;
|
|
563
|
+
} | {
|
|
564
|
+
audit: string;
|
|
565
|
+
missing?: string[];
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
export declare interface GateInput {
|
|
569
|
+
/** Pi tool name (e.g. 'bash', 'read', 'write', or a custom tool id). */
|
|
570
|
+
toolName: string;
|
|
571
|
+
/** The shell command, when `toolName === 'bash'`. */
|
|
572
|
+
command?: string;
|
|
573
|
+
enforcement: ToolEnforcement;
|
|
574
|
+
/** Names the policy allows (structured tool names + shell executable names). */
|
|
575
|
+
allowedTools: ReadonlySet<string>;
|
|
576
|
+
/**
|
|
577
|
+
* Synchronous shell analyzer (`ShellCommandAnalyzer.analyze`). Injected so the
|
|
578
|
+
* decision stays pure and testable; the analyzer's async WASM init happens
|
|
579
|
+
* once at session start.
|
|
580
|
+
*/
|
|
581
|
+
analyze: (command: string) => CommandAnalysis;
|
|
582
|
+
}
|
|
465
583
|
|
|
466
584
|
/**
|
|
467
585
|
* Baseline env keys forwarded to host-exec child processes.
|
|
@@ -732,6 +850,42 @@ export declare interface ProviderErrorRetryUi {
|
|
|
732
850
|
|
|
733
851
|
export declare function redactRetryTriageSecrets(value: string): string;
|
|
734
852
|
|
|
853
|
+
/**
|
|
854
|
+
* Resolve the session's tool policy at start-up.
|
|
855
|
+
*
|
|
856
|
+
* `off` short-circuits without a network call. Otherwise the allowed-tool set
|
|
857
|
+
* is fetched from the API. If that fetch fails, the mode decides the fallback:
|
|
858
|
+
* `enforce` **fails closed** (empty allow-set → every non-`off` tool is
|
|
859
|
+
* blocked); `watch` fails open (empty allow-set → every tool is audited but
|
|
860
|
+
* allowed).
|
|
861
|
+
*
|
|
862
|
+
* The result is a **session-start snapshot**: it is resolved once and cached for
|
|
863
|
+
* the session's lifetime. Policy edits made while a task is running do not take
|
|
864
|
+
* effect until the next session — a deliberate trade-off (one resolution per
|
|
865
|
+
* session, stable enforcement for the run) accepted over re-fetching per call.
|
|
866
|
+
*/
|
|
867
|
+
export declare function resolveSessionToolPolicy(input: ResolveSessionToolPolicyInput): Promise<SessionToolPolicy>;
|
|
868
|
+
|
|
869
|
+
declare interface ResolveSessionToolPolicyInput {
|
|
870
|
+
agent: AllowedToolsClient;
|
|
871
|
+
profileId: string;
|
|
872
|
+
teamId: string;
|
|
873
|
+
/**
|
|
874
|
+
* The profile's enforcement mode, already known to the daemon from the
|
|
875
|
+
* resolved runtime profile. Used to decide fail-open vs fail-closed when the
|
|
876
|
+
* allowed-tools fetch fails.
|
|
877
|
+
*/
|
|
878
|
+
enforcement: ToolEnforcement;
|
|
879
|
+
logger: ToolPolicyLogger;
|
|
880
|
+
/**
|
|
881
|
+
* Deadline for the allowed-tools fetch. A hung API call must not stall session
|
|
882
|
+
* start-up indefinitely, so on timeout we abort and fall back to the
|
|
883
|
+
* mode-appropriate degraded policy. Defaults to
|
|
884
|
+
* {@link DEFAULT_RESOLVE_TIMEOUT_MS}; `0`/negative disables the deadline.
|
|
885
|
+
*/
|
|
886
|
+
timeoutMs?: number;
|
|
887
|
+
}
|
|
888
|
+
|
|
735
889
|
export declare function resolveTaskWorktreePath(mainRepo: string, workspaceId: string): string;
|
|
736
890
|
|
|
737
891
|
export declare interface ResumeCommand {
|
|
@@ -836,6 +990,22 @@ export declare interface SandboxConfig {
|
|
|
836
990
|
};
|
|
837
991
|
}
|
|
838
992
|
|
|
993
|
+
/** The resolved allow-set + enforcement mode for a runtime session. */
|
|
994
|
+
export declare interface SessionToolPolicy {
|
|
995
|
+
enforcement: ToolEnforcement;
|
|
996
|
+
allowedTools: ReadonlySet<string>;
|
|
997
|
+
/**
|
|
998
|
+
* `true` when the allow-set is a **degraded fallback** — the allowed-tools
|
|
999
|
+
* fetch failed or timed out and this policy is the fail-closed/fail-open
|
|
1000
|
+
* default, NOT the operator's actual configuration. An intentional
|
|
1001
|
+
* empty-but-resolved policy (e.g. a profile with no bound tools) has
|
|
1002
|
+
* `degraded: false`. Surfaced in every audit/block log so an operator can tell
|
|
1003
|
+
* "blocked because the policy is empty" from "blocked because we couldn't read
|
|
1004
|
+
* the policy". `off` and successful resolutions are never degraded.
|
|
1005
|
+
*/
|
|
1006
|
+
degraded: boolean;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
839
1009
|
/** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
|
|
840
1010
|
export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
|
|
841
1011
|
|
|
@@ -1086,6 +1256,27 @@ declare type TaskUsage = Static<typeof TaskUsage>;
|
|
|
1086
1256
|
*/
|
|
1087
1257
|
export declare function toGuestPath(localCwd: string, localPath: string, guestWorkspace: string): string;
|
|
1088
1258
|
|
|
1259
|
+
/** Enforcement mode resolved for the session's runtime profile. */
|
|
1260
|
+
export declare type ToolEnforcement = 'off' | 'watch' | 'enforce';
|
|
1261
|
+
|
|
1262
|
+
export declare interface ToolPolicyExtensionDeps {
|
|
1263
|
+
policy: SessionToolPolicy;
|
|
1264
|
+
analyzer: ShellCommandAnalyzer;
|
|
1265
|
+
logger: ToolPolicyLogger;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Structured logger the resolver and gate emit to. Deliberately the pino
|
|
1270
|
+
* `(obj, msg)` shape so the daemon can pass a task-bound pino child directly —
|
|
1271
|
+
* every tool-policy line then carries the daemon's taskId/attemptN context and
|
|
1272
|
+
* lands in the same NDJSON stream as the rest of the run.
|
|
1273
|
+
*/
|
|
1274
|
+
declare interface ToolPolicyLogger {
|
|
1275
|
+
debug: (obj: Record<string, unknown>, msg: string) => void;
|
|
1276
|
+
info: (obj: Record<string, unknown>, msg: string) => void;
|
|
1277
|
+
warn: (obj: Record<string, unknown>, msg: string) => void;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1089
1280
|
declare interface TrackedError {
|
|
1090
1281
|
toolName: string;
|
|
1091
1282
|
toolCallId: string;
|