flavor-code 1.2.18 → 1.3.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.
@@ -0,0 +1,230 @@
1
+ // src/plugins/plugin-worker-entry.ts
2
+ import { readFile, realpath } from "fs/promises";
3
+ import { isAbsolute, relative, resolve, sep } from "path";
4
+ import { fileURLToPath, pathToFileURL } from "url";
5
+ import { createContext, runInContext, SourceTextModule } from "vm";
6
+ import { parentPort, workerData } from "worker_threads";
7
+ var MAX_MODULE_BYTES = 2 * 1024 * 1024;
8
+ var CONTRIBUTION_NAME_SOURCE = "^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$";
9
+ var data = workerData;
10
+ var port = parentPort;
11
+ if (port === null) throw new Error("Plugin sandbox requires a parent port");
12
+ var activationKeepAlive = setInterval(() => void 0, 1e3);
13
+ function send(message) {
14
+ port.postMessage(message);
15
+ }
16
+ function errorMessage(error) {
17
+ return error instanceof Error ? error.message : String(error);
18
+ }
19
+ function assertContained(root, candidate) {
20
+ const delta = relative(root, candidate);
21
+ if (delta === ".." || delta.startsWith(`..${sep}`) || isAbsolute(delta)) throw new Error("Plugin module import escapes the plugin root");
22
+ }
23
+ var SANDBOX_BOOTSTRAP = `(() => {
24
+ "use strict";
25
+ const stores = { command: new Map(), tool: new Map(), hook: new Map(), skillRoot: new Map(), modelAdapter: new Map() };
26
+ let deactivate;
27
+ let accepting = true;
28
+ const signal = Object.freeze({ aborted: false, reason: undefined, addEventListener() {}, removeEventListener() {}, throwIfAborted() {} });
29
+ const cloneJson = (value) => value === undefined ? undefined : JSON.parse(JSON.stringify(value));
30
+ const deepFreeze = (value) => {
31
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
32
+ for (const child of Object.values(value)) deepFreeze(child);
33
+ return Object.freeze(value);
34
+ };
35
+ const config = deepFreeze(JSON.parse(globalThis.__flavorConfigJson));
36
+ delete globalThis.__flavorConfigJson;
37
+ const namePattern = new RegExp(${JSON.stringify(CONTRIBUTION_NAME_SOURCE)});
38
+ const name = (value) => {
39
+ const result = String(value);
40
+ if (!namePattern.test(result)) throw new Error("Invalid contribution name: " + result);
41
+ return result;
42
+ };
43
+ const register = (kind, rawName, value, metadata) => {
44
+ if (!accepting) throw new Error("Plugin registrations are closed after activation");
45
+ const contributionName = name(rawName);
46
+ if (stores[kind].has(contributionName)) throw new Error("Duplicate " + kind + " contribution: " + contributionName);
47
+ stores[kind].set(contributionName, { value, metadata });
48
+ let active = true;
49
+ return Object.freeze(function disposeRegistration() {
50
+ if (!active) return;
51
+ active = false;
52
+ stores[kind].delete(contributionName);
53
+ });
54
+ };
55
+ const context = Object.freeze({
56
+ signal,
57
+ config,
58
+ logger: Object.freeze({ debug() {}, info() {}, warn() {}, error() {} }),
59
+ services: Object.freeze({ filesystem: Object.freeze({
60
+ async readFile() { throw new Error("Filesystem read is not available in sandbox"); },
61
+ async writeFile() { throw new Error("Filesystem write is not available in sandbox"); },
62
+ }) }),
63
+ registerCommand(rawName, handler, description) {
64
+ if (typeof handler !== "function") throw new Error("Sandbox command handler must be a function");
65
+ return register("command", rawName, handler, { description: typeof description === "string" ? description : undefined });
66
+ },
67
+ registerTool(rawName, tool) {
68
+ if (!tool || typeof tool !== "object" || typeof tool.execute !== "function") throw new Error("Sandbox tool must provide execute(input, signal, context)");
69
+ return register("tool", rawName, tool, {
70
+ toolName: typeof tool.name === "string" ? tool.name : String(rawName),
71
+ description: typeof tool.description === "string" ? tool.description : "Plugin tool " + String(rawName),
72
+ modelInputSchema: cloneJson(tool.modelInputSchema), readOnly: tool.readOnly === true,
73
+ });
74
+ },
75
+ registerHook(rawName, handler, options) {
76
+ if (typeof handler !== "function") throw new Error("Sandbox hook handler must be a function");
77
+ return register("hook", rawName, handler, { options: cloneJson(options) });
78
+ },
79
+ registerSkillRoot(rawName, root) {
80
+ if (typeof root !== "string") throw new Error("Sandbox skill root must be a string");
81
+ return register("skillRoot", rawName, root, { root });
82
+ },
83
+ registerModelAdapter(rawName, adapter) {
84
+ if (!adapter || typeof adapter.stream !== "function") throw new Error("Sandbox model adapter must provide stream(request)");
85
+ return register("modelAdapter", rawName, adapter, {});
86
+ },
87
+ });
88
+ const describe = () => ({
89
+ commands: [...stores.command].map(([entryName, entry]) => ({ name: entryName, ...entry.metadata })),
90
+ tools: [...stores.tool].map(([entryName, entry]) => ({ name: entryName, ...entry.metadata })),
91
+ hooks: [...stores.hook].map(([entryName, entry]) => ({ name: entryName, ...entry.metadata })),
92
+ skillRoots: [...stores.skillRoot].map(([entryName, entry]) => ({ name: entryName, ...entry.metadata })),
93
+ modelAdapters: [...stores.modelAdapter].map(([entryName]) => ({ name: entryName })),
94
+ });
95
+ const invoke = async (kind, entryName, args) => {
96
+ const entry = stores[kind]?.get(entryName);
97
+ if (!entry) throw new Error("Unknown sandbox contribution: " + kind + ":" + entryName);
98
+ if (kind === "command") return entry.value(args[0], Object.freeze({ ...(args[1] || {}), signal }));
99
+ if (kind === "tool") return entry.value.execute(args[0], signal, args[1]);
100
+ if (kind === "hook") return entry.value(args[0], signal);
101
+ if (kind === "modelAdapter") {
102
+ const events = [];
103
+ for await (const event of entry.value.stream(args[0])) events.push(event);
104
+ return events;
105
+ }
106
+ throw new Error("Unsupported sandbox invocation kind: " + kind);
107
+ };
108
+ globalThis.__flavorSandbox = Object.freeze({
109
+ async activate(fn) {
110
+ const result = await fn(context);
111
+ if (result !== undefined && typeof result !== "function") throw new Error("Plugin activate result must be a disposer or undefined");
112
+ deactivate = result;
113
+ accepting = false;
114
+ return JSON.stringify(describe());
115
+ },
116
+ async invokeJson(kind, entryName, argsJson) {
117
+ try { return JSON.stringify({ ok: true, value: await invoke(kind, entryName, JSON.parse(argsJson)) }); }
118
+ catch (error) { return JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }); }
119
+ },
120
+ async shutdown() { if (deactivate) await deactivate(); deactivate = undefined; },
121
+ });
122
+ })();`;
123
+ async function loadPlugin(pluginRoot, entryPath) {
124
+ const context = createContext(/* @__PURE__ */ Object.create(null), {
125
+ name: `flavor-plugin:${data.pluginName}`,
126
+ codeGeneration: { strings: false, wasm: false }
127
+ });
128
+ context.__flavorConfigJson = data.configJson;
129
+ runInContext(SANDBOX_BOOTSTRAP, context, { timeout: 1e3 });
130
+ const modules = /* @__PURE__ */ new Map();
131
+ const getModule = async (input) => {
132
+ const physical = await realpath(input);
133
+ assertContained(pluginRoot, physical);
134
+ const existing = modules.get(physical);
135
+ if (existing !== void 0) return existing;
136
+ const source = await readFile(physical, "utf8");
137
+ if (Buffer.byteLength(source) > MAX_MODULE_BYTES) throw new Error(`Plugin module exceeds ${MAX_MODULE_BYTES} bytes`);
138
+ const module = new SourceTextModule(source, {
139
+ context,
140
+ identifier: pathToFileURL(physical).href,
141
+ initializeImportMeta(meta) {
142
+ meta.url = pathToFileURL(physical).href;
143
+ },
144
+ importModuleDynamically: async (specifier, referencingModule) => {
145
+ const imported = await resolveModule(specifier, referencingModule);
146
+ if (imported.status === "unlinked") await imported.link(linker);
147
+ if (imported.status === "linked") await imported.evaluate();
148
+ return imported;
149
+ }
150
+ });
151
+ modules.set(physical, module);
152
+ return module;
153
+ };
154
+ const resolveModule = async (specifier, referencingModule) => {
155
+ if (!specifier.startsWith("./") && !specifier.startsWith("../")) throw new Error(`Sandbox blocks external module import: ${specifier}`);
156
+ return getModule(resolve(fileURLToPath(referencingModule.identifier), "..", specifier));
157
+ };
158
+ const linker = (specifier, referencingModule) => resolveModule(specifier, referencingModule);
159
+ const entry = await getModule(entryPath);
160
+ await entry.link(linker);
161
+ await entry.evaluate();
162
+ const activate = entry.namespace.activate;
163
+ if (typeof activate !== "function") throw new Error("Plugin entry must export activate(context)");
164
+ context.__flavorActivate = activate;
165
+ const contributionsJson = await runInContext(
166
+ "globalThis.__flavorSandbox.activate(globalThis.__flavorActivate)",
167
+ context
168
+ );
169
+ return { context, contributions: JSON.parse(contributionsJson) };
170
+ }
171
+ async function main() {
172
+ const pluginRoot = await realpath(data.pluginRoot);
173
+ assertContained(pluginRoot, await realpath(data.entryPath));
174
+ const loaded = await loadPlugin(pluginRoot, data.entryPath);
175
+ const { contributions } = loaded;
176
+ const result = {
177
+ ok: true,
178
+ name: data.pluginName,
179
+ version: data.pluginVersion,
180
+ registeredTools: contributions.tools.map(({ name }) => name),
181
+ registeredCommands: contributions.commands.map(({ name }) => name),
182
+ registeredHooks: contributions.hooks.map(({ name }) => name),
183
+ registeredSkillRoots: contributions.skillRoots.map(({ name }) => name),
184
+ registeredModelAdapters: contributions.modelAdapters.map(({ name }) => name),
185
+ contributions
186
+ };
187
+ send({ type: "ready", result });
188
+ port.on("message", (raw) => {
189
+ void handleMessage(raw, loaded.context);
190
+ });
191
+ clearInterval(activationKeepAlive);
192
+ }
193
+ async function handleMessage(raw, context) {
194
+ if (typeof raw !== "object" || raw === null || !("type" in raw)) {
195
+ send({ type: "fatal", error: "Invalid sandbox parent message" });
196
+ return;
197
+ }
198
+ const message = raw;
199
+ if (message.type === "shutdown") {
200
+ try {
201
+ await runInContext("globalThis.__flavorSandbox.shutdown()", context);
202
+ send({ type: "disposed" });
203
+ } catch (error) {
204
+ send({ type: "fatal", error: `Plugin sandbox shutdown failed: ${errorMessage(error)}` });
205
+ }
206
+ return;
207
+ }
208
+ if (message.type !== "invoke" || !Number.isSafeInteger(message.id) || typeof message.name !== "string" || typeof message.argsJson !== "string" || !["command", "tool", "hook", "modelAdapter"].includes(message.kind)) {
209
+ send({ type: "fatal", error: "Invalid sandbox invocation message" });
210
+ return;
211
+ }
212
+ context.__flavorKind = message.kind;
213
+ context.__flavorName = message.name;
214
+ context.__flavorArgs = message.argsJson;
215
+ try {
216
+ const responseJson = await runInContext(
217
+ "globalThis.__flavorSandbox.invokeJson(globalThis.__flavorKind, globalThis.__flavorName, globalThis.__flavorArgs)",
218
+ context
219
+ );
220
+ const response = JSON.parse(responseJson);
221
+ if (response.ok) send({ type: "response", id: message.id, ok: true, valueJson: response.value === void 0 ? "null" : JSON.stringify(response.value) });
222
+ else send({ type: "response", id: message.id, ok: false, error: response.error ?? "Sandbox invocation failed" });
223
+ } catch (error) {
224
+ send({ type: "response", id: message.id, ok: false, error: errorMessage(error) });
225
+ }
226
+ }
227
+ main().catch((error) => {
228
+ clearInterval(activationKeepAlive);
229
+ send({ type: "fatal", error: errorMessage(error) });
230
+ });
@@ -13,7 +13,7 @@ export interface FixPluginVerifyReport {
13
13
  commands: string[];
14
14
  error?: string;
15
15
  }
