@deepstrike/wasm 0.1.6

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/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # DeepStrike WASM SDK
2
+
3
+ Agent framework built on a Rust kernel compiled to WebAssembly. Runs in browsers, Cloudflare Workers, Deno Deploy, and Vercel Edge — anywhere that supports `fetch` and WASM.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @deepstrike/wasm
9
+ ```
10
+
11
+ The Rust kernel is distributed as a pre-built `.wasm` binary (`@deepstrike/wasm-kernel`), which is an indirect dependency — you never import from it directly.
12
+
13
+ ---
14
+
15
+ ## Quick start
16
+
17
+ ```typescript
18
+ import { Agent, AnthropicProvider, tool } from "@deepstrike/wasm"
19
+
20
+ const add = tool("add", "Add two numbers.", {
21
+ type: "object",
22
+ properties: { x: { type: "number" }, y: { type: "number" } },
23
+ required: ["x", "y"],
24
+ }, async ({ x, y }) => String((x as number) + (y as number)))
25
+
26
+ const agent = new Agent(
27
+ new AnthropicProvider(apiKey),
28
+ { maxTokens: 32_000, maxTurns: 10 },
29
+ )
30
+ agent.register(add)
31
+
32
+ const answer = await agent.run("What is 2 + 3?")
33
+ console.log(answer) // "5"
34
+ ```
35
+
36
+ Streaming:
37
+
38
+ ```typescript
39
+ for await (const event of agent.runStreaming("Summarize this page")) {
40
+ if (event.type === "text_delta") process.stdout.write(event.delta)
41
+ else if (event.type === "tool_call") console.log(`\n[→ ${event.name}]`)
42
+ else if (event.type === "done") console.log(`\ndone in ${event.iterations} turns (${event.status})`)
43
+ }
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Architecture
49
+
50
+ ```
51
+ src/
52
+ ├── index.ts # Public exports
53
+ ├── agent.ts # Agent — top-level entry point
54
+ ├── types.ts # Shared type definitions
55
+ ├── providers/ # LLM adapters (fetch-based SSE)
56
+ ├── tools/ # tool() helper, executeTools (no fs/shell)
57
+ ├── memory/ # WorkingMemory + MemorySource/Extractor interfaces
58
+ ├── knowledge/ # KnowledgeSource interface
59
+ ├── harness/ # SinglePassHarness, EvalLoopHarness, QualityGate
60
+ ├── signals/ # RuntimeSignal, SignalSource, ScheduledPrompt
61
+ └── safety/ # PermissionManager
62
+ ```
63
+
64
+ The kernel (`@deepstrike/wasm-kernel`, Rust/wasm-bindgen) owns:
65
+ - `LoopStateMachine` — drives `call_llm → execute_tools → load_skills → done`
66
+ - `ContextEngine` — 5-partition context with pressure-based compression
67
+ - `Governance` — tool veto authority
68
+ - `SignalRouter` — external interrupt queue
69
+
70
+ ### WASM constraints vs Node SDK
71
+
72
+ | Capability | Browser | Cloudflare Worker | Node |
73
+ |---|---|---|---|
74
+ | `fs` read/write | no | no | yes |
75
+ | `bash` tool | no | no | yes |
76
+ | Long-term storage | IndexedDB | KV / D1 | SQLite |
77
+ | External signals | `postMessage` | event | any |
78
+
79
+ The WASM SDK ships **no `readFile` built-in**. Tools must be pure JS / serializable data. Skill loading is delegated to the host (fetch from a URL, read from IndexedDB, etc.).
80
+
81
+ ---
82
+
83
+ ## Providers
84
+
85
+ All providers use `fetch` — no Node.js `http` module.
86
+
87
+ | Class | Backend |
88
+ |-------|---------|
89
+ | `AnthropicProvider` | Anthropic API (SSE) |
90
+ | `OpenAIProvider` | OpenAI API (SSE) |
91
+ | `QwenProvider` | DashScope |
92
+ | `DeepSeekProvider` | DeepSeek API |
93
+ | `MiniMaxProvider` | MiniMax API |
94
+
95
+ ```typescript
96
+ import { AnthropicProvider } from "@deepstrike/wasm"
97
+
98
+ const provider = new AnthropicProvider("sk-...", "claude-opus-4-7")
99
+ ```
100
+
101
+ Thinking / reasoning:
102
+
103
+ ```typescript
104
+ for await (const event of agent.runStreaming("...", undefined, { enable_thinking: true })) {
105
+ if (event.type === "thinking_delta") console.log(event.delta)
106
+ }
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Tools
112
+
113
+ Tools must be pure functions — no shell, no filesystem.
114
+
115
+ ```typescript
116
+ import { tool, Agent } from "@deepstrike/wasm"
117
+
118
+ const fetchUrl = tool("fetch_url", "Fetch a URL and return its text.", {
119
+ type: "object",
120
+ properties: { url: { type: "string" } },
121
+ required: ["url"],
122
+ }, async ({ url }) => {
123
+ const resp = await fetch(url as string)
124
+ return resp.text()
125
+ })
126
+
127
+ agent.register(fetchUrl)
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Governance
133
+
134
+ ```typescript
135
+ import { Agent, AnthropicProvider, Governance } from "@deepstrike/wasm"
136
+
137
+ const gov = new Governance()
138
+ gov.blockTool("dangerous_tool")
139
+
140
+ const agent = new Agent(provider, {
141
+ maxTokens: 32_000,
142
+ governance: gov,
143
+ })
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Memory
149
+
150
+ `WorkingMemory` is an in-process scratch pad for within-run state:
151
+
152
+ ```typescript
153
+ import { WorkingMemory } from "@deepstrike/wasm"
154
+
155
+ const mem = new WorkingMemory()
156
+ mem.set("step", 1)
157
+ mem.get("step") // 1
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Knowledge
163
+
164
+ ```typescript
165
+ import type { KnowledgeSource } from "@deepstrike/wasm"
166
+
167
+ class VectorSearch implements KnowledgeSource {
168
+ async retrieve(goal: string, topK = 5): Promise<string[]> {
169
+ const resp = await fetch(`/api/search?q=${encodeURIComponent(goal)}&k=${topK}`)
170
+ return resp.json()
171
+ }
172
+ }
173
+
174
+ const agent = new Agent(provider, {
175
+ maxTokens: 32_000,
176
+ maxTurns: 10,
177
+ knowledgeSource: new VectorSearch(),
178
+ })
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Harness
184
+
185
+ ```typescript
186
+ import { SinglePassHarness, HarnessLoop } from "@deepstrike/wasm"
187
+
188
+ // Single pass — always passes
189
+ const harness = new SinglePassHarness(agent)
190
+ const outcome = await harness.run({ goal: "Write a haiku" })
191
+ console.log(outcome.result)
192
+
193
+ // Eval loop — LLM-judges the output; retries up to 3 times
194
+ const loop = new HarnessLoop(agent, evalProvider, { maxAttempts: 3 })
195
+ for await (const event of loop.runStreaming({
196
+ goal: "Write a haiku",
197
+ criteria: [
198
+ { text: "Exactly 3 lines", required: true },
199
+ { text: "Contains a seasonal reference", required: false },
200
+ ],
201
+ })) {
202
+ if (event.type === "done") console.log(event.verdict.passed, event.verdict.overallScore)
203
+ }
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Signals & interrupts
209
+
210
+ ```typescript
211
+ import { ScheduledPrompt } from "@deepstrike/wasm"
212
+ import type { SignalSource, RuntimeSignal } from "@deepstrike/wasm"
213
+
214
+ // Interrupt from a UI button
215
+ document.getElementById("stop")!.onclick = () => agent.interrupt()
216
+
217
+ // Convert a scheduled prompt to a RuntimeSignal
218
+ const prompt = new ScheduledPrompt("Daily standup summary", 1_700_000_000_000)
219
+ const signal = prompt.toSignal()
220
+ // signal.source === "cron", signal.signalType === "job"
221
+
222
+ // Feed signals from postMessage (browser) or Cloudflare event
223
+ class PostMessageSource implements SignalSource {
224
+ private queue: RuntimeSignal[] = []
225
+ constructor() {
226
+ self.addEventListener("message", (e: MessageEvent) => {
227
+ if (e.data?.source) this.queue.push(e.data as RuntimeSignal)
228
+ })
229
+ }
230
+ async nextSignal(): Promise<RuntimeSignal | null> {
231
+ return this.queue.shift() ?? null
232
+ }
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Permissions
239
+
240
+ ```typescript
241
+ import { PermissionManager, PermissionMode } from "@deepstrike/wasm"
242
+
243
+ const pm = new PermissionManager(PermissionMode.DEFAULT)
244
+ pm.grant("fetch", "execute")
245
+ pm.grant("storage", "*")
246
+ pm.revoke("fetch", "execute")
247
+
248
+ const decision = pm.evaluate("fetch", "execute")
249
+ decision.allowed // boolean
250
+ decision.reason // string
251
+ ```
252
+
253
+ Modes: `DEFAULT` (evaluate grants), `PLAN` (block all), `AUTO` (allow all).
254
+
255
+ ---
256
+
257
+ ## Edge runtime examples
258
+
259
+ ### Cloudflare Worker
260
+
261
+ ```typescript
262
+ import init from "@deepstrike/wasm-kernel"
263
+ import { Agent, AnthropicProvider } from "@deepstrike/wasm"
264
+ import wasmBinary from "@deepstrike/wasm-kernel/deepstrike_wasm_bg.wasm"
265
+
266
+ export default {
267
+ async fetch(request: Request, env: Env): Promise<Response> {
268
+ await init(wasmBinary)
269
+ const agent = new Agent(new AnthropicProvider(env.ANTHROPIC_KEY), { maxTokens: 32_000, maxTurns: 10 })
270
+ const result = await agent.run(await request.text())
271
+ return new Response(result)
272
+ },
273
+ }
274
+ ```
275
+
276
+ ### Browser (Vite / bundler)
277
+
278
+ ```typescript
279
+ import init from "@deepstrike/wasm-kernel"
280
+ import { Agent, AnthropicProvider } from "@deepstrike/wasm"
281
+
282
+ await init()
283
+ const agent = new Agent(new AnthropicProvider(import.meta.env.VITE_ANTHROPIC_KEY), { maxTokens: 32_000, maxTurns: 10 })
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Stream events
289
+
290
+ | Event type | Fields |
291
+ |------------|--------|
292
+ | `text_delta` | `delta: string` |
293
+ | `thinking_delta` | `delta: string` |
294
+ | `tool_call` | `id, name, arguments` |
295
+ | `tool_result` | `callId, name, content, isError` |
296
+ | `done` | `iterations, totalTokens, status` |
297
+ | `error` | `message: string` |
298
+
299
+ `status` mirrors the kernel termination reason: `completed` / `max_turns` / `token_budget` / `timeout` / `user_abort` / `error`.
@@ -0,0 +1,57 @@
1
+ import type { LLMProvider, StreamEvent } from "./types.js";
2
+ import type { RegisteredTool } from "./tools/index.js";
3
+ import type { KnowledgeSource } from "./knowledge/index.js";
4
+ import type { SignalSource } from "./signals/index.js";
5
+ import type { DreamStore } from "./memory/index.js";
6
+ import { Governance } from "./governance.js";
7
+ export interface SkillMetadata {
8
+ name: string;
9
+ description: string;
10
+ whenToUse?: string;
11
+ allowedTools?: string[];
12
+ effort?: number;
13
+ estimatedTokens?: number;
14
+ }
15
+ export interface AgentOptions {
16
+ maxTokens: number;
17
+ maxTurns?: number;
18
+ timeoutMs?: number;
19
+ extensions?: Record<string, unknown>;
20
+ /**
21
+ * System-level instructions prepended to every context render.
22
+ * Passed to the kernel's `system` partition before the first LLM call.
23
+ */
24
+ systemPrompt?: string;
25
+ /**
26
+ * Long-term memory snippets pre-seeded into the context before the first LLM call.
27
+ * Each string is pushed to the kernel's `memory` partition.
28
+ */
29
+ initialMemory?: string[];
30
+ skillDir?: string;
31
+ knowledgeSource?: KnowledgeSource;
32
+ signalSource?: SignalSource;
33
+ dreamStore?: DreamStore;
34
+ agentId?: string;
35
+ governance?: Governance;
36
+ /** Host-provided skill content map (name → markdown body). WASM has no fs access. */
37
+ skillContentMap?: Map<string, string>;
38
+ }
39
+ export declare class Agent {
40
+ private readonly provider;
41
+ private readonly options;
42
+ private tools;
43
+ private blockedTools;
44
+ private extensions;
45
+ private interrupted;
46
+ private pendingInterrupt;
47
+ private _pendingSkills;
48
+ constructor(provider: LLMProvider, options: AgentOptions);
49
+ register(...tools: RegisteredTool[]): this;
50
+ unregister(name: string): this;
51
+ blockTool(name: string): this;
52
+ interrupt(): void;
53
+ run(goal: string, criteria?: string[], extensions?: Record<string, unknown>): Promise<string>;
54
+ runStreaming(goal: string, criteria?: string[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
55
+ /** Register available skills from host-provided metadata (WASM has no fs access). */
56
+ setAvailableSkills(skills: SkillMetadata[]): void;
57
+ }
package/dist/agent.js ADDED
@@ -0,0 +1,244 @@
1
+ import { executeTools } from "./tools/index.js";
2
+ async function loadKernel() {
3
+ return import("@deepstrike/wasm-kernel");
4
+ }
5
+ export class Agent {
6
+ provider;
7
+ options;
8
+ tools = new Map();
9
+ blockedTools = new Set();
10
+ extensions;
11
+ interrupted = false;
12
+ pendingInterrupt = false;
13
+ _pendingSkills = [];
14
+ constructor(provider, options) {
15
+ this.provider = provider;
16
+ this.options = options;
17
+ this.extensions = options.extensions ?? {};
18
+ }
19
+ register(...tools) {
20
+ for (const t of tools)
21
+ this.tools.set(t.schema.name, t);
22
+ return this;
23
+ }
24
+ unregister(name) { this.tools.delete(name); return this; }
25
+ blockTool(name) { this.blockedTools.add(name); return this; }
26
+ interrupt() { this.interrupted = true; }
27
+ async run(goal, criteria, extensions) {
28
+ let text = "";
29
+ for await (const evt of this.runStreaming(goal, criteria, extensions)) {
30
+ if (evt.type === "text_delta")
31
+ text += evt.delta;
32
+ }
33
+ return text;
34
+ }
35
+ async *runStreaming(goal, criteria, extensions) {
36
+ this.interrupted = false;
37
+ this.pendingInterrupt = false;
38
+ if (this.options.knowledgeSource) {
39
+ await this.options.knowledgeSource.init();
40
+ }
41
+ const kernel = await loadKernel();
42
+ if (this.options.governance)
43
+ this.options.governance._attach(kernel);
44
+ const ext = { ...this.extensions, ...(extensions ?? {}) };
45
+ const sm = new kernel.LoopStateMachine({
46
+ maxTokens: this.options.maxTokens,
47
+ maxTurns: this.options.maxTurns ?? 25,
48
+ timeoutMs: this.options.timeoutMs,
49
+ });
50
+ sm.setTools(Array.from(this.tools.values()).map(t => t.schema));
51
+ if (this.options.systemPrompt) {
52
+ const tokens = Math.max(1, Math.ceil(this.options.systemPrompt.length / 4));
53
+ sm.addSystemMessage(this.options.systemPrompt, tokens);
54
+ }
55
+ for (const mem of this.options.initialMemory ?? []) {
56
+ sm.addMemoryMessage(mem, Math.max(1, Math.ceil(mem.length / 4)));
57
+ }
58
+ if (this._pendingSkills.length > 0) {
59
+ sm.setAvailableSkills(this._pendingSkills);
60
+ }
61
+ if (this.options.dreamStore && this.options.agentId)
62
+ sm.setMemoryEnabled(true);
63
+ if (this.options.knowledgeSource)
64
+ sm.setKnowledgeEnabled(true);
65
+ const router = new kernel.SignalRouter(256);
66
+ let action = sm.start({ goal, criteria: criteria ?? [] });
67
+ let finalText = "";
68
+ const sessionStart = Date.now();
69
+ const sessionMsgs = [{ role: "user", content: goal }];
70
+ while (!sm.isTerminal()) {
71
+ if (this.interrupted) {
72
+ action = sm.feedTimeout();
73
+ break;
74
+ }
75
+ if (this.pendingInterrupt) {
76
+ this.pendingInterrupt = false;
77
+ action = sm.feedTimeout();
78
+ break;
79
+ }
80
+ if (this.options.signalSource) {
81
+ const sig = await this.options.signalSource.nextSignal();
82
+ if (sig) {
83
+ const kernelSig = {
84
+ id: crypto.randomUUID(),
85
+ source: sig.source,
86
+ signalType: sig.signalType,
87
+ urgency: sig.urgency,
88
+ summary: String(sig.payload?.goal ?? sig.signalType),
89
+ payload: JSON.stringify(sig.payload ?? {}),
90
+ dedupeKey: sig.dedupeKey,
91
+ timestampMs: Date.now(),
92
+ };
93
+ const disposition = router.ingest(kernelSig, action.kind === "execute_tools");
94
+ if (disposition === "interrupt_now") {
95
+ action = sm.feedTimeout();
96
+ break;
97
+ }
98
+ if (disposition === "interrupt")
99
+ this.pendingInterrupt = true;
100
+ }
101
+ }
102
+ sm.takeObservations();
103
+ if (action.kind === "call_llm") {
104
+ finalText = "";
105
+ const finalToolCalls = [];
106
+ const messages = (action.messages ?? []);
107
+ const tools = (action.tools ?? []);
108
+ try {
109
+ for await (const evt of this.provider.stream(messages, tools, Object.keys(ext).length ? ext : undefined)) {
110
+ yield evt;
111
+ if (evt.type === "text_delta")
112
+ finalText += evt.delta;
113
+ else if (evt.type === "tool_call") {
114
+ const tc = evt;
115
+ finalToolCalls.push({ id: tc.id, name: tc.name, arguments: JSON.stringify(tc.arguments) });
116
+ }
117
+ }
118
+ }
119
+ catch (err) {
120
+ yield { type: "error", message: String(err) };
121
+ action = sm.feedTimeout();
122
+ break;
123
+ }
124
+ action = sm.feedLlmResponse({ role: "assistant", content: finalText, toolCalls: finalToolCalls });
125
+ sessionMsgs.push({ role: "assistant", content: finalText, toolCalls: finalToolCalls });
126
+ }
127
+ else if (action.kind === "execute_tools") {
128
+ const allCalls = (action.calls ?? []);
129
+ // Governance check
130
+ const permittedCalls = [];
131
+ for (const c of allCalls) {
132
+ if (this.blockedTools.has(c.name)) {
133
+ yield { type: "error", message: `tool blocked: ${c.name}` };
134
+ continue;
135
+ }
136
+ if (this.options.governance) {
137
+ this.options.governance.setTime(Date.now());
138
+ const verdict = this.options.governance.evaluate(c.name, c.arguments);
139
+ if (verdict.kind === "deny") {
140
+ yield { type: "error", message: `permission denied: ${c.name} — ${verdict.reason}` };
141
+ continue;
142
+ }
143
+ if (verdict.kind === "ask_user") {
144
+ yield { type: "permission_request", callId: c.id, toolName: c.name, arguments: c.arguments, reason: verdict.reason };
145
+ continue;
146
+ }
147
+ }
148
+ permittedCalls.push(c);
149
+ }
150
+ const skillCalls = permittedCalls.filter(c => c.name === "skill");
151
+ const memoryCalls = permittedCalls.filter(c => c.name === "memory");
152
+ const knowledgeCalls = permittedCalls.filter(c => c.name === "knowledge");
153
+ const regularCalls = permittedCalls.filter(c => !["skill", "memory", "knowledge"].includes(c.name));
154
+ // skill: WASM host must provide skill content via a registered tool or extension
155
+ const skillContentMap = this.options.skillContentMap ?? new Map();
156
+ const skillResults = skillCalls.map(c => {
157
+ const args = tryParseJson(c.arguments);
158
+ const name = String(args?.name ?? "");
159
+ const content = skillContentMap.get(name);
160
+ const output = content ?? `Skill "${name}" not found.`;
161
+ return { callId: c.id, output, isError: content === undefined };
162
+ });
163
+ const memoryResults = [];
164
+ if (this.options.dreamStore && this.options.agentId) {
165
+ for (const c of memoryCalls) {
166
+ const args = tryParseJson(c.arguments);
167
+ const query = String(args?.query ?? "");
168
+ const topK = typeof args?.top_k === "number" ? args.top_k : 5;
169
+ const entries = await this.options.dreamStore.search(this.options.agentId, query, topK);
170
+ const output = entries.length ? entries.map(e => `[score=${e.score.toFixed(3)}] ${e.text}`).join("\n---\n") : "No relevant memories found.";
171
+ yield { type: "tool_result", callId: c.id, name: c.name, content: output, isError: false };
172
+ memoryResults.push({ callId: c.id, output, isError: false });
173
+ }
174
+ }
175
+ else {
176
+ for (const c of memoryCalls)
177
+ memoryResults.push({ callId: c.id, output: "Memory retrieval not configured.", isError: true });
178
+ }
179
+ const knowledgeResults = [];
180
+ if (this.options.knowledgeSource) {
181
+ for (const c of knowledgeCalls) {
182
+ const args = tryParseJson(c.arguments);
183
+ const query = String(args?.query ?? "");
184
+ const topK = typeof args?.top_k === "number" ? args.top_k : 5;
185
+ const snippets = await this.options.knowledgeSource.retrieve(query, topK);
186
+ const output = snippets.length ? snippets.join("\n---\n") : "No relevant knowledge found.";
187
+ yield { type: "tool_result", callId: c.id, name: c.name, content: output, isError: false };
188
+ knowledgeResults.push({ callId: c.id, output, isError: false });
189
+ }
190
+ }
191
+ else {
192
+ for (const c of knowledgeCalls)
193
+ knowledgeResults.push({ callId: c.id, output: "Knowledge source not configured.", isError: true });
194
+ }
195
+ const results = await executeTools(regularCalls, this.tools);
196
+ for (const r of results) {
197
+ const name = regularCalls.find(c => c.id === r.callId)?.name ?? "";
198
+ yield { type: "tool_result", callId: r.callId, name, content: r.output, isError: r.isError };
199
+ }
200
+ action = sm.feedToolResults([
201
+ ...skillResults,
202
+ ...memoryResults,
203
+ ...knowledgeResults,
204
+ ...results.map(r => ({ callId: r.callId, output: r.output, isError: r.isError })),
205
+ ]);
206
+ }
207
+ else if (action.kind === "done") {
208
+ break;
209
+ }
210
+ }
211
+ const result = action.result;
212
+ if (this.options.dreamStore && this.options.agentId && sessionMsgs.length > 1) {
213
+ try {
214
+ await this.options.dreamStore.saveSession({
215
+ sessionId: crypto.randomUUID(),
216
+ agentId: this.options.agentId,
217
+ messages: sessionMsgs,
218
+ metadata: null,
219
+ createdAtMs: sessionStart,
220
+ updatedAtMs: Date.now(),
221
+ });
222
+ }
223
+ catch { /* session save failure must not surface to caller */ }
224
+ }
225
+ yield {
226
+ type: "done",
227
+ iterations: result?.turnsUsed ?? 0,
228
+ totalTokens: Number(result?.totalTokensUsed ?? 0),
229
+ status: result?.termination ?? "error",
230
+ };
231
+ }
232
+ /** Register available skills from host-provided metadata (WASM has no fs access). */
233
+ setAvailableSkills(skills) {
234
+ this._pendingSkills = skills;
235
+ }
236
+ }
237
+ function tryParseJson(s) {
238
+ try {
239
+ return JSON.parse(s);
240
+ }
241
+ catch {
242
+ return null;
243
+ }
244
+ }
@@ -0,0 +1,14 @@
1
+ export interface GovernanceVerdict {
2
+ kind: "allow" | "deny" | "rate_limited" | "ask_user";
3
+ reason?: string;
4
+ retryAfterMs?: number;
5
+ }
6
+ export declare class Governance {
7
+ private _inner;
8
+ private _pendingBlocks;
9
+ /** Called by Agent after the WASM kernel module is loaded. */
10
+ _attach(kernel: typeof import("@deepstrike/wasm-kernel")): void;
11
+ blockTool(name: string): this;
12
+ setTime(nowMs: number): this;
13
+ evaluate(toolName: string, argsJson: string): GovernanceVerdict;
14
+ }
@@ -0,0 +1,28 @@
1
+ export class Governance {
2
+ _inner = null;
3
+ _pendingBlocks = [];
4
+ /** Called by Agent after the WASM kernel module is loaded. */
5
+ _attach(kernel) {
6
+ if (this._inner)
7
+ return;
8
+ this._inner = new kernel.Governance();
9
+ for (const name of this._pendingBlocks)
10
+ this._inner.blockTool(name);
11
+ }
12
+ blockTool(name) {
13
+ if (this._inner)
14
+ this._inner.blockTool(name);
15
+ else
16
+ this._pendingBlocks.push(name);
17
+ return this;
18
+ }
19
+ setTime(nowMs) {
20
+ this._inner?.setTime(nowMs);
21
+ return this;
22
+ }
23
+ evaluate(toolName, argsJson) {
24
+ if (!this._inner)
25
+ return { kind: "allow" };
26
+ return this._inner.evaluate(toolName, argsJson);
27
+ }
28
+ }