@deepstrike/wasm 0.2.35 → 0.2.36
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/runtime/large-result-spool.d.ts +7 -0
- package/dist/runtime/large-result-spool.js +25 -0
- package/dist/runtime/runner.d.ts +91 -6
- package/dist/runtime/runner.js +269 -78
- package/dist/runtime/sub-agent-orchestrator.js +6 -1
- package/dist/runtime/types/agent.d.ts +10 -1
- package/package.json +2 -2
|
@@ -45,5 +45,12 @@ export declare class LargeResultSpool {
|
|
|
45
45
|
processToolResult(result: ToolResult): Promise<SpooledToolResult>;
|
|
46
46
|
persistOutput(callId: string, content: string): Promise<string>;
|
|
47
47
|
readSpooledResult(spoolRef: string): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* O7: locate a spooled output by the tool call's id (the `read_result` meta-tool only knows
|
|
50
|
+
* `call_id`, not the content-hashed key `persistOutput` chose). Scans the driver's key list for
|
|
51
|
+
* the `.spool/${callId}-*.txt` naming convention; returns `undefined` if nothing was ever
|
|
52
|
+
* spooled for that call.
|
|
53
|
+
*/
|
|
54
|
+
findByCallId(callId: string): Promise<string | undefined>;
|
|
48
55
|
cleanup(maxAgeMs?: number): Promise<number>;
|
|
49
56
|
}
|
|
@@ -113,6 +113,31 @@ omitted: ${omitted} chars
|
|
|
113
113
|
async readSpooledResult(spoolRef) {
|
|
114
114
|
return this.driver.read(spoolRef);
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* O7: locate a spooled output by the tool call's id (the `read_result` meta-tool only knows
|
|
118
|
+
* `call_id`, not the content-hashed key `persistOutput` chose). Scans the driver's key list for
|
|
119
|
+
* the `.spool/${callId}-*.txt` naming convention; returns `undefined` if nothing was ever
|
|
120
|
+
* spooled for that call.
|
|
121
|
+
*/
|
|
122
|
+
async findByCallId(callId) {
|
|
123
|
+
let keys;
|
|
124
|
+
try {
|
|
125
|
+
keys = await this.driver.list();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const prefix = `.spool/${callId}-`;
|
|
131
|
+
const match = keys.find(k => k.startsWith(prefix) && k.endsWith('.txt'));
|
|
132
|
+
if (!match)
|
|
133
|
+
return undefined;
|
|
134
|
+
try {
|
|
135
|
+
return await this.driver.read(match);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
116
141
|
async cleanup(maxAgeMs) {
|
|
117
142
|
const limit = maxAgeMs ?? this.config.maxAgeMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
118
143
|
try {
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { LLMProvider, Message, StreamEvent, PermissionRequestEvent, Permiss
|
|
|
2
2
|
import type { ToolSuspendEvent } from "./execution-plane.js";
|
|
3
3
|
import type { DreamStore, DreamResult } from "../memory/index.js";
|
|
4
4
|
import type { KnowledgeSource } from "../knowledge/index.js";
|
|
5
|
-
import type { SignalSource } from "../signals/index.js";
|
|
5
|
+
import type { SignalSource, RuntimeSignal } from "../signals/index.js";
|
|
6
6
|
import type { SessionLog, SessionEvent } from "./session-log.js";
|
|
7
7
|
import type { ExecutionPlane } from "./execution-plane.js";
|
|
8
8
|
import { type GovernancePolicy } from "../governance.js";
|
|
@@ -59,6 +59,16 @@ export interface TurnMetrics {
|
|
|
59
59
|
};
|
|
60
60
|
cacheCreationTokens: number;
|
|
61
61
|
}
|
|
62
|
+
/** O5: decision returned by `onToolCall` — `block: true` denies this call before it executes. */
|
|
63
|
+
export interface ToolCallHookDecision {
|
|
64
|
+
block?: boolean;
|
|
65
|
+
reason?: string;
|
|
66
|
+
}
|
|
67
|
+
/** O5: decision returned by `onToolResult` — replace the output and/or inject a signal note. */
|
|
68
|
+
export interface ToolResultHookDecision {
|
|
69
|
+
replaceOutput?: string;
|
|
70
|
+
note?: string;
|
|
71
|
+
}
|
|
62
72
|
export interface RuntimeOptions {
|
|
63
73
|
provider: LLMProvider;
|
|
64
74
|
/** M1/G3 intelligence routing: resolve a per-node provider from a workflow node's `modelHint`.
|
|
@@ -78,6 +88,8 @@ export interface RuntimeOptions {
|
|
|
78
88
|
* the knowledge partition before turn 1. Requires dreamStore + agentId. */
|
|
79
89
|
preQueryMemory?: (ctx: {
|
|
80
90
|
goal: string;
|
|
91
|
+
/** K4: `"initial"` = pre-turn-1 fetch; `"renewal"` = re-fired after a sprint renewal. */
|
|
92
|
+
phase?: "initial" | "renewal";
|
|
81
93
|
}) => Promise<string[] | undefined> | string[] | undefined;
|
|
82
94
|
systemPrompt?: string;
|
|
83
95
|
initialMemory?: string[];
|
|
@@ -95,6 +107,39 @@ export interface RuntimeOptions {
|
|
|
95
107
|
};
|
|
96
108
|
schedulerBudget?: SchedulerBudget;
|
|
97
109
|
resourceQuota?: ResourceQuota;
|
|
110
|
+
/** O6: the in-kernel repeat fuse — identical tool call (same name AND args) `denyAfter` turns in a
|
|
111
|
+
* row ⇒ deny + directive note; `terminateAfter` ⇒ run ends `no_progress`. Defaults 5/8; `false`
|
|
112
|
+
* disables. Same-tool/different-args loops never trip it. */
|
|
113
|
+
repeatFuse?: {
|
|
114
|
+
denyAfter?: number;
|
|
115
|
+
terminateAfter?: number;
|
|
116
|
+
} | false;
|
|
117
|
+
/** O4: turn-end criteria gate — one kernel-injected self-check turn before accepting completion
|
|
118
|
+
* while `criteria` stand. Default enabled; `false` accepts the first finish unconditionally. */
|
|
119
|
+
criteriaGate?: boolean;
|
|
120
|
+
/** K2: max share of `maxTokens` the durable knowledge partition may occupy. Over budget ⇒
|
|
121
|
+
* warn-once observation + oldest unpinned non-skill entries evicted at the next boundary.
|
|
122
|
+
* Pinned/skill entries are exempt. `0` disables. Default: kernel's 0.25. */
|
|
123
|
+
knowledgeBudgetRatio?: number;
|
|
124
|
+
/** K3: default lease (in turns) for every skill activation — auto-deactivates after N turns
|
|
125
|
+
* (toolset re-widens, knowledge pin boundary-swept). Absent ⇒ permanent (default). */
|
|
126
|
+
skillLeaseTurns?: number;
|
|
127
|
+
/** O5 (PreToolUse-hook analog): stateful host veto over each kernel-approved call; return
|
|
128
|
+
* `{ block: true, reason }` to deny — the reason reaches the model as a denied result. Errs-open. */
|
|
129
|
+
onToolCall?: (call: {
|
|
130
|
+
callId: string;
|
|
131
|
+
name: string;
|
|
132
|
+
arguments: string;
|
|
133
|
+
}) => Promise<ToolCallHookDecision | undefined | void> | ToolCallHookDecision | undefined | void;
|
|
134
|
+
/** O5 (PostToolUse-hook analog): inspect each executed result; `{ replaceOutput }` swaps what the
|
|
135
|
+
* model sees, `{ note }` injects a signal note (the `injectNote` channel). Errs-open. */
|
|
136
|
+
onToolResult?: (result: {
|
|
137
|
+
callId: string;
|
|
138
|
+
name: string;
|
|
139
|
+
arguments: string;
|
|
140
|
+
output: string;
|
|
141
|
+
isError: boolean;
|
|
142
|
+
}) => Promise<ToolResultHookDecision | undefined | void> | ToolResultHookDecision | undefined | void;
|
|
98
143
|
memoryPolicy?: MemoryPolicy;
|
|
99
144
|
tokenizer?: string;
|
|
100
145
|
enablePlanTool?: boolean;
|
|
@@ -140,8 +185,15 @@ export declare class RuntimeRunner {
|
|
|
140
185
|
private pendingObservations;
|
|
141
186
|
private activeKernel;
|
|
142
187
|
private currentSessionId;
|
|
188
|
+
/** O2 (system-reminder channel): host-pushed notes awaiting the next turn-boundary drain. */
|
|
189
|
+
private injectedSignals;
|
|
190
|
+
/** Skill names whose content has already been pushed into the durable `knowledge` slot this
|
|
191
|
+
* run — guards against re-pushing a duplicate entry if the model calls `skill(name)` again for
|
|
192
|
+
* an already-active skill (loading is idempotent; the knowledge push should be too). */
|
|
193
|
+
private knowledgePushedSkills;
|
|
194
|
+
/** K4: the active run's goal, kept for the renewal-boundary memory re-query. */
|
|
195
|
+
private currentGoal;
|
|
143
196
|
private nextArchiveStart;
|
|
144
|
-
private localPageOutCache;
|
|
145
197
|
/** M5 v2.1: sub-workflow specs a top-level agent authored via `start_workflow`, awaiting auto-drive
|
|
146
198
|
* at the next safe point (after the tool turn resolves, kernel back in Reason). */
|
|
147
199
|
private pendingAuthoredWorkflows;
|
|
@@ -149,6 +201,15 @@ export declare class RuntimeRunner {
|
|
|
149
201
|
constructor(opts: RuntimeOptions);
|
|
150
202
|
get hostOptions(): RuntimeOptions;
|
|
151
203
|
interrupt(): void;
|
|
204
|
+
/** Push a contextual note into the run's signal stream (the system-reminder channel): it drains at
|
|
205
|
+
* the next turn boundary, routes through the kernel attention policy, and — once acted on — renders
|
|
206
|
+
* as a `[SIGNAL] <text>` line in the volatile state turn plus a durable directive. `urgency` maps to
|
|
207
|
+
* the kernel disposition ladder: `"normal"` queues (default), `"high"` soft-interrupts, `"critical"`
|
|
208
|
+
* preempts. */
|
|
209
|
+
injectNote(text: string, urgency?: RuntimeSignal["urgency"]): void;
|
|
210
|
+
/** Injected-note drain shared with the main loop's per-turn poll: injected notes first (FIFO), then
|
|
211
|
+
* the configured `signalSource` — one code path so the two inbound channels never drift. */
|
|
212
|
+
private nextInboundSignal;
|
|
152
213
|
run(req: {
|
|
153
214
|
sessionId: string;
|
|
154
215
|
goal: string;
|
|
@@ -160,11 +221,29 @@ export declare class RuntimeRunner {
|
|
|
160
221
|
}>;
|
|
161
222
|
}): AsyncIterable<StreamEvent>;
|
|
162
223
|
wake(sessionId: string, extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
163
|
-
/** Push content into Slot 2 (system_knowledge) via add_knowledge_message.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
224
|
+
/** Push content into Slot 2 (system_knowledge) via add_knowledge_message.
|
|
225
|
+
* K1: `opts.key` gives the entry identity — a same-key push upserts (applied at the next
|
|
226
|
+
* compaction/renewal boundary) instead of appending a duplicate. `opts.pinned` exempts the
|
|
227
|
+
* entry from the knowledge-budget sweep. */
|
|
228
|
+
pushKnowledge(message: Message, tokens?: number, opts?: {
|
|
229
|
+
key?: string;
|
|
230
|
+
pinned?: boolean;
|
|
231
|
+
}): void;
|
|
232
|
+
/** K1: mark a keyed knowledge entry for removal at the next compaction/renewal boundary.
|
|
233
|
+
* Errs-open: an unknown key is a kernel-side no-op. */
|
|
234
|
+
removeKnowledge(key: string): void;
|
|
235
|
+
/** K3: host-driven skill deactivation — toolset re-widens at the next provider call, the
|
|
236
|
+
* skill's knowledge pin drops at the next boundary. Errs-open: not-active is a no-op. */
|
|
237
|
+
deactivateSkill(name: string): void;
|
|
167
238
|
private resolveKernelSuspend;
|
|
239
|
+
/**
|
|
240
|
+
* O7: resolve a `read_result` meta-tool call to the full text of a previously-evicted tool
|
|
241
|
+
* output. Resolution order: (a) this turn's in-memory `pendingSpoolOutputs` map, (b) the result
|
|
242
|
+
* spool (persisted once the kernel observation `large_result_spooled` was processed), (c) a
|
|
243
|
+
* session-log scan for the original `tool_completed` event carrying that `call_id`. Slices the
|
|
244
|
+
* resolved text by `[offset, offset + maxBytes)` (plain string slice — "bytes-ish").
|
|
245
|
+
*/
|
|
246
|
+
private resolveReadResult;
|
|
168
247
|
dream(agentId: string, nowMs?: number): Promise<DreamResult>;
|
|
169
248
|
private execute;
|
|
170
249
|
spawnSubAgent(spec: AgentRunSpec): Promise<SubAgentResult>;
|
|
@@ -256,6 +335,12 @@ export declare class RuntimeRunner {
|
|
|
256
335
|
failed: string[];
|
|
257
336
|
}>;
|
|
258
337
|
private appendObservations;
|
|
338
|
+
/** I4 + K4: fetch long-term memory hits for the current goal and land them in `history` as an
|
|
339
|
+
* ordinary user turn — single-use retrieval content that decays with the compression pyramid,
|
|
340
|
+
* never pinned into `knowledge`. `phase: "initial"` = once before turn 1; `phase: "renewal"` =
|
|
341
|
+
* re-fired after each sprint renewal (renewal drops the old history INCLUDING earlier memory
|
|
342
|
+
* hits). Errs-open throughout. */
|
|
343
|
+
private prefetchMemoryIntoHistory;
|
|
259
344
|
private archiveSemanticPageOut;
|
|
260
345
|
}
|
|
261
346
|
export declare function collectText(stream: AsyncIterable<StreamEvent>): Promise<string>;
|
package/dist/runtime/runner.js
CHANGED
|
@@ -11,7 +11,7 @@ import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
|
|
|
11
11
|
import { extractJsonValue, schemaInstruction, schemaRetryInstruction, validateAgainstSchema, } from "./output-schema.js";
|
|
12
12
|
import { resolveReducer } from "./reducers.js";
|
|
13
13
|
import { loopInstruction, classifyInstruction, judgeGoal, extractLoopContinue, extractClassifyBranch, extractJudgeWinner, } from "./workflow-control-flow.js";
|
|
14
|
-
import { kernelObservationToSessionEvent
|
|
14
|
+
import { kernelObservationToSessionEvent } from "./kernel-event-log.js";
|
|
15
15
|
import { assertNativeProfile } from "./os-profile.js";
|
|
16
16
|
import { LargeResultSpool } from "./large-result-spool.js";
|
|
17
17
|
export class RuntimeRunner {
|
|
@@ -22,8 +22,15 @@ export class RuntimeRunner {
|
|
|
22
22
|
pendingObservations = [];
|
|
23
23
|
activeKernel = null;
|
|
24
24
|
currentSessionId = null;
|
|
25
|
+
/** O2 (system-reminder channel): host-pushed notes awaiting the next turn-boundary drain. */
|
|
26
|
+
injectedSignals = [];
|
|
27
|
+
/** Skill names whose content has already been pushed into the durable `knowledge` slot this
|
|
28
|
+
* run — guards against re-pushing a duplicate entry if the model calls `skill(name)` again for
|
|
29
|
+
* an already-active skill (loading is idempotent; the knowledge push should be too). */
|
|
30
|
+
knowledgePushedSkills = new Set();
|
|
31
|
+
/** K4: the active run's goal, kept for the renewal-boundary memory re-query. */
|
|
32
|
+
currentGoal = "";
|
|
25
33
|
nextArchiveStart = 0;
|
|
26
|
-
localPageOutCache = [];
|
|
27
34
|
/** M5 v2.1: sub-workflow specs a top-level agent authored via `start_workflow`, awaiting auto-drive
|
|
28
35
|
* at the next safe point (after the tool turn resolves, kernel back in Reason). */
|
|
29
36
|
pendingAuthoredWorkflows = [];
|
|
@@ -33,6 +40,24 @@ export class RuntimeRunner {
|
|
|
33
40
|
}
|
|
34
41
|
get hostOptions() { return this.opts; }
|
|
35
42
|
interrupt() { this.interrupted = true; this.abortController?.abort(); }
|
|
43
|
+
/** Push a contextual note into the run's signal stream (the system-reminder channel): it drains at
|
|
44
|
+
* the next turn boundary, routes through the kernel attention policy, and — once acted on — renders
|
|
45
|
+
* as a `[SIGNAL] <text>` line in the volatile state turn plus a durable directive. `urgency` maps to
|
|
46
|
+
* the kernel disposition ladder: `"normal"` queues (default), `"high"` soft-interrupts, `"critical"`
|
|
47
|
+
* preempts. */
|
|
48
|
+
injectNote(text, urgency = "normal") {
|
|
49
|
+
this.injectedSignals.push({ source: "custom", signalType: "event", urgency, payload: { goal: text } });
|
|
50
|
+
}
|
|
51
|
+
/** Injected-note drain shared with the main loop's per-turn poll: injected notes first (FIFO), then
|
|
52
|
+
* the configured `signalSource` — one code path so the two inbound channels never drift. */
|
|
53
|
+
async nextInboundSignal() {
|
|
54
|
+
const injected = this.injectedSignals.shift();
|
|
55
|
+
if (injected)
|
|
56
|
+
return injected;
|
|
57
|
+
if (!this.opts.signalSource)
|
|
58
|
+
return null;
|
|
59
|
+
return this.opts.signalSource.nextSignal();
|
|
60
|
+
}
|
|
36
61
|
async *run(req) {
|
|
37
62
|
const prior = req.inheritEvents ?? await this.opts.sessionLog.read(req.sessionId);
|
|
38
63
|
const midRun = isMidRun(prior);
|
|
@@ -58,59 +83,35 @@ export class RuntimeRunner {
|
|
|
58
83
|
const start = startEntry.event;
|
|
59
84
|
yield* this.execute(sessionId, start.goal, start.criteria, extensions, events, true);
|
|
60
85
|
}
|
|
61
|
-
/** Push content into Slot 2 (system_knowledge) via add_knowledge_message.
|
|
62
|
-
|
|
86
|
+
/** Push content into Slot 2 (system_knowledge) via add_knowledge_message.
|
|
87
|
+
* K1: `opts.key` gives the entry identity — a same-key push upserts (applied at the next
|
|
88
|
+
* compaction/renewal boundary) instead of appending a duplicate. `opts.pinned` exempts the
|
|
89
|
+
* entry from the knowledge-budget sweep. */
|
|
90
|
+
pushKnowledge(message, tokens, opts) {
|
|
63
91
|
if (!this.activeKernel)
|
|
64
92
|
return;
|
|
65
93
|
kernelApply(this.activeKernel, this.pendingObservations, {
|
|
66
94
|
kind: "add_knowledge_message",
|
|
67
95
|
content: message.content ?? "",
|
|
68
96
|
tokens: tokens ?? Math.max(1, Math.ceil((message.content?.length ?? 0) / 4)),
|
|
97
|
+
...(opts?.key !== undefined ? { key: opts.key } : {}),
|
|
98
|
+
...(opts?.pinned ? { pinned: true } : {}),
|
|
69
99
|
});
|
|
70
100
|
}
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
101
|
+
/** K1: mark a keyed knowledge entry for removal at the next compaction/renewal boundary.
|
|
102
|
+
* Errs-open: an unknown key is a kernel-side no-op. */
|
|
103
|
+
removeKnowledge(key) {
|
|
104
|
+
if (!this.activeKernel)
|
|
75
105
|
return;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for (const hit of localHits) {
|
|
83
|
-
entries.push({
|
|
84
|
-
content: `[local semantic cache] ${hit.role}: ${hit.content}`,
|
|
85
|
-
source: "semantic_cache",
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const remainingK = topK - entries.length;
|
|
89
|
-
if (remainingK > 0 && this.opts.dreamStore && this.opts.agentId) {
|
|
90
|
-
const hits = await this.opts.dreamStore.search(this.opts.agentId, query, remainingK);
|
|
91
|
-
for (const hit of hits) {
|
|
92
|
-
entries.push({
|
|
93
|
-
content: `[memory score=${hit.score.toFixed(3)}] ${hit.text}`,
|
|
94
|
-
source: "memory",
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else if (req.tool === "knowledge" && this.opts.knowledgeSource) {
|
|
100
|
-
const snippets = await this.opts.knowledgeSource.retrieve(query, topK);
|
|
101
|
-
for (const snippet of snippets) {
|
|
102
|
-
entries.push({ content: snippet, source: "knowledge" });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (entries.length === 0)
|
|
106
|
+
kernelApply(this.activeKernel, this.pendingObservations, { kind: "remove_knowledge", key });
|
|
107
|
+
}
|
|
108
|
+
/** K3: host-driven skill deactivation — toolset re-widens at the next provider call, the
|
|
109
|
+
* skill's knowledge pin drops at the next boundary. Errs-open: not-active is a no-op. */
|
|
110
|
+
deactivateSkill(name) {
|
|
111
|
+
if (!this.activeKernel)
|
|
107
112
|
return;
|
|
108
|
-
kernelApply(
|
|
109
|
-
|
|
110
|
-
kind: "page_in",
|
|
111
|
-
turn: runtime.turn(),
|
|
112
|
-
entry_count: entries.length,
|
|
113
|
-
}));
|
|
113
|
+
kernelApply(this.activeKernel, this.pendingObservations, { kind: "skill_deactivated", name });
|
|
114
|
+
this.knowledgePushedSkills.delete(name);
|
|
114
115
|
}
|
|
115
116
|
async resolveKernelSuspend(runtime, sessionId) {
|
|
116
117
|
const gated = this.pendingObservations.filter((o) => o.kind === "tool_gated" && typeof o.call_id === "string" && typeof o.tool === "string");
|
|
@@ -190,6 +191,63 @@ export class RuntimeRunner {
|
|
|
190
191
|
}
|
|
191
192
|
return { approved, denied, events };
|
|
192
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* O7: resolve a `read_result` meta-tool call to the full text of a previously-evicted tool
|
|
196
|
+
* output. Resolution order: (a) this turn's in-memory `pendingSpoolOutputs` map, (b) the result
|
|
197
|
+
* spool (persisted once the kernel observation `large_result_spooled` was processed), (c) a
|
|
198
|
+
* session-log scan for the original `tool_completed` event carrying that `call_id`. Slices the
|
|
199
|
+
* resolved text by `[offset, offset + maxBytes)` (plain string slice — "bytes-ish").
|
|
200
|
+
*/
|
|
201
|
+
async resolveReadResult(sessionId, argsJson) {
|
|
202
|
+
let callId = "";
|
|
203
|
+
let offset = 0;
|
|
204
|
+
let maxBytes = 4000;
|
|
205
|
+
try {
|
|
206
|
+
const args = JSON.parse(argsJson || "{}");
|
|
207
|
+
callId = typeof args.call_id === "string" ? args.call_id : "";
|
|
208
|
+
if (typeof args.offset === "number" && Number.isFinite(args.offset))
|
|
209
|
+
offset = args.offset;
|
|
210
|
+
if (typeof args.max_bytes === "number" && Number.isFinite(args.max_bytes))
|
|
211
|
+
maxBytes = args.max_bytes;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// malformed arguments — callId stays empty, falls through to "not found" below
|
|
215
|
+
}
|
|
216
|
+
let full = this.pendingSpoolOutputs.get(callId)?.output;
|
|
217
|
+
if (full === undefined && this.opts.resultSpool) {
|
|
218
|
+
try {
|
|
219
|
+
full = await this.opts.resultSpool.findByCallId(callId);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
full = undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (full === undefined) {
|
|
226
|
+
try {
|
|
227
|
+
const events = await this.opts.sessionLog.read(sessionId);
|
|
228
|
+
for (const { event } of events) {
|
|
229
|
+
if (event.kind !== "tool_completed")
|
|
230
|
+
continue;
|
|
231
|
+
const match = event.results.find(r => r.call_id === callId);
|
|
232
|
+
if (match)
|
|
233
|
+
full = match.output;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
full = undefined;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (full === undefined) {
|
|
241
|
+
return { text: `no stored output for call_id "${callId}"`, isError: true };
|
|
242
|
+
}
|
|
243
|
+
const start = Math.max(0, offset);
|
|
244
|
+
const end = Math.min(full.length, start + Math.max(0, maxBytes));
|
|
245
|
+
const slice = full.slice(start, end);
|
|
246
|
+
return {
|
|
247
|
+
text: `[read_result ${callId}: chars ${start}–${end} of ${full.length}]\n${slice}`,
|
|
248
|
+
isError: false,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
193
251
|
async dream(agentId, nowMs = Date.now()) {
|
|
194
252
|
if (!this.opts.dreamStore)
|
|
195
253
|
throw new Error("dreamStore not configured");
|
|
@@ -351,14 +409,35 @@ export class RuntimeRunner {
|
|
|
351
409
|
messages: replayed.map(messageToKernelMessage),
|
|
352
410
|
});
|
|
353
411
|
// P1-B B3: rebuild active-skill gating after a wake (active_skills is not snapshotted).
|
|
412
|
+
// `knowledge` isn't snapshotted either (same graceful-reset philosophy) — best-effort re-push
|
|
413
|
+
// the skill's content from its replayed tool_result so the durable copy survives a wake too.
|
|
414
|
+
const toolResultByCallId = new Map();
|
|
415
|
+
for (const m of replayed) {
|
|
416
|
+
for (const part of m.contentParts ?? []) {
|
|
417
|
+
if (part.type === "tool_result" && part.callId && part.output !== undefined) {
|
|
418
|
+
toolResultByCallId.set(part.callId, part.output);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
354
422
|
for (const m of replayed) {
|
|
355
423
|
for (const tc of m.toolCalls ?? []) {
|
|
356
424
|
if (tc.name !== "skill")
|
|
357
425
|
continue;
|
|
358
426
|
try {
|
|
359
427
|
const name = JSON.parse(tc.arguments || "{}").name;
|
|
360
|
-
if (name)
|
|
361
|
-
|
|
428
|
+
if (!name)
|
|
429
|
+
continue;
|
|
430
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
431
|
+
kind: "skill_activated",
|
|
432
|
+
name,
|
|
433
|
+
...(this.opts.skillLeaseTurns !== undefined ? { lease_turns: this.opts.skillLeaseTurns } : {}),
|
|
434
|
+
});
|
|
435
|
+
const output = toolResultByCallId.get(tc.id);
|
|
436
|
+
if (output && !this.knowledgePushedSkills.has(name)) {
|
|
437
|
+
this.knowledgePushedSkills.add(name);
|
|
438
|
+
// K1: keyed — the kernel-side upsert is the authoritative dedup across wake replays.
|
|
439
|
+
this.pushKnowledge({ role: "system", content: output }, undefined, { key: `skill:${name}` });
|
|
440
|
+
}
|
|
362
441
|
}
|
|
363
442
|
catch { /* skip */ }
|
|
364
443
|
}
|
|
@@ -386,24 +465,13 @@ export class RuntimeRunner {
|
|
|
386
465
|
startPayload.run_spec = agentRunSpecToKernel(spec);
|
|
387
466
|
}
|
|
388
467
|
this.applyKernelPolicies(runtime);
|
|
389
|
-
// I4: pre-fetch memory
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
continue;
|
|
397
|
-
const hits = await this.opts.dreamStore.search(this.opts.agentId, q, 5);
|
|
398
|
-
for (const hit of hits) {
|
|
399
|
-
entries.push({ content: `[memory score=${hit.score.toFixed(3)}] ${hit.text}`, source: "memory" });
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (entries.length > 0) {
|
|
403
|
-
kernelApply(runtime, this.pendingObservations, { kind: "page_in", entries });
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
catch { /* errs-open */ }
|
|
468
|
+
// I4: pre-fetch memory before the first LLM turn (mirrors Node). Strict dynamic context
|
|
469
|
+
// control: single-use retrieval content, not a stable skill — lands in `history` like an
|
|
470
|
+
// ordinary `memory` tool result, so it decays with the compression pyramid instead of
|
|
471
|
+
// pinning itself in `knowledge` forever.
|
|
472
|
+
this.currentGoal = goal;
|
|
473
|
+
if (!resumeMidRun) {
|
|
474
|
+
await this.prefetchMemoryIntoHistory(runtime, "initial");
|
|
407
475
|
}
|
|
408
476
|
let action = resumeMidRun
|
|
409
477
|
? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
|
|
@@ -413,16 +481,13 @@ export class RuntimeRunner {
|
|
|
413
481
|
// I0b: kernel-throw safety net — see Node runner for full rationale.
|
|
414
482
|
try {
|
|
415
483
|
while (!runtime.isTerminal()) {
|
|
416
|
-
if (action.kind === "execute_tool") {
|
|
417
|
-
await this.applyKernelPageIn(runtime, sessionId);
|
|
418
|
-
}
|
|
419
484
|
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
|
|
420
485
|
if (this.interrupted) {
|
|
421
486
|
action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
|
|
422
487
|
break;
|
|
423
488
|
}
|
|
424
|
-
if (this.opts.signalSource) {
|
|
425
|
-
const sig = await this.
|
|
489
|
+
if (this.opts.signalSource || this.injectedSignals.length > 0) {
|
|
490
|
+
const sig = await this.nextInboundSignal();
|
|
426
491
|
if (sig) {
|
|
427
492
|
const sigAction = kernelMaybeAction(runtime, this.pendingObservations, signalToKernelEvent(sig));
|
|
428
493
|
if (sigAction)
|
|
@@ -608,7 +673,16 @@ export class RuntimeRunner {
|
|
|
608
673
|
// the orchestrator collects them and `runWorkflow` sends them to the parent kernel.
|
|
609
674
|
// M5 v1: `start_workflow` (author a sub-workflow) flattens to the same append path.
|
|
610
675
|
const submitCalls = allCalls.filter(c => c.name === "submit_workflow_nodes" || c.name === "start_workflow");
|
|
611
|
-
|
|
676
|
+
// O7: `read_result` re-fetches a tool output the kernel evicted from context. Content is
|
|
677
|
+
// host-resolved: (a) this turn's in-memory pending spool map, (b) the on-disk result spool,
|
|
678
|
+
// (c) a session-log scan for the original `tool_completed` event.
|
|
679
|
+
const readResultCalls = allCalls.filter(c => c.name === "read_result");
|
|
680
|
+
const normalCalls = allCalls.filter(c => c.name !== "submit_workflow_nodes" && c.name !== "start_workflow" && c.name !== "read_result");
|
|
681
|
+
for (const call of readResultCalls) {
|
|
682
|
+
const out = await this.resolveReadResult(sessionId, call.arguments);
|
|
683
|
+
toolResults.push({ callId: call.id, output: out.text, isError: out.isError });
|
|
684
|
+
yield { type: "tool_result", callId: call.id, content: out.text, isError: out.isError };
|
|
685
|
+
}
|
|
612
686
|
for (const call of submitCalls) {
|
|
613
687
|
// M5 v2.1: a TOP-LEVEL agent authoring a whole sub-workflow via `start_workflow` — record the
|
|
614
688
|
// spec and AUTO-PIVOT once this tool turn resolves. A workflow-NODE's `start_workflow` (and
|
|
@@ -630,7 +704,35 @@ export class RuntimeRunner {
|
|
|
630
704
|
toolResults.push({ callId: call.id, output: "submitted", isError: false });
|
|
631
705
|
yield { type: "tool_result", callId: call.id, content: "submitted", isError: false };
|
|
632
706
|
}
|
|
633
|
-
|
|
707
|
+
// O5 (PreToolUse-hook analog): stateful host veto over each kernel-approved call.
|
|
708
|
+
// A blocked call never executes; its reason reaches the model as a denied result.
|
|
709
|
+
let executableCalls = normalCalls;
|
|
710
|
+
if (this.opts.onToolCall) {
|
|
711
|
+
const allowed = [];
|
|
712
|
+
for (const call of normalCalls) {
|
|
713
|
+
let decision;
|
|
714
|
+
try {
|
|
715
|
+
decision = await this.opts.onToolCall({ callId: call.id, name: call.name, arguments: call.arguments });
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
decision = undefined;
|
|
719
|
+
}
|
|
720
|
+
if (decision?.block) {
|
|
721
|
+
const reason = decision.reason ?? "blocked by host onToolCall hook";
|
|
722
|
+
yield { type: "tool_denied", callId: call.id, toolName: call.name, reason };
|
|
723
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
724
|
+
kind: "tool_denied", turn: runtime.turn(), call_id: call.id, tool_name: call.name, reason,
|
|
725
|
+
});
|
|
726
|
+
const out = `blocked by host hook: ${reason}`;
|
|
727
|
+
toolResults.push({ callId: call.id, output: out, isError: true, errorKind: "governance_denied" });
|
|
728
|
+
yield { type: "tool_result", callId: call.id, name: call.name, content: out, isError: true };
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
allowed.push(call);
|
|
732
|
+
}
|
|
733
|
+
executableCalls = allowed;
|
|
734
|
+
}
|
|
735
|
+
for await (const evt of this.opts.executionPlane.executeAll(executableCalls, runCtx)) {
|
|
634
736
|
yield evt;
|
|
635
737
|
if (evt.type === "tool_result") {
|
|
636
738
|
const tre = evt;
|
|
@@ -684,6 +786,31 @@ export class RuntimeRunner {
|
|
|
684
786
|
});
|
|
685
787
|
}
|
|
686
788
|
}
|
|
789
|
+
// O5 (PostToolUse-hook analog): host inspection of each executed result before it reaches
|
|
790
|
+
// the kernel/session-log — replace the output and/or inject a signal note. Errs-open.
|
|
791
|
+
if (this.opts.onToolResult) {
|
|
792
|
+
for (const r of toolResults) {
|
|
793
|
+
const call = executableCalls.find(c => c.id === r.callId);
|
|
794
|
+
if (!call)
|
|
795
|
+
continue;
|
|
796
|
+
let decision;
|
|
797
|
+
try {
|
|
798
|
+
decision = await this.opts.onToolResult({
|
|
799
|
+
callId: r.callId, name: call.name, arguments: call.arguments,
|
|
800
|
+
output: r.output, isError: r.isError,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
decision = undefined;
|
|
805
|
+
}
|
|
806
|
+
if (!decision)
|
|
807
|
+
continue;
|
|
808
|
+
if (typeof decision.replaceOutput === "string")
|
|
809
|
+
r.output = decision.replaceOutput;
|
|
810
|
+
if (decision.note)
|
|
811
|
+
this.injectNote(decision.note);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
687
814
|
await this.opts.sessionLog.append(sessionId, {
|
|
688
815
|
kind: "tool_completed",
|
|
689
816
|
turn: runtime.turn(),
|
|
@@ -701,6 +828,12 @@ export class RuntimeRunner {
|
|
|
701
828
|
}
|
|
702
829
|
}
|
|
703
830
|
// P1-B B3: a successfully-resolved `skill` call activates that skill for the next turn.
|
|
831
|
+
//
|
|
832
|
+
// Strict dynamic context control: a skill is METHOD content — how to do something — reused
|
|
833
|
+
// for the rest of the run, unlike a one-off memory/knowledge lookup (fact content, relevant
|
|
834
|
+
// for the moment it's used). So its text ALSO goes into the durable `knowledge` slot here
|
|
835
|
+
// (in addition to the ordinary tool_result already headed for `history`, where it will decay
|
|
836
|
+
// with the compression pyramid like any other tool output). First activation only.
|
|
704
837
|
for (const call of allCalls) {
|
|
705
838
|
if (call.name !== "skill")
|
|
706
839
|
continue;
|
|
@@ -709,8 +842,20 @@ export class RuntimeRunner {
|
|
|
709
842
|
continue;
|
|
710
843
|
try {
|
|
711
844
|
const name = JSON.parse(call.arguments || "{}").name;
|
|
712
|
-
if (name)
|
|
713
|
-
|
|
845
|
+
if (!name)
|
|
846
|
+
continue;
|
|
847
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
848
|
+
kind: "skill_activated",
|
|
849
|
+
name,
|
|
850
|
+
...(this.opts.skillLeaseTurns !== undefined ? { lease_turns: this.opts.skillLeaseTurns } : {}),
|
|
851
|
+
});
|
|
852
|
+
// With a lease configured, skip the Set optimization: an expired-then-reloaded skill
|
|
853
|
+
// must re-pin — only the kernel knows the lease state; its upsert dedupes anyway.
|
|
854
|
+
if (this.opts.skillLeaseTurns !== undefined || !this.knowledgePushedSkills.has(name)) {
|
|
855
|
+
this.knowledgePushedSkills.add(name);
|
|
856
|
+
// K1: keyed `skill:<name>` — the kernel-side upsert dedupes across runner instances.
|
|
857
|
+
this.pushKnowledge({ role: "system", content: res.output }, undefined, { key: `skill:${name}` });
|
|
858
|
+
}
|
|
714
859
|
}
|
|
715
860
|
catch { /* skip */ }
|
|
716
861
|
}
|
|
@@ -991,6 +1136,21 @@ export class RuntimeRunner {
|
|
|
991
1136
|
: {}),
|
|
992
1137
|
};
|
|
993
1138
|
}
|
|
1139
|
+
// O6: tune/disable the in-kernel repeat fuse (absent ⇒ kernel defaults: enabled, 5/8).
|
|
1140
|
+
if (this.opts.repeatFuse !== undefined) {
|
|
1141
|
+
const rf = this.opts.repeatFuse;
|
|
1142
|
+
config.repeat_fuse = rf === false
|
|
1143
|
+
? { enabled: false, deny_after: 0, terminate_after: 0 }
|
|
1144
|
+
: { enabled: true, deny_after: rf.denyAfter ?? 5, terminate_after: rf.terminateAfter ?? 8 };
|
|
1145
|
+
}
|
|
1146
|
+
// O4: turn-end criteria gate toggle (absent ⇒ kernel default: enabled).
|
|
1147
|
+
if (this.opts.criteriaGate !== undefined) {
|
|
1148
|
+
config.criteria_gate = this.opts.criteriaGate;
|
|
1149
|
+
}
|
|
1150
|
+
// K2: knowledge budget ratio (absent ⇒ kernel default 0.25; 0 disables).
|
|
1151
|
+
if (this.opts.knowledgeBudgetRatio !== undefined) {
|
|
1152
|
+
config.knowledge_budget_ratio = this.opts.knowledgeBudgetRatio;
|
|
1153
|
+
}
|
|
994
1154
|
kernelApply(runtime, this.pendingObservations, { kind: "configure_run", config });
|
|
995
1155
|
if (this.opts.memoryPolicy) {
|
|
996
1156
|
const m = this.opts.memoryPolicy;
|
|
@@ -1113,7 +1273,8 @@ export class RuntimeRunner {
|
|
|
1113
1273
|
if (!source)
|
|
1114
1274
|
return null;
|
|
1115
1275
|
while (!batchState.settled) {
|
|
1116
|
-
|
|
1276
|
+
// O2: injected notes participate in the monitor too (drain order matches nextInboundSignal).
|
|
1277
|
+
const sig = this.injectedSignals.shift() ?? await source.nextSignal();
|
|
1117
1278
|
if (batchState.settled)
|
|
1118
1279
|
break;
|
|
1119
1280
|
if (!sig) {
|
|
@@ -1258,9 +1419,6 @@ export class RuntimeRunner {
|
|
|
1258
1419
|
});
|
|
1259
1420
|
if (!event)
|
|
1260
1421
|
continue;
|
|
1261
|
-
if (obs.kind === "page_out" && obs.archived) {
|
|
1262
|
-
this.localPageOutCache.push(...obs.archived);
|
|
1263
|
-
}
|
|
1264
1422
|
const compressedSeq = await this.opts.sessionLog.append(sessionId, event);
|
|
1265
1423
|
if (event.kind === "compressed") {
|
|
1266
1424
|
nextArchiveStart = compressedSeq + 1;
|
|
@@ -1271,9 +1429,42 @@ export class RuntimeRunner {
|
|
|
1271
1429
|
&& obs.archived.length > 0) {
|
|
1272
1430
|
void this.archiveSemanticPageOut(obs.archived, compressionAction(obs.action));
|
|
1273
1431
|
}
|
|
1432
|
+
// K4: a sprint renewal dropped the old history — including any earlier memory hits — so
|
|
1433
|
+
// re-run the preQueryMemory prefetch for the new sprint (live observations only).
|
|
1434
|
+
if (obs.kind === "renewed") {
|
|
1435
|
+
await this.prefetchMemoryIntoHistory(runtime, "renewal");
|
|
1436
|
+
}
|
|
1274
1437
|
}
|
|
1275
1438
|
return nextArchiveStart;
|
|
1276
1439
|
}
|
|
1440
|
+
/** I4 + K4: fetch long-term memory hits for the current goal and land them in `history` as an
|
|
1441
|
+
* ordinary user turn — single-use retrieval content that decays with the compression pyramid,
|
|
1442
|
+
* never pinned into `knowledge`. `phase: "initial"` = once before turn 1; `phase: "renewal"` =
|
|
1443
|
+
* re-fired after each sprint renewal (renewal drops the old history INCLUDING earlier memory
|
|
1444
|
+
* hits). Errs-open throughout. */
|
|
1445
|
+
async prefetchMemoryIntoHistory(runtime, phase) {
|
|
1446
|
+
if (!this.opts.preQueryMemory || !this.opts.dreamStore || !this.opts.agentId)
|
|
1447
|
+
return;
|
|
1448
|
+
try {
|
|
1449
|
+
const queries = await this.opts.preQueryMemory({ goal: this.currentGoal, phase });
|
|
1450
|
+
const lines = [];
|
|
1451
|
+
for (const q of queries ?? []) {
|
|
1452
|
+
if (typeof q !== "string" || !q.trim())
|
|
1453
|
+
continue;
|
|
1454
|
+
const hits = await this.opts.dreamStore.search(this.opts.agentId, q, 5);
|
|
1455
|
+
for (const hit of hits) {
|
|
1456
|
+
lines.push(`[memory score=${hit.score.toFixed(3)}] ${hit.text}`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (lines.length > 0) {
|
|
1460
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
1461
|
+
kind: "add_history_message",
|
|
1462
|
+
message: { role: "user", content: lines.join("\n") },
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
catch { /* errs-open */ }
|
|
1467
|
+
}
|
|
1277
1468
|
async archiveSemanticPageOut(archived, action) {
|
|
1278
1469
|
if (!this.opts.dreamStore || !this.opts.agentId)
|
|
1279
1470
|
return;
|
|
@@ -19,7 +19,9 @@ function terminationFromStatus(status) {
|
|
|
19
19
|
normalized === "timeout" ||
|
|
20
20
|
normalized === "user_abort" ||
|
|
21
21
|
normalized === "error" ||
|
|
22
|
-
normalized === "milestone_exceeded"
|
|
22
|
+
normalized === "milestone_exceeded" ||
|
|
23
|
+
normalized === "context_overflow" ||
|
|
24
|
+
normalized === "no_progress") {
|
|
23
25
|
return normalized;
|
|
24
26
|
}
|
|
25
27
|
return status;
|
|
@@ -72,6 +74,9 @@ export class SubAgentOrchestrator {
|
|
|
72
74
|
provider: resolveProvider(ctx.parentOpts, ctx.spec.modelHint),
|
|
73
75
|
// M4/G5: cap the child run at the node's token budget (falls back to the inherited cap).
|
|
74
76
|
maxTotalTokens: ctx.spec.tokenBudget ?? ctx.parentOpts.maxTotalTokens,
|
|
77
|
+
// O3: per-child turn / wall-clock caps (fall back to the inherited limits).
|
|
78
|
+
maxTurns: ctx.spec.maxTurns ?? ctx.parentOpts.maxTurns,
|
|
79
|
+
timeoutMs: ctx.spec.maxWallMs ?? ctx.parentOpts.timeoutMs,
|
|
75
80
|
executionPlane: filteredPlane,
|
|
76
81
|
agentId: ctx.spec.identity.agentId,
|
|
77
82
|
systemPrompt,
|
|
@@ -2,7 +2,12 @@ import type { Message, ToolSchema } from "../../types.js";
|
|
|
2
2
|
export type KernelAgentRole = "explore" | "plan" | "implement" | "verify" | "custom";
|
|
3
3
|
export type AgentIsolation = "shared" | "read_only" | "worktree" | "remote";
|
|
4
4
|
export type ContextInheritance = "none" | "system_only" | "full";
|
|
5
|
-
export type TerminationReason = "completed" | "max_turns" | "token_budget" | "timeout" | "user_abort" | "error" | "milestone_exceeded"
|
|
5
|
+
export type TerminationReason = "completed" | "max_turns" | "token_budget" | "timeout" | "user_abort" | "error" | "milestone_exceeded"
|
|
6
|
+
/** v0.2.35 recovery ladder: compaction exhausted and the prompt still exceeds the provider window. */
|
|
7
|
+
| "context_overflow"
|
|
8
|
+
/** Repeat-fuse escalation: the same tool call (name AND args) re-issued past `terminateAfter` —
|
|
9
|
+
* a stall, distinct from `max_turns` which productive runs can also hit. */
|
|
10
|
+
| "no_progress";
|
|
6
11
|
export type MilestonePolicy = "require_verifier" | "terminate" | "auto_pass";
|
|
7
12
|
export interface AgentIdentity {
|
|
8
13
|
agentId: string;
|
|
@@ -28,6 +33,10 @@ export interface AgentRunSpec {
|
|
|
28
33
|
modelHint?: string;
|
|
29
34
|
/** M4/G5: cumulative token cap for this sub-agent's run (sets the child kernel's `maxTotalTokens`). */
|
|
30
35
|
tokenBudget?: number;
|
|
36
|
+
/** O3: per-child turn cap (sets the child runner's `maxTurns`; falls back to the parent's). */
|
|
37
|
+
maxTurns?: number;
|
|
38
|
+
/** O3: per-child wall-clock cap in ms (sets the child runner's `timeoutMs`; falls back to the parent's). */
|
|
39
|
+
maxWallMs?: number;
|
|
31
40
|
}
|
|
32
41
|
/** Kernel process-table observation (Phase 3 canonical spawn signal). */
|
|
33
42
|
export interface AgentProcessChangedObservation {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/wasm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.36",
|
|
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.2.
|
|
18
|
+
"@deepstrike/wasm-kernel": "0.2.36"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/jest": "^30.0.0",
|