@deepstrike/wasm 0.1.11 → 0.1.15

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,362 @@
1
+ import { getKernel } from "./kernel.js";
2
+ import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-replay.js";
3
+ export class RuntimeRunner {
4
+ opts;
5
+ interrupted = false;
6
+ constructor(opts) {
7
+ this.opts = opts;
8
+ }
9
+ interrupt() { this.interrupted = true; }
10
+ async *run(req) {
11
+ const prior = await this.opts.sessionLog.read(req.sessionId);
12
+ const midRun = isMidRun(prior);
13
+ if (!midRun) {
14
+ await this.opts.sessionLog.append(req.sessionId, {
15
+ kind: "run_started",
16
+ run_id: crypto.randomUUID(),
17
+ goal: req.goal,
18
+ criteria: req.criteria ?? [],
19
+ agent_id: this.opts.agentId,
20
+ system_prompt: this.opts.systemPrompt,
21
+ });
22
+ }
23
+ yield* this.execute(req.sessionId, req.goal, req.criteria ?? [], req.extensions, prior.length > 0 ? prior : undefined, midRun);
24
+ }
25
+ async *wake(sessionId, extensions) {
26
+ const events = await this.opts.sessionLog.read(sessionId);
27
+ if (events.some(e => e.event.kind === "run_terminal"))
28
+ return;
29
+ const startEntry = [...events].reverse().find(e => e.event.kind === "run_started");
30
+ if (!startEntry)
31
+ throw new Error(`No run_started event for session: ${sessionId}`);
32
+ const start = startEntry.event;
33
+ yield* this.execute(sessionId, start.goal, start.criteria, extensions, events, true);
34
+ }
35
+ async dream(agentId, nowMs = Date.now()) {
36
+ if (!this.opts.dreamStore)
37
+ throw new Error("dreamStore not configured");
38
+ const kernel = await getKernel();
39
+ this.opts.governance?._attach(kernel);
40
+ const sessions = await this.opts.dreamStore.loadSessions(agentId);
41
+ const existingMemories = await this.opts.dreamStore.loadMemories(agentId);
42
+ if (!sessions.length)
43
+ return { sessionsProcessed: 0, insightsExtracted: 0, entriesAdded: 0, entriesRemoved: 0 };
44
+ const pipeline = new kernel.IdlePipeline(agentId);
45
+ const action1 = pipeline.feedTrigger(sessions.map(s => ({
46
+ sessionId: s.sessionId, agentId: s.agentId,
47
+ messages: s.messages.map(m => ({
48
+ role: m.role, content: m.content, tokenCount: m.tokenCount,
49
+ toolCalls: (m.toolCalls ?? []).map(tc => ({ id: tc.id, name: tc.name, arguments: tc.arguments })),
50
+ })),
51
+ metadata: JSON.stringify(s.metadata ?? null),
52
+ createdAtMs: s.createdAtMs, updatedAtMs: s.updatedAtMs,
53
+ })), existingMemories.map(e => ({ text: e.text, score: e.score, metadata: JSON.stringify(e.metadata ?? null) })), nowMs);
54
+ if (action1.kind === "noop" || action1.kind === "aborted") {
55
+ return { sessionsProcessed: 0, insightsExtracted: 0, entriesAdded: 0, entriesRemoved: 0 };
56
+ }
57
+ if (action1.kind !== "synthesize_insights")
58
+ throw new Error(`unexpected: ${action1.kind}`);
59
+ let synthesisText = "";
60
+ const providerState = this.opts.provider.createRunState?.();
61
+ const synthMsgs = (action1.messages ?? []);
62
+ const synthContext = {
63
+ systemText: synthMsgs.filter(m => m.role === "system").map(m => m.content).join("\n\n"),
64
+ turns: synthMsgs.filter(m => m.role !== "system"),
65
+ };
66
+ for await (const evt of this.opts.provider.stream(synthContext, [], undefined, providerState)) {
67
+ if (evt.type === "text_delta")
68
+ synthesisText += evt.delta;
69
+ }
70
+ const action2 = pipeline.feedSynthesisResult(synthesisText);
71
+ if (action2.kind !== "commit_memories")
72
+ throw new Error(`unexpected: ${action2.kind}`);
73
+ const cr = action2.curationResult;
74
+ const rr = action2.runResult;
75
+ const dsResult = {
76
+ toAdd: (cr.toAdd ?? []).map((e) => ({
77
+ text: e.text, score: e.score, metadata: tryParseJson(e.metadata),
78
+ })),
79
+ toRemoveIndices: (cr.toRemoveIndices ?? []).map(Number),
80
+ stats: {
81
+ insightsProcessed: cr.stats?.insightsProcessed ?? 0,
82
+ duplicatesRemoved: cr.stats?.duplicatesRemoved ?? 0,
83
+ conflictsResolved: cr.stats?.conflictsResolved ?? 0,
84
+ entriesAdded: cr.stats?.entriesAdded ?? 0,
85
+ },
86
+ };
87
+ await this.opts.dreamStore.commit(agentId, dsResult, existingMemories);
88
+ return {
89
+ sessionsProcessed: rr.sessionsProcessed,
90
+ insightsExtracted: rr.insightsExtracted,
91
+ entriesAdded: cr.stats?.entriesAdded ?? 0,
92
+ entriesRemoved: (cr.toRemoveIndices ?? []).length,
93
+ };
94
+ }
95
+ async *execute(sessionId, goal, criteria, extensions, priorEvents, resumeMidRun = false) {
96
+ this.interrupted = false;
97
+ const kernel = await getKernel();
98
+ this.opts.governance?._attach(kernel);
99
+ const ext = { ...this.opts.extensions, ...(extensions ?? {}) };
100
+ const providerState = this.opts.provider.createRunState?.();
101
+ let nextCompressedArchiveStart = nextArchivedSeqStart(priorEvents);
102
+ const providerPolicy = this.opts.provider.runtimePolicy?.() ?? {};
103
+ const effectiveMaxTurns = this.opts.maxTurns ?? providerPolicy.maxTurns ?? 25;
104
+ const effectiveTimeoutMs = this.opts.timeoutMs ?? providerPolicy.timeoutMs;
105
+ const sm = new kernel.LoopStateMachine({
106
+ maxTokens: this.opts.maxTokens,
107
+ maxTurns: effectiveMaxTurns,
108
+ timeoutMs: effectiveTimeoutMs !== undefined ? BigInt(effectiveTimeoutMs) : undefined,
109
+ });
110
+ const router = new kernel.SignalRouter(256);
111
+ sm.setTools(this.opts.executionPlane.schemas());
112
+ if (this.opts.systemPrompt) {
113
+ sm.addSystemMessage(this.opts.systemPrompt, Math.max(1, Math.ceil(this.opts.systemPrompt.length / 4)));
114
+ }
115
+ if (this.opts.initialMemory) {
116
+ for (const mem of this.opts.initialMemory) {
117
+ sm.addMemoryMessage(mem, Math.max(1, Math.ceil(mem.length / 4)));
118
+ }
119
+ }
120
+ if (this.opts.skillContentMap && this.opts.skillContentMap.size > 0) {
121
+ const metas = [...this.opts.skillContentMap.keys()].map(name => ({
122
+ name,
123
+ description: "",
124
+ estimatedTokens: 0,
125
+ }));
126
+ sm.setAvailableSkills(metas);
127
+ }
128
+ if (this.opts.dreamStore && this.opts.agentId)
129
+ sm.setMemoryEnabled(true);
130
+ if (this.opts.knowledgeSource)
131
+ sm.setKnowledgeEnabled(true);
132
+ if (priorEvents && priorEvents.length > 0) {
133
+ seedProviderReplayFromEvents(this.opts.provider, priorEvents);
134
+ sm.preloadHistory(replayMessages(priorEvents));
135
+ }
136
+ const sessionStart = Date.now();
137
+ let action = resumeMidRun
138
+ ? sm.resumeAfterPreload()
139
+ : sm.start({ goal, criteria });
140
+ while (!sm.isTerminal()) {
141
+ nextCompressedArchiveStart = await this.appendObservations(sessionId, sm, nextCompressedArchiveStart);
142
+ if (this.interrupted) {
143
+ sm.feedTimeout();
144
+ break;
145
+ }
146
+ if (this.opts.signalSource) {
147
+ const sig = await this.opts.signalSource.nextSignal();
148
+ if (sig) {
149
+ const kernelSig = {
150
+ id: crypto.randomUUID(),
151
+ source: sig.source ?? "custom",
152
+ signalType: sig.signalType ?? "event",
153
+ urgency: sig.urgency ?? "normal",
154
+ summary: String(sig.payload?.goal ?? "signal"),
155
+ payload: JSON.stringify(sig.payload ?? {}),
156
+ dedupeKey: sig.dedupeKey,
157
+ timestampMs: Date.now(),
158
+ };
159
+ const disposition = router.ingest(kernelSig, action.kind === "execute_tools");
160
+ if (disposition === "interrupt_now") {
161
+ sm.feedTimeout();
162
+ break;
163
+ }
164
+ }
165
+ }
166
+ let queued = router.next();
167
+ while (queued) {
168
+ if (queued.urgency === "critical") {
169
+ sm.feedTimeout();
170
+ break;
171
+ }
172
+ queued = router.next();
173
+ }
174
+ if (sm.isTerminal())
175
+ break;
176
+ if (action.kind === "call_llm") {
177
+ const finalToolCalls = [];
178
+ let finalText = "";
179
+ const context = action.context;
180
+ const tools = (action.tools ?? []);
181
+ let turnTokens = 0;
182
+ try {
183
+ for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState)) {
184
+ if (evt.type === "usage") {
185
+ turnTokens = evt.totalTokens;
186
+ continue;
187
+ }
188
+ yield evt;
189
+ if (evt.type === "text_delta")
190
+ finalText += evt.delta;
191
+ else if (evt.type === "tool_call") {
192
+ const tc = evt;
193
+ finalToolCalls.push({ id: tc.id, name: tc.name, arguments: JSON.stringify(tc.arguments) });
194
+ }
195
+ }
196
+ }
197
+ catch (err) {
198
+ yield { type: "error", message: String(err) };
199
+ sm.feedTimeout();
200
+ break;
201
+ }
202
+ action = sm.feedLlmResponse({
203
+ role: "assistant",
204
+ content: finalText,
205
+ toolCalls: finalToolCalls,
206
+ tokenCount: turnTokens || undefined,
207
+ });
208
+ const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
209
+ await this.opts.sessionLog.append(sessionId, {
210
+ kind: "llm_completed",
211
+ turn: sm.turn,
212
+ content: finalText,
213
+ token_count: turnTokens || undefined,
214
+ tool_calls: finalToolCalls,
215
+ ...(providerReplay ? { provider_replay: providerReplay } : {}),
216
+ });
217
+ }
218
+ else if (action.kind === "execute_tools") {
219
+ const allCalls = (action.calls ?? []);
220
+ await this.opts.sessionLog.append(sessionId, { kind: "tool_requested", turn: sm.turn, calls: allCalls });
221
+ const runCtx = {
222
+ agentId: this.opts.agentId,
223
+ skillContentMap: this.opts.skillContentMap,
224
+ dreamStore: this.opts.dreamStore,
225
+ knowledgeSource: this.opts.knowledgeSource,
226
+ governance: this.opts.governance,
227
+ onToolSuspend: this.opts.onToolSuspend,
228
+ };
229
+ const toolResults = [];
230
+ for await (const evt of this.opts.executionPlane.executeAll(allCalls, runCtx)) {
231
+ yield evt;
232
+ if (evt.type === "tool_result") {
233
+ const tre = evt;
234
+ toolResults.push({ callId: tre.callId, output: tre.content, isError: tre.isError });
235
+ }
236
+ }
237
+ await this.opts.sessionLog.append(sessionId, {
238
+ kind: "tool_completed",
239
+ turn: sm.turn,
240
+ results: toolResults.map(r => ({
241
+ call_id: r.callId,
242
+ output: r.output,
243
+ is_error: r.isError,
244
+ token_count: r.tokenCount,
245
+ })),
246
+ });
247
+ action = sm.feedToolResults(toolResults);
248
+ }
249
+ else if (action.kind === "done") {
250
+ break;
251
+ }
252
+ }
253
+ const result = action.result;
254
+ const status = result?.termination ?? "error";
255
+ const turnsUsed = result ? Math.max(1, result.turnsUsed ?? 0) : 0;
256
+ const totalTokens = result?.totalTokensUsed ? Number(result.totalTokensUsed) : 0;
257
+ nextCompressedArchiveStart = await this.appendObservations(sessionId, sm, nextCompressedArchiveStart);
258
+ await this.opts.sessionLog.append(sessionId, { kind: "run_terminal", reason: status, turns_used: turnsUsed, total_tokens: totalTokens });
259
+ if (this.opts.dreamStore && this.opts.agentId) {
260
+ const newMsgs = sm.drainNewMessages().map(m => ({
261
+ role: m.role,
262
+ content: m.content,
263
+ tokenCount: m.tokenCount,
264
+ toolCalls: m.toolCalls?.length ? m.toolCalls : undefined,
265
+ }));
266
+ if (newMsgs.length > 0) {
267
+ try {
268
+ await this.opts.dreamStore.saveSession({
269
+ sessionId: crypto.randomUUID(),
270
+ agentId: this.opts.agentId,
271
+ messages: newMsgs,
272
+ metadata: null,
273
+ createdAtMs: sessionStart,
274
+ updatedAtMs: Date.now(),
275
+ });
276
+ }
277
+ catch { /* non-fatal */ }
278
+ }
279
+ }
280
+ yield { type: "done", iterations: turnsUsed, totalTokens, status };
281
+ }
282
+ async appendObservations(sessionId, sm, nextArchiveStart) {
283
+ const observations = sm.takeObservations();
284
+ for (const obs of observations) {
285
+ if (obs.kind !== "compressed")
286
+ continue;
287
+ const latest = await this.opts.sessionLog.latestSeq(sessionId);
288
+ if (latest < nextArchiveStart)
289
+ continue;
290
+ const end = latest;
291
+ const compressedSeq = await this.opts.sessionLog.append(sessionId, {
292
+ kind: "compressed",
293
+ turn: sm.turn,
294
+ archived_seq_range: [nextArchiveStart, end],
295
+ });
296
+ nextArchiveStart = compressedSeq + 1;
297
+ }
298
+ return nextArchiveStart;
299
+ }
300
+ }
301
+ function isMidRun(events) {
302
+ return events.length > 0 && !events.some(e => e.event.kind === "run_terminal");
303
+ }
304
+ function replayMessages(events) {
305
+ const messages = [];
306
+ for (const { event: e } of events) {
307
+ if (e.kind === "run_started") {
308
+ const userText = e.criteria.length
309
+ ? `${e.goal}\n\nCriteria:\n${e.criteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}`
310
+ : e.goal;
311
+ messages.push({
312
+ role: "user",
313
+ content: userText,
314
+ toolCalls: [],
315
+ tokenCount: Math.max(1, Math.ceil(userText.length / 4)),
316
+ });
317
+ }
318
+ else if (e.kind === "llm_completed") {
319
+ messages.push({
320
+ role: "assistant",
321
+ content: e.content,
322
+ toolCalls: e.tool_calls ?? [],
323
+ tokenCount: e.token_count,
324
+ });
325
+ }
326
+ else if (e.kind === "tool_completed") {
327
+ for (const r of e.results) {
328
+ messages.push({
329
+ role: "tool",
330
+ content: r.output,
331
+ toolCalls: [],
332
+ tokenCount: r.token_count,
333
+ });
334
+ }
335
+ }
336
+ }
337
+ return messages;
338
+ }
339
+ function nextArchivedSeqStart(events) {
340
+ let next = 0;
341
+ for (const { event } of events ?? []) {
342
+ if (event.kind === "compressed")
343
+ next = Math.max(next, event.archived_seq_range[1] + 1);
344
+ }
345
+ return next;
346
+ }
347
+ function tryParseJson(s) {
348
+ try {
349
+ return JSON.parse(s);
350
+ }
351
+ catch {
352
+ return null;
353
+ }
354
+ }
355
+ export async function collectText(stream) {
356
+ let text = "";
357
+ for await (const evt of stream) {
358
+ if (evt.type === "text_delta")
359
+ text += evt.delta;
360
+ }
361
+ return text;
362
+ }
@@ -0,0 +1,55 @@
1
+ import type { ProviderReplay, ToolCall } from "../types.js";
2
+ export type SessionEvent = {
3
+ kind: "run_started";
4
+ run_id: string;
5
+ goal: string;
6
+ criteria: string[];
7
+ agent_id?: string;
8
+ system_prompt?: string;
9
+ } | {
10
+ kind: "llm_completed";
11
+ turn: number;
12
+ content: string;
13
+ token_count?: number;
14
+ tool_calls: ToolCall[];
15
+ provider_replay?: ProviderReplay;
16
+ } | {
17
+ kind: "tool_requested";
18
+ turn: number;
19
+ calls: ToolCall[];
20
+ } | {
21
+ kind: "tool_completed";
22
+ turn: number;
23
+ results: Array<{
24
+ call_id: string;
25
+ output: string;
26
+ is_error?: boolean;
27
+ token_count?: number;
28
+ }>;
29
+ } | {
30
+ kind: "compressed";
31
+ turn: number;
32
+ archived_seq_range: [number, number];
33
+ } | {
34
+ kind: "run_terminal";
35
+ reason: string;
36
+ turns_used: number;
37
+ total_tokens: number;
38
+ };
39
+ export interface SessionLog {
40
+ append(sessionId: string, event: SessionEvent): Promise<number>;
41
+ read(sessionId: string, fromSeq?: number): Promise<Array<{
42
+ seq: number;
43
+ event: SessionEvent;
44
+ }>>;
45
+ latestSeq(sessionId: string): Promise<number>;
46
+ }
47
+ export declare class InMemorySessionLog implements SessionLog {
48
+ private store;
49
+ append(sessionId: string, event: SessionEvent): Promise<number>;
50
+ read(sessionId: string, fromSeq?: number): Promise<Array<{
51
+ seq: number;
52
+ event: SessionEvent;
53
+ }>>;
54
+ latestSeq(sessionId: string): Promise<number>;
55
+ }
@@ -0,0 +1,18 @@
1
+ export class InMemorySessionLog {
2
+ store = new Map();
3
+ async append(sessionId, event) {
4
+ if (!this.store.has(sessionId))
5
+ this.store.set(sessionId, []);
6
+ const entries = this.store.get(sessionId);
7
+ const seq = entries.length;
8
+ entries.push({ seq, event });
9
+ return seq;
10
+ }
11
+ async read(sessionId, fromSeq = 0) {
12
+ return (this.store.get(sessionId) ?? []).filter(e => e.seq >= fromSeq);
13
+ }
14
+ async latestSeq(sessionId) {
15
+ const entries = this.store.get(sessionId);
16
+ return entries ? entries.length - 1 : -1;
17
+ }
18
+ }
package/dist/types.d.ts CHANGED
@@ -20,6 +20,11 @@ export interface ToolSchema {
20
20
  description: string;
21
21
  parameters: string;
22
22
  }