16
- /** Dry-run a plugin in a shadow PluginHost sandbox. The real host is untouched. */
16
+ /** Verify a plugin in an isolated Worker sandbox. The real host is untouched. */
17
17
  export declare function verifyFixPlugin(workspace: string, name: string): Promise<FixPluginVerifyReport>;
18
18
  /** Copy the current plugin dir into .versions/<name>/<timestamp>/. */
19
19
  export declare function snapshotFixPlugin(workspace: string, name: string): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import type { ModelRegistry } from "../models/registry.js";
2
- import { type AggregatedOutcome, type EvidencePacket, type Gap, type Plan } from "./types.js";
2
+ import { type AggregatedOutcome, type EvidencePacket, type Gap, type HostVerificationEvidence, type Plan } from "./types.js";
3
3
  export interface ClassifierOptions {
4
4
  registry: ModelRegistry;
5
5
  modelId: string;
@@ -9,4 +9,4 @@ export interface ClassifierOptions {
9
9
  }
10
10
  export declare function runClassifier(evidence: EvidencePacket, plan: Plan, options: ClassifierOptions): Promise<AggregatedOutcome>;
11
11
  export declare function computeFingerprint(gaps: Gap[]): string;
12
- export declare function collectEvidence(workspace: string, objective: string, finalResponse: string, priorGaps: string): Promise<EvidencePacket>;
12
+ export declare function collectEvidence(workspace: string, objective: string, finalResponse: string, priorGaps: string, contractHash?: string, hostVerification?: HostVerificationEvidence): Promise<EvidencePacket>;
@@ -1,6 +1,6 @@
1
1
  import type { AgentEvent } from "../agent/types.js";
2
2
  import type { ModelRegistry } from "../models/registry.js";
3
- import type { GoalRuntimeEvent, GoalState } from "./types.js";
3
+ import type { GoalRuntimeEvent, GoalState, HostVerificationEvidence } from "./types.js";
4
4
  export interface GoalOrchestratorOptions {
5
5
  workspace: string;
6
6
  registry: ModelRegistry;
@@ -22,6 +22,7 @@ export interface GoalOrchestratorOptions {
22
22
  persistence?: {
23
23
  save(state: GoalState): Promise<void>;
24
24
  };
25
+ verifyHost?(signal: AbortSignal): Promise<HostVerificationEvidence>;
25
26
  }
26
27
  export declare class GoalOrchestrator {
27
28
  #private;
@@ -88,6 +88,22 @@ export interface EvidencePacket {
88
88
  planFile: string | null;
89
89
  finalResponse: string;
90
90
  priorGaps: string;
91
+ contractHash: string;
92
+ workspaceDiffHash: string;
93
+ workspaceStatus: string[];
94
+ hostVerification?: HostVerificationEvidence;
95
+ }
96
+ export interface HostVerificationEvidence {
97
+ passed: boolean;
98
+ summary: string;
99
+ commands: Array<{
100
+ command: string;
101
+ args: string[];
102
+ exitCode: number | null;
103
+ stdout?: string;
104
+ stderr?: string;
105
+ truncated?: boolean;
106
+ }>;
91
107
  }
92
108
  export declare const GoalStateSchema: z.ZodObject<{
93
109
  id: z.ZodString;
@@ -141,6 +157,23 @@ export declare const GoalStateSchema: z.ZodObject<{
141
157
  }, z.core.$strict>>;
142
158
  gapFingerprint: z.ZodString;
143
159
  stallStreak: z.ZodNumber;
160
+ contractHash: z.ZodString;
161
+ evidenceRounds: z.ZodArray<z.ZodObject<{
162
+ round: z.ZodNumber;
163
+ workspaceDiffHash: z.ZodString;
164
+ hostVerification: z.ZodOptional<z.ZodObject<{
165
+ passed: z.ZodBoolean;
166
+ summary: z.ZodString;
167
+ commands: z.ZodArray<z.ZodObject<{
168
+ command: z.ZodString;
169
+ args: z.ZodArray<z.ZodString>;
170
+ exitCode: z.ZodNullable<z.ZodNumber>;
171
+ stdout: z.ZodOptional<z.ZodString>;
172
+ stderr: z.ZodOptional<z.ZodString>;
173
+ truncated: z.ZodOptional<z.ZodBoolean>;
174
+ }, z.core.$strict>>;
175
+ }, z.core.$strict>>;
176
+ }, z.core.$strict>>;
144
177
  createdAt: z.ZodString;
145
178
  updatedAt: z.ZodString;
146
179
  }, z.core.$strict>;
@@ -0,0 +1,4 @@
1
+ import type { HarnessJournalRecord } from "./journal.js";
2
+ /** Mechanical replay invariants owned by the durable harness package. */
3
+ export declare function harnessInvariantViolations(records: readonly HarnessJournalRecord[]): string[];
4
+ export declare function assertHarnessInvariants(records: readonly HarnessJournalRecord[]): void;
@@ -0,0 +1,109 @@
1
+ import { z } from "zod";
2
+ export declare const HARNESS_JOURNAL_VERSION: 1;
3
+ declare const EventTypeSchema: z.ZodEnum<{
4
+ "model.completed": "model.completed";
5
+ "model.requested": "model.requested";
6
+ "queue.acked": "queue.acked";
7
+ "queue.admitted": "queue.admitted";
8
+ "queue.claimed": "queue.claimed";
9
+ "queue.released": "queue.released";
10
+ "recovery.completed": "recovery.completed";
11
+ "savepoint.created": "savepoint.created";
12
+ "tool.completed": "tool.completed";
13
+ "tool.interrupted": "tool.interrupted";
14
+ "tool.started": "tool.started";
15
+ "turn.completed": "turn.completed";
16
+ "turn.interrupted": "turn.interrupted";
17
+ "turn.started": "turn.started";
18
+ }>;
19
+ declare const JournalRecordSchema: z.ZodObject<{
20
+ version: z.ZodLiteral<1>;
21
+ sequence: z.ZodNumber;
22
+ id: z.ZodString;
23
+ timestamp: z.ZodString;
24
+ type: z.ZodEnum<{
25
+ "model.completed": "model.completed";
26
+ "model.requested": "model.requested";
27
+ "queue.acked": "queue.acked";
28
+ "queue.admitted": "queue.admitted";
29
+ "queue.claimed": "queue.claimed";
30
+ "queue.released": "queue.released";
31
+ "recovery.completed": "recovery.completed";
32
+ "savepoint.created": "savepoint.created";
33
+ "tool.completed": "tool.completed";
34
+ "tool.interrupted": "tool.interrupted";
35
+ "tool.started": "tool.started";
36
+ "turn.completed": "turn.completed";
37
+ "turn.interrupted": "turn.interrupted";
38
+ "turn.started": "turn.started";
39
+ }>;
40
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
41
+ previousHash: z.ZodNullable<z.ZodString>;
42
+ hash: z.ZodString;
43
+ }, z.core.$strict>;
44
+ export type HarnessJournalEventType = z.infer<typeof EventTypeSchema>;
45
+ export type HarnessJournalRecord = z.infer<typeof JournalRecordSchema>;
46
+ export interface DurableQueueItem<T = unknown> {
47
+ id: string;
48
+ kind: "steer" | "followUp";
49
+ payload: T;
50
+ admittedAt: string;
51
+ recovered: boolean;
52
+ }
53
+ export interface IncompleteToolCall {
54
+ id: string;
55
+ tool: string;
56
+ retrySafe: boolean;
57
+ inputHash: string;
58
+ }
59
+ export interface HarnessRecovery<T = unknown> {
60
+ queue: DurableQueueItem<T>[];
61
+ incompleteTools: IncompleteToolCall[];
62
+ incompleteModelIds: string[];
63
+ interruptedTurnIds: string[];
64
+ lastSavepoint?: {
65
+ id: string;
66
+ phase: string;
67
+ configHash: string;
68
+ };
69
+ }
70
+ export interface HarnessJournalOptions {
71
+ workspace: string;
72
+ sessionId: string;
73
+ now?(): string;
74
+ maxBytes?: number;
75
+ }
76
+ /**
77
+ * Crash-consistent, hash-chained execution journal. Queue admission uses a
78
+ * synchronous fsync because returning from steer/followUp is the durability
79
+ * boundary: once the UI says "queued", the prompt must survive a hard crash.
80
+ */
81
+ export declare class HarnessJournal {
82
+ #private;
83
+ constructor(options: HarnessJournalOptions);
84
+ get path(): string;
85
+ get records(): readonly HarnessJournalRecord[];
86
+ admitQueue<T>(kind: "steer" | "followUp", payload: T): string;
87
+ claimQueue(id: string): void;
88
+ ackQueue(id: string): void;
89
+ releaseQueue(id: string, reason: string): void;
90
+ startTurn(config: unknown, prompt: unknown): string;
91
+ completeTurn(id: string): void;
92
+ interruptTurn(id: string, reason: string): void;
93
+ startModel(input: {
94
+ agent: string;
95
+ model: string;
96
+ iteration: number;
97
+ attempt: number;
98
+ messages: unknown;
99
+ }): string;
100
+ completeModel(id: string, completed: boolean, error?: string): void;
101
+ startTool(tool: string, input: unknown, retrySafe: boolean): string;
102
+ completeTool(id: string, result: unknown): void;
103
+ interruptTool(id: string, reason: string): void;
104
+ savepoint(phase: string, config: unknown): string;
105
+ recover<T = unknown>(): HarnessRecovery<T>;
106
+ markRecoveryComplete(recovery: HarnessRecovery): void;
107
+ }
108
+ export declare function hashJson(value: unknown): string;
109
+ export {};
@@ -1,12 +1,13 @@
1
- import { AgentLoop } from "../agent/loop.js";
1
+ import { AgentLoop, type AgentLoopOptions } from "../agent/loop.js";
2
2
  import type { TaskNode } from "../agent/planner.js";
3
3
  import type { HallucinationGuard } from "../hallucination/guard.js";
4
4
  import type { PermissionMode, PermissionProfile } from "../permissions/engine.js";
5
+ import type { CompiledPermissionPolicy } from "../permissions/policy.js";
5
6
  import type { ContextManager } from "../context/manager.js";
6
7
  import type { HookBus } from "../hooks/bus.js";
7
8
  import type { ModelRegistry } from "../models/registry.js";
8
9
  import type { ModelTool } from "../models/types.js";
9
- import { ToolRuntime, type ApprovalCallback } from "../tools/runtime.js";
10
+ import { ToolRuntime, type ApprovalCallback, type ToolRuntimeOptions } from "../tools/runtime.js";
10
11
  import type { ToolDefinition } from "../tools/types.js";
11
12
  export interface LocalHarnessOptions {
12
13
  registry: ModelRegistry;
@@ -25,6 +26,9 @@ export interface LocalHarnessOptions {
25
26
  /** When true, non-destructive tools skip the approval callback. Destructive tools still require confirmation. */
26
27
  loopMode?: boolean;
27
28
  afterToolSuccess?(tool: string, paths: readonly string[], input: unknown, output: unknown, context: import("../tools/types.js").ToolContext): Promise<readonly string[]>;
29
+ toolJournal?: ToolRuntimeOptions["journal"];
30
+ permissionPolicy?: CompiledPermissionPolicy;
31
+ modelJournal?: AgentLoopOptions["modelJournal"];
28
32
  }
29
33
  export interface HarnessProfile {
30
34
  modelId: string;
@@ -1,4 +1,5 @@
1
1
  import { type LegacyPermissionMode, type PermissionMode } from "../config/schema.js";
2
+ import type { CompiledPermissionPolicy } from "./policy.js";
2
3
  export type { PermissionMode } from "../config/schema.js";
3
4
  export type PermissionDecision = {
4
5
  decision: "allow" | "deny" | "ask";
@@ -22,6 +23,7 @@ export interface PermissionEngineOptions {
22
23
  workspace: string;
23
24
  mode?: PermissionMode | LegacyPermissionMode;
24
25
  profile?: PermissionProfile;
26
+ policy?: CompiledPermissionPolicy;
25
27
  }
26
28
  export type ToolCategory = "control" | "read" | "write" | "destructive" | "shell" | "network" | "unknown";
27
29
  export declare function getToolCategory(name: string): ToolCategory;
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import type { PermissionDecision, PermissionRequest } from "./engine.js";
3
+ declare const RuleSchema: z.ZodObject<{
4
+ id: z.ZodString;
5
+ decision: z.ZodEnum<{
6
+ allow: "allow";
7
+ ask: "ask";
8
+ deny: "deny";
9
+ }>;
10
+ prefix: z.ZodArray<z.ZodString>;
11
+ tool: z.ZodOptional<z.ZodString>;
12
+ agent: z.ZodOptional<z.ZodEnum<{
13
+ main: "main";
14
+ subagent: "subagent";
15
+ }>>;
16
+ justification: z.ZodOptional<z.ZodString>;
17
+ match: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString>>>;
18
+ notMatch: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString>>>;
19
+ }, z.core.$strict>;
20
+ export type PermissionRule = z.infer<typeof RuleSchema> & {
21
+ source: string;
22
+ tier: "managed" | "user" | "project" | "local" | "session";
23
+ };
24
+ export interface PermissionPolicyLoadOptions {
25
+ workspace: string;
26
+ home: string;
27
+ managedPath?: string;
28
+ sessionRules?: readonly z.input<typeof RuleSchema>[];
29
+ }
30
+ export declare class CompiledPermissionPolicy {
31
+ #private;
32
+ readonly diagnostics: readonly string[];
33
+ constructor(rules: readonly PermissionRule[], diagnostics?: readonly string[]);
34
+ get rules(): readonly PermissionRule[];
35
+ decide(request: PermissionRequest): PermissionDecision | undefined;
36
+ }
37
+ export declare function loadPermissionPolicy(options: PermissionPolicyLoadOptions): Promise<CompiledPermissionPolicy>;
38
+ export {};