@deepstrike/sdk 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -4
- package/dist/index.js +5 -2
- package/dist/kernel.d.ts +54 -0
- package/dist/kernel.js +8 -0
- package/dist/providers/anthropic.d.ts +4 -1
- package/dist/providers/anthropic.js +51 -0
- package/dist/providers/base.d.ts +6 -0
- package/dist/providers/base.js +10 -1
- package/dist/providers/catalog.js +5 -2
- package/dist/providers/deepseek.d.ts +4 -1
- package/dist/providers/deepseek.js +79 -8
- package/dist/providers/glm.d.ts +2 -1
- package/dist/providers/glm.js +7 -0
- package/dist/providers/kimi.d.ts +2 -1
- package/dist/providers/kimi.js +7 -0
- package/dist/providers/minimax.d.ts +28 -2
- package/dist/providers/minimax.js +200 -1
- package/dist/providers/openai-chat.d.ts +18 -2
- package/dist/providers/openai-chat.js +37 -3
- package/dist/providers/openai.d.ts +14 -1
- package/dist/providers/openai.js +48 -7
- package/dist/providers/profiles.d.ts +6 -0
- package/dist/providers/profiles.js +6 -0
- package/dist/providers/qwen.d.ts +3 -1
- package/dist/providers/qwen.js +20 -2
- package/dist/providers/replay-validator.d.ts +32 -0
- package/dist/providers/replay-validator.js +90 -0
- package/dist/runtime/kernel-event-log.js +22 -0
- package/dist/runtime/kernel-step.d.ts +13 -0
- package/dist/runtime/provider-replay.d.ts +16 -1
- package/dist/runtime/provider-replay.js +47 -4
- package/dist/runtime/runner.d.ts +22 -1
- package/dist/runtime/runner.js +68 -2
- package/dist/runtime/session-log.d.ts +22 -0
- package/dist/runtime/session-repair.d.ts +26 -3
- package/dist/runtime/session-repair.js +33 -32
- package/dist/types/agent.d.ts +50 -0
- package/dist/types/agent.js +110 -0
- package/dist/types.d.ts +38 -0
- package/package.json +2 -2
|
@@ -198,6 +198,28 @@ export function kernelObservationToSessionEvent(obs, turn, opts = {}) {
|
|
|
198
198
|
memory_id: obs.memory_id ?? "",
|
|
199
199
|
error: obs.error ?? "",
|
|
200
200
|
});
|
|
201
|
+
case "workflow_batch_spawned": {
|
|
202
|
+
// Batch metadata persisted for resume recovery; individual nodes are
|
|
203
|
+
// recorded when they complete (via workflow_node_completed).
|
|
204
|
+
const nodes = obs.nodes ?? [];
|
|
205
|
+
return withCategory({
|
|
206
|
+
kind: "workflow_batch_spawned",
|
|
207
|
+
turn: t,
|
|
208
|
+
node_count: nodes.length,
|
|
209
|
+
node_ids: nodes.map((n) => n.agent_id ?? ""),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
case "workflow_completed": {
|
|
213
|
+
const completed = obs.completed ?? [];
|
|
214
|
+
const failed = obs.failed ?? [];
|
|
215
|
+
return withCategory({
|
|
216
|
+
kind: "workflow_completed",
|
|
217
|
+
turn: t,
|
|
218
|
+
completed,
|
|
219
|
+
failed,
|
|
220
|
+
total_nodes: completed.length + failed.length,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
201
223
|
default:
|
|
202
224
|
return null;
|
|
203
225
|
}
|
|
@@ -90,6 +90,19 @@ export interface KernelObservation {
|
|
|
90
90
|
requires_async_response?: boolean;
|
|
91
91
|
/** memory_validation_failed (Phase 7). */
|
|
92
92
|
error?: string;
|
|
93
|
+
/** workflow_batch_spawned: per-node spawn descriptors (agent_id + goal + role/isolation). */
|
|
94
|
+
nodes?: Array<{
|
|
95
|
+
agent_id: string;
|
|
96
|
+
goal: string;
|
|
97
|
+
role: string;
|
|
98
|
+
isolation: string;
|
|
99
|
+
context_inheritance: string;
|
|
100
|
+
model_hint?: string;
|
|
101
|
+
trust?: string;
|
|
102
|
+
}>;
|
|
103
|
+
/** workflow_completed. */
|
|
104
|
+
completed?: string[];
|
|
105
|
+
failed?: string[];
|
|
93
106
|
}
|
|
94
107
|
export declare function toolSchemaToKernel(schema: ToolSchema): Record<string, unknown>;
|
|
95
108
|
export declare function skillMetadataToKernel(skill: SkillMetadata): Record<string, unknown>;
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import type { LLMProvider, Message, ProviderReplay, ToolCall } from "../types.js";
|
|
1
|
+
import type { LLMProvider, Message, ProviderDescriptor, ProviderReplay, RenderedContext, ReplayabilityAssessment, ToolCall } from "../types.js";
|
|
2
2
|
import type { SessionEvent } from "./session-log.js";
|
|
3
3
|
export declare function assistantReplayKey(message: Pick<Message, "content" | "toolCalls">): string;
|
|
4
|
+
/**
|
|
5
|
+
* A stored replay may only be seeded into a provider speaking the same wire
|
|
6
|
+
* protocol. On a cross-protocol fallback (provider A -> provider B) the
|
|
7
|
+
* incompatible envelope is skipped so B re-serializes neutral context instead
|
|
8
|
+
* of replaying A's protocol-specific shape.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isReplayCompatibleWithProvider(replay: ProviderReplay, descriptor: ProviderDescriptor | undefined): boolean;
|
|
4
11
|
export declare function seedProviderReplayFromEvents(provider: LLMProvider, events: Array<{
|
|
5
12
|
event: SessionEvent;
|
|
6
13
|
}>): void;
|
|
7
14
|
export declare function peekProviderReplay(provider: LLMProvider, content: string, toolCalls: ToolCall[]): ProviderReplay | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Pre-flight query for fallback routing: would `context` validate against
|
|
17
|
+
* `provider` (with `extensions`) before the request is sent? Seed any persisted
|
|
18
|
+
* replay (via `seedProviderReplayFromEvents`) first so the assessment reflects
|
|
19
|
+
* what the provider can actually replay. Providers that do not implement
|
|
20
|
+
* `assessReplayability` (no reasoning-replay requirement) are reported as ok.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assessProviderReplayability(provider: LLMProvider, context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { effectiveProviderReplay } from "./session-repair.js";
|
|
2
1
|
function sortObjectKeys(val) {
|
|
3
2
|
if (val === null || typeof val !== "object") {
|
|
4
3
|
return val;
|
|
@@ -34,19 +33,63 @@ export function assistantReplayKey(message) {
|
|
|
34
33
|
toolCalls,
|
|
35
34
|
});
|
|
36
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Infer the wire protocol a stored replay envelope belongs to.
|
|
38
|
+
*
|
|
39
|
+
* New envelopes carry an explicit `protocol`. Legacy envelopes are inferred
|
|
40
|
+
* from their shape: Anthropic persisted `native_blocks`, OpenAI-compatible
|
|
41
|
+
* persisted `reasoning_content` / `reasoning_details`.
|
|
42
|
+
*/
|
|
43
|
+
function replayProtocol(replay) {
|
|
44
|
+
if (replay.protocol)
|
|
45
|
+
return replay.protocol;
|
|
46
|
+
if (replay.native_blocks?.length)
|
|
47
|
+
return "anthropic-messages";
|
|
48
|
+
if (replay.reasoning_content != null || replay.reasoning_details !== undefined)
|
|
49
|
+
return "openai-chat";
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A stored replay may only be seeded into a provider speaking the same wire
|
|
54
|
+
* protocol. On a cross-protocol fallback (provider A -> provider B) the
|
|
55
|
+
* incompatible envelope is skipped so B re-serializes neutral context instead
|
|
56
|
+
* of replaying A's protocol-specific shape.
|
|
57
|
+
*/
|
|
58
|
+
export function isReplayCompatibleWithProvider(replay, descriptor) {
|
|
59
|
+
if (!descriptor)
|
|
60
|
+
return true;
|
|
61
|
+
const protocol = replayProtocol(replay);
|
|
62
|
+
if (!protocol)
|
|
63
|
+
return true;
|
|
64
|
+
return protocol === descriptor.protocol;
|
|
65
|
+
}
|
|
37
66
|
export function seedProviderReplayFromEvents(provider, events) {
|
|
38
67
|
if (!provider.seedProviderReplay)
|
|
39
68
|
return;
|
|
69
|
+
const descriptor = provider.descriptor?.();
|
|
40
70
|
for (const { event } of events) {
|
|
41
71
|
if (event.kind !== "llm_completed")
|
|
42
72
|
continue;
|
|
43
73
|
const toolCalls = event.tool_calls ?? [];
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
74
|
+
const stored = event.provider_replay;
|
|
75
|
+
if (stored && !isReplayCompatibleWithProvider(stored, descriptor))
|
|
46
76
|
continue;
|
|
47
|
-
|
|
77
|
+
// Pass the message even when no replay was persisted: a provider may
|
|
78
|
+
// reconstruct a legacy replay (e.g. Anthropic native_blocks) from the
|
|
79
|
+
// neutral transcript. Providers that cannot reconstruct simply no-op.
|
|
80
|
+
provider.seedProviderReplay({ content: event.content, toolCalls }, stored ?? {});
|
|
48
81
|
}
|
|
49
82
|
}
|
|
50
83
|
export function peekProviderReplay(provider, content, toolCalls) {
|
|
51
84
|
return provider.peekProviderReplay?.({ content, toolCalls });
|
|
52
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Pre-flight query for fallback routing: would `context` validate against
|
|
88
|
+
* `provider` (with `extensions`) before the request is sent? Seed any persisted
|
|
89
|
+
* replay (via `seedProviderReplayFromEvents`) first so the assessment reflects
|
|
90
|
+
* what the provider can actually replay. Providers that do not implement
|
|
91
|
+
* `assessReplayability` (no reasoning-replay requirement) are reported as ok.
|
|
92
|
+
*/
|
|
93
|
+
export function assessProviderReplayability(provider, context, extensions) {
|
|
94
|
+
return provider.assessReplayability?.(context, extensions) ?? { ok: true, offendingCallIds: [] };
|
|
95
|
+
}
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { SessionLog, SessionEvent } from "./session-log.js";
|
|
|
6
6
|
import type { ArchiveStore } from "./archive.js";
|
|
7
7
|
import type { ExecutionPlane } from "./execution-plane.js";
|
|
8
8
|
import { type MemoryPolicy, type ResourceQuota } from "../kernel.js";
|
|
9
|
-
import type { AgentRunSpec, MilestoneCheckResult, MilestoneContract, MilestonePolicy } from "../types/agent.js";
|
|
9
|
+
import type { AgentRunSpec, MilestoneCheckResult, MilestoneContract, MilestonePolicy, WorkflowSpec } from "../types/agent.js";
|
|
10
10
|
import { type SubAgentOrchestrator } from "./sub-agent-orchestrator.js";
|
|
11
11
|
import { type GovernancePolicy } from "../governance.js";
|
|
12
12
|
import { type NativeOsProfile, type OsProfileId } from "./os-profile.js";
|
|
@@ -159,6 +159,27 @@ export declare class RuntimeRunner {
|
|
|
159
159
|
* Requires an active parent run (`run()` / `wake()` in progress or paused at milestone).
|
|
160
160
|
*/
|
|
161
161
|
spawnSubAgent(spec: AgentRunSpec): AsyncIterable<StreamEvent>;
|
|
162
|
+
/**
|
|
163
|
+
* W0-ABI: run a declarative workflow DAG. The kernel owns the DAG and gates every node spawn
|
|
164
|
+
* through the syscall trap; this driver runs each kernel-emitted batch of nodes in parallel,
|
|
165
|
+
* feeds their results back, and loops until the kernel reports the workflow complete.
|
|
166
|
+
* Returns the completed / failed node agent-ids.
|
|
167
|
+
*/
|
|
168
|
+
runWorkflow(spec: WorkflowSpec, opts?: {
|
|
169
|
+
resumedCompleted?: string[];
|
|
170
|
+
}): Promise<{
|
|
171
|
+
completed: string[];
|
|
172
|
+
failed: string[];
|
|
173
|
+
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Resume a workflow from the parent session's completed nodes.
|
|
176
|
+
* Reads the session log, extracts completed workflow node agent_ids, and
|
|
177
|
+
* calls runWorkflow with resumedCompleted so the kernel skips those nodes.
|
|
178
|
+
*/
|
|
179
|
+
resumeWorkflow(spec: WorkflowSpec): Promise<{
|
|
180
|
+
completed: string[];
|
|
181
|
+
failed: string[];
|
|
182
|
+
}>;
|
|
162
183
|
interrupt(): void;
|
|
163
184
|
run(req: {
|
|
164
185
|
sessionId: string;
|
package/dist/runtime/runner.js
CHANGED
|
@@ -3,10 +3,10 @@ import { resolvePermissionRequest } from "./execution-plane.js";
|
|
|
3
3
|
import { getKernel } from "../kernel.js";
|
|
4
4
|
import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-replay.js";
|
|
5
5
|
import { sanitizeReplayText } from "./replay-sanitize.js";
|
|
6
|
-
import { buildLlmCompletedEvent, buildRunTerminalEvent, repairEventsForRecovery } from "./session-repair.js";
|
|
6
|
+
import { buildLlmCompletedEvent, buildRunTerminalEvent, buildWorkflowNodeCompletedEvent, recoverCompletedWorkflowNodes, repairEventsForRecovery, } from "./session-repair.js";
|
|
7
7
|
import { KernelPrimitivesDashboard } from "./kernel-primitives-dashboard.js";
|
|
8
8
|
import { capabilityMarker, capabilitySkill, capabilityTool, capabilityCommandMount, capabilityCommandUnmount, kernelAction, kernelApply, kernelMaybeAction, forceCompact, messageToKernelMessage, skillMetadataToKernel, taskUpdateToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
|
|
9
|
-
import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, } from "../types/agent.js";
|
|
9
|
+
import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, workflowNodeToManifest, workflowNodeToSpec, workflowSpecToKernel, } from "../types/agent.js";
|
|
10
10
|
import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
|
|
11
11
|
import { governancePolicyToKernelEvent } from "../governance.js";
|
|
12
12
|
import { kernelObservationToSessionEvent, withCategory } from "./kernel-event-log.js";
|
|
@@ -256,6 +256,72 @@ export class RuntimeRunner {
|
|
|
256
256
|
});
|
|
257
257
|
yield { type: "done", iterations: result.result.turnsUsed, totalTokens: result.result.totalTokensUsed, status: result.result.termination };
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* W0-ABI: run a declarative workflow DAG. The kernel owns the DAG and gates every node spawn
|
|
261
|
+
* through the syscall trap; this driver runs each kernel-emitted batch of nodes in parallel,
|
|
262
|
+
* feeds their results back, and loops until the kernel reports the workflow complete.
|
|
263
|
+
* Returns the completed / failed node agent-ids.
|
|
264
|
+
*/
|
|
265
|
+
async runWorkflow(spec, opts) {
|
|
266
|
+
if (!this.activeKernel || !this.currentSessionId) {
|
|
267
|
+
throw new Error("runWorkflow requires an active parent run");
|
|
268
|
+
}
|
|
269
|
+
const parentSessionId = this.currentSessionId;
|
|
270
|
+
const runtime = this.activeKernel;
|
|
271
|
+
const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
|
|
272
|
+
let observations = kernelApply(runtime, this.pendingObservations, {
|
|
273
|
+
kind: "load_workflow",
|
|
274
|
+
spec: workflowSpecToKernel(spec),
|
|
275
|
+
parent_session_id: parentSessionId,
|
|
276
|
+
// W0-ABI resume: skip nodes already completed before an interruption.
|
|
277
|
+
...(opts?.resumedCompleted?.length ? { resumed_completed: opts.resumedCompleted } : {}),
|
|
278
|
+
});
|
|
279
|
+
for (;;) {
|
|
280
|
+
const done = observations.find(o => o.kind === "workflow_completed");
|
|
281
|
+
if (done)
|
|
282
|
+
return { completed: done.completed ?? [], failed: done.failed ?? [] };
|
|
283
|
+
const batch = observations.find(o => o.kind === "workflow_batch_spawned");
|
|
284
|
+
const nodes = batch?.nodes ?? [];
|
|
285
|
+
if (nodes.length === 0)
|
|
286
|
+
return { completed: [], failed: [] }; // nothing to run (e.g. all gated)
|
|
287
|
+
// Run the batch's nodes in parallel — each is independent within a round.
|
|
288
|
+
const results = await Promise.all(nodes.map(node => orchestrator.run({
|
|
289
|
+
parentOpts: this.opts,
|
|
290
|
+
parentSessionId,
|
|
291
|
+
spec: workflowNodeToSpec(node, parentSessionId),
|
|
292
|
+
manifest: workflowNodeToManifest(node, parentSessionId),
|
|
293
|
+
sessionLog: this.opts.sessionLog,
|
|
294
|
+
...(this.opts.subAgentHarness ? { harness: this.opts.subAgentHarness } : {}),
|
|
295
|
+
})));
|
|
296
|
+
// Feed completions back; the draining feed yields the next batch or completion.
|
|
297
|
+
observations = [];
|
|
298
|
+
for (const result of results) {
|
|
299
|
+
observations = kernelApply(runtime, this.pendingObservations, {
|
|
300
|
+
kind: "sub_agent_completed",
|
|
301
|
+
result: subAgentResultToKernel(result),
|
|
302
|
+
});
|
|
303
|
+
// Persist node completion for resume recovery.
|
|
304
|
+
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodeCompletedEvent({
|
|
305
|
+
turn: runtime.turn(),
|
|
306
|
+
agentId: result.agentId,
|
|
307
|
+
termination: result.result.termination,
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Resume a workflow from the parent session's completed nodes.
|
|
314
|
+
* Reads the session log, extracts completed workflow node agent_ids, and
|
|
315
|
+
* calls runWorkflow with resumedCompleted so the kernel skips those nodes.
|
|
316
|
+
*/
|
|
317
|
+
async resumeWorkflow(spec) {
|
|
318
|
+
if (!this.currentSessionId) {
|
|
319
|
+
throw new Error("resumeWorkflow requires an active parent run");
|
|
320
|
+
}
|
|
321
|
+
const events = await this.opts.sessionLog.read(this.currentSessionId);
|
|
322
|
+
const resumedCompleted = recoverCompletedWorkflowNodes(events);
|
|
323
|
+
return this.runWorkflow(spec, { resumedCompleted });
|
|
324
|
+
}
|
|
259
325
|
interrupt() { this.interrupted = true; }
|
|
260
326
|
async *run(req) {
|
|
261
327
|
const prior = req.inheritEvents ?? await this.opts.sessionLog.read(req.sessionId);
|
|
@@ -236,6 +236,28 @@ export type SessionEvent = {
|
|
|
236
236
|
kind: "memory_retrieval_result";
|
|
237
237
|
selected_memory_ids: string[];
|
|
238
238
|
selection_rationale: string;
|
|
239
|
+
} | {
|
|
240
|
+
kind: "workflow_node_completed";
|
|
241
|
+
turn: number;
|
|
242
|
+
category?: KernelEventCategory;
|
|
243
|
+
primitive?: KernelPrimitive;
|
|
244
|
+
agent_id: string;
|
|
245
|
+
termination: string;
|
|
246
|
+
} | {
|
|
247
|
+
kind: "workflow_batch_spawned";
|
|
248
|
+
turn: number;
|
|
249
|
+
category?: KernelEventCategory;
|
|
250
|
+
primitive?: KernelPrimitive;
|
|
251
|
+
node_count: number;
|
|
252
|
+
node_ids: string[];
|
|
253
|
+
} | {
|
|
254
|
+
kind: "workflow_completed";
|
|
255
|
+
turn: number;
|
|
256
|
+
category?: KernelEventCategory;
|
|
257
|
+
primitive?: KernelPrimitive;
|
|
258
|
+
completed: string[];
|
|
259
|
+
failed: string[];
|
|
260
|
+
total_nodes: number;
|
|
239
261
|
} | {
|
|
240
262
|
kind: "run_terminal";
|
|
241
263
|
reason: string;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { ProviderReplay, ToolCall } from "../types.js";
|
|
2
2
|
import type { SessionEvent } from "./session-log.js";
|
|
3
3
|
export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a persisted llm_completed event for recovery.
|
|
6
|
+
*
|
|
7
|
+
* Content is sanitized and token_count backfilled, but the stored
|
|
8
|
+
* `provider_replay` envelope is passed through verbatim — this layer is
|
|
9
|
+
* provider-neutral and must never synthesize protocol-specific replay shapes
|
|
10
|
+
* (e.g. Anthropic `native_blocks`). Legacy reconstruction for a given protocol
|
|
11
|
+
* is the responsibility of that provider's `seedProviderReplay`.
|
|
12
|
+
*/
|
|
7
13
|
export declare function normalizeLlmCompleted(event: Extract<SessionEvent, {
|
|
8
14
|
kind: "llm_completed";
|
|
9
15
|
}>, maxBytes?: number): Extract<SessionEvent, {
|
|
@@ -35,3 +41,20 @@ export declare function buildRunTerminalEvent(input: {
|
|
|
35
41
|
}): Extract<SessionEvent, {
|
|
36
42
|
kind: "run_terminal";
|
|
37
43
|
}>;
|
|
44
|
+
/** Build workflow_node_completed for persistence after a node finishes. */
|
|
45
|
+
export declare function buildWorkflowNodeCompletedEvent(input: {
|
|
46
|
+
turn: number;
|
|
47
|
+
agentId: string;
|
|
48
|
+
termination: string;
|
|
49
|
+
}): Extract<SessionEvent, {
|
|
50
|
+
kind: "workflow_node_completed";
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Recover completed workflow node agent_ids from a session event stream.
|
|
54
|
+
* Scans for workflow_node_completed events and returns the agent_ids whose
|
|
55
|
+
* termination was "completed". Used to rebuild resumedCompleted for resumeWorkflow.
|
|
56
|
+
*/
|
|
57
|
+
export declare function recoverCompletedWorkflowNodes(events: Array<{
|
|
58
|
+
seq: number;
|
|
59
|
+
event: SessionEvent;
|
|
60
|
+
}>): string[];
|
|
@@ -3,41 +3,19 @@ export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay
|
|
|
3
3
|
function estimateTokenCount(text) {
|
|
4
4
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export function synthesizeProviderReplay(content, toolCalls) {
|
|
16
|
-
if (!toolCalls.length)
|
|
17
|
-
return undefined;
|
|
18
|
-
const blocks = [];
|
|
19
|
-
if (content)
|
|
20
|
-
blocks.push({ type: "text", text: content });
|
|
21
|
-
for (const tc of toolCalls) {
|
|
22
|
-
blocks.push({
|
|
23
|
-
type: "tool_use",
|
|
24
|
-
id: tc.id,
|
|
25
|
-
name: tc.name,
|
|
26
|
-
input: parseToolInput(tc.arguments),
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
return { native_blocks: blocks };
|
|
30
|
-
}
|
|
31
|
-
export function effectiveProviderReplay(content, toolCalls, stored) {
|
|
32
|
-
if (stored?.native_blocks?.length || stored?.reasoning_content != null) {
|
|
33
|
-
return stored;
|
|
34
|
-
}
|
|
35
|
-
return synthesizeProviderReplay(content, toolCalls);
|
|
36
|
-
}
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a persisted llm_completed event for recovery.
|
|
8
|
+
*
|
|
9
|
+
* Content is sanitized and token_count backfilled, but the stored
|
|
10
|
+
* `provider_replay` envelope is passed through verbatim — this layer is
|
|
11
|
+
* provider-neutral and must never synthesize protocol-specific replay shapes
|
|
12
|
+
* (e.g. Anthropic `native_blocks`). Legacy reconstruction for a given protocol
|
|
13
|
+
* is the responsibility of that provider's `seedProviderReplay`.
|
|
14
|
+
*/
|
|
37
15
|
export function normalizeLlmCompleted(event, maxBytes) {
|
|
38
16
|
const content = sanitizeReplayText(event.content ?? "", maxBytes);
|
|
39
17
|
const toolCalls = event.tool_calls ?? [];
|
|
40
|
-
const providerReplay =
|
|
18
|
+
const providerReplay = event.provider_replay;
|
|
41
19
|
return {
|
|
42
20
|
kind: "llm_completed",
|
|
43
21
|
turn: event.turn,
|
|
@@ -75,3 +53,26 @@ export function buildRunTerminalEvent(input) {
|
|
|
75
53
|
total_tokens: Math.max(0, input.totalTokens),
|
|
76
54
|
};
|
|
77
55
|
}
|
|
56
|
+
/** Build workflow_node_completed for persistence after a node finishes. */
|
|
57
|
+
export function buildWorkflowNodeCompletedEvent(input) {
|
|
58
|
+
return {
|
|
59
|
+
kind: "workflow_node_completed",
|
|
60
|
+
turn: input.turn,
|
|
61
|
+
agent_id: input.agentId,
|
|
62
|
+
termination: input.termination,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Recover completed workflow node agent_ids from a session event stream.
|
|
67
|
+
* Scans for workflow_node_completed events and returns the agent_ids whose
|
|
68
|
+
* termination was "completed". Used to rebuild resumedCompleted for resumeWorkflow.
|
|
69
|
+
*/
|
|
70
|
+
export function recoverCompletedWorkflowNodes(events) {
|
|
71
|
+
const completed = [];
|
|
72
|
+
for (const { event } of events) {
|
|
73
|
+
if (event.kind === "workflow_node_completed" && event.termination === "completed") {
|
|
74
|
+
completed.push(event.agent_id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return completed;
|
|
78
|
+
}
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -75,3 +75,53 @@ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResul
|
|
|
75
75
|
export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
|
|
76
76
|
export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
|
|
77
77
|
export declare function milestoneCheckFail(phaseId: string, reason: string): MilestoneCheckResult;
|
|
78
|
+
/** A task for a workflow node: a full object, or a bare goal string. */
|
|
79
|
+
export type WorkflowTaskSpec = {
|
|
80
|
+
goal: string;
|
|
81
|
+
criteria?: string[];
|
|
82
|
+
lane?: string;
|
|
83
|
+
} | string;
|
|
84
|
+
/** W3 trust level for a workflow node. */
|
|
85
|
+
export type NodeTrust = "trusted" | "quarantined";
|
|
86
|
+
/** One node in a declarative workflow DAG (camelCase host shape). */
|
|
87
|
+
export interface WorkflowNodeSpec {
|
|
88
|
+
task: WorkflowTaskSpec;
|
|
89
|
+
role: KernelAgentRole;
|
|
90
|
+
isolation?: AgentIsolation;
|
|
91
|
+
contextInheritance?: ContextInheritance;
|
|
92
|
+
modelHint?: string;
|
|
93
|
+
/** W3: `quarantined` nodes read untrusted content and must run without privileges. */
|
|
94
|
+
trust?: NodeTrust;
|
|
95
|
+
/** Indices of nodes this node depends on. */
|
|
96
|
+
dependsOn?: number[];
|
|
97
|
+
}
|
|
98
|
+
/** A declarative workflow DAG the kernel runs node-by-node, gating each spawn. */
|
|
99
|
+
export interface WorkflowSpec {
|
|
100
|
+
nodes: WorkflowNodeSpec[];
|
|
101
|
+
}
|
|
102
|
+
/** Per-node spawn descriptor carried in the `workflow_batch_spawned` observation. */
|
|
103
|
+
export interface WorkflowSpawnInfo {
|
|
104
|
+
agent_id: string;
|
|
105
|
+
goal: string;
|
|
106
|
+
role: string;
|
|
107
|
+
isolation: string;
|
|
108
|
+
context_inheritance: string;
|
|
109
|
+
model_hint?: string;
|
|
110
|
+
/** W3 trust level: `"trusted"` | `"quarantined"`. */
|
|
111
|
+
trust?: string;
|
|
112
|
+
}
|
|
113
|
+
/** Map a host `WorkflowSpec` to the snake_case kernel JSON (`load_workflow.spec`). */
|
|
114
|
+
export declare function workflowSpecToKernel(spec: WorkflowSpec): Record<string, unknown>;
|
|
115
|
+
/** Build a sub-agent run spec for a kernel-generated workflow node. */
|
|
116
|
+
export declare function workflowNodeToSpec(node: WorkflowSpawnInfo, parentSessionId: string): AgentRunSpec;
|
|
117
|
+
/** Build the host manifest for a kernel-generated workflow node. */
|
|
118
|
+
export declare function workflowNodeToManifest(node: WorkflowSpawnInfo, parentSessionId: string): AgentProcessChangedObservation;
|
|
119
|
+
/** N parallel read-only Explore workers feeding a single Plan synthesizer (barrier). */
|
|
120
|
+
export declare function fanoutSynthesize(workers: WorkflowTaskSpec[], synthesize: WorkflowTaskSpec): WorkflowSpec;
|
|
121
|
+
/** N parallel Implement generators feeding a single Verify filter/dedupe step (barrier). */
|
|
122
|
+
export declare function generateAndFilter(generators: WorkflowTaskSpec[], filter: WorkflowTaskSpec): WorkflowSpec;
|
|
123
|
+
/**
|
|
124
|
+
* One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
|
|
125
|
+
* re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
|
|
126
|
+
*/
|
|
127
|
+
export declare function verifyRules(rules: WorkflowTaskSpec[], skeptic?: WorkflowTaskSpec): WorkflowSpec;
|
package/dist/types/agent.js
CHANGED
|
@@ -94,3 +94,113 @@ export function milestoneCheckPass(phaseId) {
|
|
|
94
94
|
export function milestoneCheckFail(phaseId, reason) {
|
|
95
95
|
return { phaseId, passed: false, reason };
|
|
96
96
|
}
|
|
97
|
+
/** Map a host `WorkflowSpec` to the snake_case kernel JSON (`load_workflow.spec`). */
|
|
98
|
+
export function workflowSpecToKernel(spec) {
|
|
99
|
+
return {
|
|
100
|
+
nodes: spec.nodes.map(n => {
|
|
101
|
+
const task = typeof n.task === "string" ? { goal: n.task } : n.task;
|
|
102
|
+
return {
|
|
103
|
+
task: {
|
|
104
|
+
goal: task.goal,
|
|
105
|
+
// `criteria` is required by the kernel's RuntimeTask serde (no default).
|
|
106
|
+
criteria: task.criteria ?? [],
|
|
107
|
+
...(task.lane ? { lane: task.lane } : {}),
|
|
108
|
+
},
|
|
109
|
+
role: n.role,
|
|
110
|
+
// role/isolation/context_inheritance have no serde default in the kernel — always emit.
|
|
111
|
+
isolation: n.isolation ?? "shared",
|
|
112
|
+
context_inheritance: n.contextInheritance ?? "none",
|
|
113
|
+
...(n.modelHint ? { model_hint: n.modelHint } : {}),
|
|
114
|
+
...(n.trust && n.trust !== "trusted" ? { trust: n.trust } : {}),
|
|
115
|
+
...(n.dependsOn && n.dependsOn.length ? { depends_on: n.dependsOn } : {}),
|
|
116
|
+
};
|
|
117
|
+
}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Build a sub-agent run spec for a kernel-generated workflow node. */
|
|
121
|
+
export function workflowNodeToSpec(node, parentSessionId) {
|
|
122
|
+
return {
|
|
123
|
+
identity: {
|
|
124
|
+
agentId: node.agent_id,
|
|
125
|
+
sessionId: `${parentSessionId}-${node.agent_id}`,
|
|
126
|
+
isSubAgent: true,
|
|
127
|
+
parentSessionId,
|
|
128
|
+
},
|
|
129
|
+
role: node.role,
|
|
130
|
+
isolation: node.isolation,
|
|
131
|
+
goal: node.goal,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Build the host manifest for a kernel-generated workflow node. */
|
|
135
|
+
export function workflowNodeToManifest(node, parentSessionId) {
|
|
136
|
+
return {
|
|
137
|
+
kind: "agent_process_changed",
|
|
138
|
+
agent_id: node.agent_id,
|
|
139
|
+
parent_session_id: parentSessionId,
|
|
140
|
+
role: node.role,
|
|
141
|
+
isolation: node.isolation,
|
|
142
|
+
context_inheritance: node.context_inheritance,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// ─── W1/W2 workflow templates (the six patterns as one-liners) ───
|
|
146
|
+
// Roles carry the kernel's role_defaults isolation/inheritance so host-built specs match the
|
|
147
|
+
// core `orchestration::workflow` constructors (e.g. verifiers stay bias-resistant).
|
|
148
|
+
function asTask(t) {
|
|
149
|
+
return typeof t === "string" ? { goal: t } : t;
|
|
150
|
+
}
|
|
151
|
+
/** N parallel read-only Explore workers feeding a single Plan synthesizer (barrier). */
|
|
152
|
+
export function fanoutSynthesize(workers, synthesize) {
|
|
153
|
+
const nodes = workers.map(t => ({
|
|
154
|
+
task: asTask(t),
|
|
155
|
+
role: "explore",
|
|
156
|
+
isolation: "read_only",
|
|
157
|
+
contextInheritance: "system_only",
|
|
158
|
+
}));
|
|
159
|
+
nodes.push({
|
|
160
|
+
task: asTask(synthesize),
|
|
161
|
+
role: "plan",
|
|
162
|
+
isolation: "shared",
|
|
163
|
+
contextInheritance: "full",
|
|
164
|
+
dependsOn: workers.map((_, i) => i),
|
|
165
|
+
});
|
|
166
|
+
return { nodes };
|
|
167
|
+
}
|
|
168
|
+
/** N parallel Implement generators feeding a single Verify filter/dedupe step (barrier). */
|
|
169
|
+
export function generateAndFilter(generators, filter) {
|
|
170
|
+
const nodes = generators.map(t => ({
|
|
171
|
+
task: asTask(t),
|
|
172
|
+
role: "implement",
|
|
173
|
+
isolation: "worktree",
|
|
174
|
+
contextInheritance: "full",
|
|
175
|
+
}));
|
|
176
|
+
nodes.push({
|
|
177
|
+
task: asTask(filter),
|
|
178
|
+
role: "verify",
|
|
179
|
+
isolation: "read_only",
|
|
180
|
+
contextInheritance: "none",
|
|
181
|
+
dependsOn: generators.map((_, i) => i),
|
|
182
|
+
});
|
|
183
|
+
return { nodes };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
|
|
187
|
+
* re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
|
|
188
|
+
*/
|
|
189
|
+
export function verifyRules(rules, skeptic) {
|
|
190
|
+
const nodes = rules.map(t => ({
|
|
191
|
+
task: asTask(t),
|
|
192
|
+
role: "verify",
|
|
193
|
+
isolation: "read_only",
|
|
194
|
+
contextInheritance: "none",
|
|
195
|
+
}));
|
|
196
|
+
if (skeptic !== undefined) {
|
|
197
|
+
nodes.push({
|
|
198
|
+
task: asTask(skeptic),
|
|
199
|
+
role: "verify",
|
|
200
|
+
isolation: "read_only",
|
|
201
|
+
contextInheritance: "none",
|
|
202
|
+
dependsOn: rules.map((_, i) => i),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return { nodes };
|
|
206
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -184,12 +184,41 @@ export interface RetryConfig {
|
|
|
184
184
|
* Responses `previous_response_id` without leaking those semantics into the kernel.
|
|
185
185
|
*/
|
|
186
186
|
export type ProviderRunState = Record<string, unknown>;
|
|
187
|
+
export type ProviderProtocol = "anthropic-messages" | "openai-chat" | "openai-responses" | "gemini";
|
|
188
|
+
export interface ProviderDescriptor {
|
|
189
|
+
provider: string;
|
|
190
|
+
protocol: ProviderProtocol;
|
|
191
|
+
model: string;
|
|
192
|
+
reasoning: {
|
|
193
|
+
supported: boolean;
|
|
194
|
+
preserveAcrossToolTurns: boolean;
|
|
195
|
+
requiresReplayForToolTurns?: boolean;
|
|
196
|
+
};
|
|
197
|
+
toolCalls: {
|
|
198
|
+
supported: boolean;
|
|
199
|
+
requiresStrictPairing: boolean;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
187
202
|
/** Provider-native fields required to replay a turn across requests (thinking blocks, reasoning_content, etc.). */
|
|
188
203
|
export interface ProviderReplay {
|
|
204
|
+
schema_version?: 1 | 2;
|
|
205
|
+
provider?: string;
|
|
206
|
+
protocol?: ProviderProtocol;
|
|
207
|
+
model?: string;
|
|
189
208
|
/** Anthropic-style assistant content blocks (thinking, text, tool_use). */
|
|
190
209
|
native_blocks?: Array<Record<string, unknown>>;
|
|
191
210
|
/** OpenAI-compatible reasoning field (DeepSeek, etc.). */
|
|
192
211
|
reasoning_content?: string;
|
|
212
|
+
reasoning_details?: unknown;
|
|
213
|
+
native_message?: unknown;
|
|
214
|
+
tool_calls?: unknown[];
|
|
215
|
+
}
|
|
216
|
+
/** Result of a pre-flight reasoning-replay assessment for a target provider. */
|
|
217
|
+
export interface ReplayabilityAssessment {
|
|
218
|
+
/** True when every reasoning-requiring tool-call turn has replay available. */
|
|
219
|
+
ok: boolean;
|
|
220
|
+
/** Tool-call ids whose turn lacks the required non-empty reasoning replay. */
|
|
221
|
+
offendingCallIds: string[];
|
|
193
222
|
}
|
|
194
223
|
/** Structured render output produced by the kernel for each LLM call. */
|
|
195
224
|
export interface RenderedContext {
|
|
@@ -214,6 +243,7 @@ export interface RuntimePolicy {
|
|
|
214
243
|
}
|
|
215
244
|
export interface LLMProvider {
|
|
216
245
|
createRunState?(): ProviderRunState;
|
|
246
|
+
descriptor?(): ProviderDescriptor;
|
|
217
247
|
/**
|
|
218
248
|
* Optional: return the recommended runtime policy for this provider's model.
|
|
219
249
|
* RuntimeRunner uses this as a fallback when the caller has not specified
|
|
@@ -224,6 +254,14 @@ export interface LLMProvider {
|
|
|
224
254
|
peekProviderReplay?(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
225
255
|
/** Restore provider-native replay fields when rebuilding history from SessionLog. */
|
|
226
256
|
seedProviderReplay?(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
257
|
+
/**
|
|
258
|
+
* Pre-flight query: would this history validate against this provider with the
|
|
259
|
+
* given extensions, without sending the request? Returns the tool-call ids
|
|
260
|
+
* whose turn lacks the reasoning replay this provider requires, so an embedder
|
|
261
|
+
* can route around the failure (keep thinking on, disable it, or skip this
|
|
262
|
+
* candidate) before issuing the request. Seed any persisted replay first.
|
|
263
|
+
*/
|
|
264
|
+
assessReplayability?(context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
227
265
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
228
266
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
|
|
229
267
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
23
|
-
"@deepstrike/core": "0.2.
|
|
23
|
+
"@deepstrike/core": "0.2.8",
|
|
24
24
|
"@google/generative-ai": "^0.24.1",
|
|
25
25
|
"openai": "^5.23.2"
|
|
26
26
|
},
|