23
+ /** Structured provider context from the kernel (`call_llm` action). */
24
+ export interface RenderedContext {
25
+ systemText: string;
26
+ turns: Message[];
27
+ }
23
28
  export interface StreamEvent {
24
29
  type: string;
25
30
  }
@@ -27,6 +32,10 @@ export interface TextDelta extends StreamEvent {
27
32
  type: "text_delta";
28
33
  delta: string;
29
34
  }
35
+ export interface UsageEvent extends StreamEvent {
36
+ type: "usage";
37
+ totalTokens: number;
38
+ }
30
39
  export interface ThinkingDelta extends StreamEvent {
31
40
  type: "thinking_delta";
32
41
  delta: string;
@@ -61,6 +70,23 @@ export interface PermissionRequestEvent extends StreamEvent {
61
70
  arguments: string;
62
71
  reason: string;
63
72
  }
73
+ /**
74
+ * Opaque per-run state owned by the provider (e.g. OpenAI Responses continuation).
75
+ * The framework creates and threads this object; providers may read/write it.
76
+ */
77
+ export type ProviderRunState = Record<string, unknown>;
78
+ export interface ProviderReplay {
79
+ native_blocks?: Array<Record<string, unknown>>;
80
+ reasoning_content?: string;
81
+ }
64
82
  export interface LLMProvider {
65
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
83
+ createRunState?(): ProviderRunState;
84
+ runtimePolicy?(): {
85
+ maxTurns?: number;
86
+ timeoutMs?: number;
87
+ };
88
+ peekProviderReplay?(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
89
+ seedProviderReplay?(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
90
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
91
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
66
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.1.11",
3
+ "version": "0.1.15",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "node --experimental-vm-modules node_modules/.bin/jest"
16
16
  },
17
17
  "dependencies": {
18
- "@deepstrike/wasm-kernel": "0.1.11"
18
+ "@deepstrike/wasm-kernel": "0.1.15"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/jest": "^30.0.0",
package/dist/agent.d.ts DELETED
@@ -1,57 +0,0 @@
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
- }