@deepstrike/sdk 0.2.35 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/os/public.d.ts +1 -1
- package/dist/os/public.js +1 -1
- package/dist/runtime/facade.js +11 -11
- package/dist/runtime/kernel-event-log.d.ts +0 -6
- package/dist/runtime/kernel-event-log.js +38 -62
- package/dist/runtime/kernel-step.d.ts +11 -0
- package/dist/runtime/kernel-step.js +12 -0
- package/dist/runtime/large-result-spool.d.ts +7 -0
- package/dist/runtime/large-result-spool.js +25 -0
- package/dist/runtime/loop-driver.d.ts +108 -0
- package/dist/runtime/loop-driver.js +198 -0
- package/dist/runtime/os-snapshot.d.ts +0 -1
- package/dist/runtime/os-snapshot.js +0 -19
- package/dist/runtime/reactive-session.d.ts +5 -2
- package/dist/runtime/reactive-session.js +17 -4
- package/dist/runtime/run-group.d.ts +9 -0
- package/dist/runtime/run-group.js +18 -4
- package/dist/runtime/runner.d.ts +151 -12
- package/dist/runtime/runner.js +568 -155
- package/dist/runtime/session-log.d.ts +31 -54
- package/dist/runtime/session-repair.d.ts +29 -7
- package/dist/runtime/session-repair.js +37 -9
- package/dist/runtime/sub-agent-orchestrator.d.ts +12 -0
- package/dist/runtime/sub-agent-orchestrator.js +54 -30
- package/dist/runtime/workflow-control-flow.d.ts +10 -2
- package/dist/runtime/workflow-control-flow.js +27 -6
- package/dist/signals/gateway.d.ts +4 -2
- package/dist/signals/gateway.js +8 -1
- package/dist/types/agent.d.ts +40 -2
- package/dist/types/agent.js +25 -1
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { KernelPrimitive } from "./kernel-event-log.js";
|
|
1
2
|
import type { ContentPart, ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
|
|
2
|
-
import type { KernelEventCategory, KernelPrimitive } from "./kernel-event-log.js";
|
|
3
3
|
export type RollbackReason = {
|
|
4
4
|
kind: "fatal_tool_error";
|
|
5
5
|
tool_name: string;
|
|
@@ -75,8 +75,6 @@ export type SessionEvent = {
|
|
|
75
75
|
} | {
|
|
76
76
|
kind: "compressed";
|
|
77
77
|
turn: number;
|
|
78
|
-
category?: KernelEventCategory;
|
|
79
|
-
primitive?: KernelPrimitive;
|
|
80
78
|
archived_seq_range: [number, number];
|
|
81
79
|
action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
|
|
82
80
|
summary?: string;
|
|
@@ -86,8 +84,6 @@ export type SessionEvent = {
|
|
|
86
84
|
} | {
|
|
87
85
|
kind: "page_out";
|
|
88
86
|
turn: number;
|
|
89
|
-
category?: KernelEventCategory;
|
|
90
|
-
primitive?: KernelPrimitive;
|
|
91
87
|
action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
|
|
92
88
|
summary?: string;
|
|
93
89
|
tier_hint?: string;
|
|
@@ -95,14 +91,10 @@ export type SessionEvent = {
|
|
|
95
91
|
} | {
|
|
96
92
|
kind: "page_in";
|
|
97
93
|
turn: number;
|
|
98
|
-
category?: KernelEventCategory;
|
|
99
|
-
primitive?: KernelPrimitive;
|
|
100
94
|
entry_count: number;
|
|
101
95
|
} | {
|
|
102
96
|
kind: "large_result_spooled";
|
|
103
97
|
turn: number;
|
|
104
|
-
category?: KernelEventCategory;
|
|
105
|
-
primitive?: KernelPrimitive;
|
|
106
98
|
call_id: string;
|
|
107
99
|
tool: string;
|
|
108
100
|
original_size: number;
|
|
@@ -111,15 +103,11 @@ export type SessionEvent = {
|
|
|
111
103
|
} | {
|
|
112
104
|
kind: "rollbacked";
|
|
113
105
|
turn: number;
|
|
114
|
-
category?: KernelEventCategory;
|
|
115
|
-
primitive?: KernelPrimitive;
|
|
116
106
|
checkpoint_history_len: number;
|
|
117
107
|
reason?: RollbackReason;
|
|
118
108
|
} | {
|
|
119
109
|
kind: "capability_changed";
|
|
120
110
|
turn: number;
|
|
121
|
-
category?: KernelEventCategory;
|
|
122
|
-
primitive?: KernelPrimitive;
|
|
123
111
|
added: string[];
|
|
124
112
|
removed: string[];
|
|
125
113
|
change_kind?: string;
|
|
@@ -130,78 +118,51 @@ export type SessionEvent = {
|
|
|
130
118
|
} | {
|
|
131
119
|
kind: "context_renewed";
|
|
132
120
|
turn: number;
|
|
133
|
-
category?: KernelEventCategory;
|
|
134
|
-
primitive?: KernelPrimitive;
|
|
135
121
|
sprint: number;
|
|
136
122
|
handoff_ref: string;
|
|
137
123
|
} | {
|
|
138
124
|
kind: "suspended";
|
|
139
125
|
turn: number;
|
|
140
|
-
category?: KernelEventCategory;
|
|
141
|
-
primitive?: KernelPrimitive;
|
|
142
126
|
reason: string;
|
|
143
127
|
pending_calls?: string[];
|
|
144
128
|
} | {
|
|
145
129
|
kind: "resumed";
|
|
146
130
|
turn: number;
|
|
147
|
-
category?: KernelEventCategory;
|
|
148
|
-
primitive?: KernelPrimitive;
|
|
149
131
|
approved?: string[];
|
|
150
132
|
denied?: string[];
|
|
151
133
|
} | {
|
|
152
134
|
kind: "tool_gated";
|
|
153
135
|
turn: number;
|
|
154
|
-
category?: KernelEventCategory;
|
|
155
|
-
primitive?: KernelPrimitive;
|
|
156
136
|
call_id: string;
|
|
157
137
|
tool: string;
|
|
158
138
|
reason: string;
|
|
159
139
|
} | {
|
|
160
140
|
kind: "signal_disposed";
|
|
161
141
|
turn: number;
|
|
162
|
-
category?: KernelEventCategory;
|
|
163
|
-
primitive?: KernelPrimitive;
|
|
164
142
|
signal_id: string;
|
|
165
143
|
disposition: string;
|
|
166
144
|
queue_depth: number;
|
|
167
145
|
} | {
|
|
168
146
|
kind: "budget_exceeded";
|
|
169
147
|
turn: number;
|
|
170
|
-
category?: KernelEventCategory;
|
|
171
|
-
primitive?: KernelPrimitive;
|
|
172
148
|
budget: string;
|
|
173
149
|
} | {
|
|
174
150
|
kind: "milestone_advanced";
|
|
175
151
|
turn: number;
|
|
176
|
-
category?: KernelEventCategory;
|
|
177
|
-
primitive?: KernelPrimitive;
|
|
178
152
|
phase_id: string;
|
|
179
153
|
capabilities_unlocked: string[];
|
|
180
154
|
} | {
|
|
181
155
|
kind: "milestone_blocked";
|
|
182
156
|
turn: number;
|
|
183
|
-
category?: KernelEventCategory;
|
|
184
|
-
primitive?: KernelPrimitive;
|
|
185
157
|
phase_id: string;
|
|
186
158
|
reason: string;
|
|
187
|
-
} | {
|
|
188
|
-
kind: "milestone_evidence";
|
|
189
|
-
turn: number;
|
|
190
|
-
category?: KernelEventCategory;
|
|
191
|
-
primitive?: KernelPrimitive;
|
|
192
|
-
phase_id: string;
|
|
193
|
-
evidence: string[];
|
|
194
159
|
} | {
|
|
195
160
|
kind: "checkpoint_taken";
|
|
196
161
|
turn: number;
|
|
197
|
-
category?: KernelEventCategory;
|
|
198
|
-
primitive?: KernelPrimitive;
|
|
199
162
|
history_len: number;
|
|
200
163
|
} | {
|
|
201
164
|
kind: "agent_process_changed";
|
|
202
165
|
turn: number;
|
|
203
|
-
category?: KernelEventCategory;
|
|
204
|
-
primitive?: KernelPrimitive;
|
|
205
166
|
agent_id: string;
|
|
206
167
|
parent_session_id: string;
|
|
207
168
|
role: string;
|
|
@@ -213,24 +174,18 @@ export type SessionEvent = {
|
|
|
213
174
|
} | {
|
|
214
175
|
kind: "memory_written";
|
|
215
176
|
turn: number;
|
|
216
|
-
category?: KernelEventCategory;
|
|
217
|
-
primitive?: KernelPrimitive;
|
|
218
177
|
memory_id: string;
|
|
219
178
|
memory_kind: string;
|
|
220
179
|
size_bytes: number;
|
|
221
180
|
} | {
|
|
222
181
|
kind: "memory_queried";
|
|
223
182
|
turn: number;
|
|
224
|
-
category?: KernelEventCategory;
|
|
225
|
-
primitive?: KernelPrimitive;
|
|
226
183
|
query_context: string;
|
|
227
184
|
requested_k: number;
|
|
228
185
|
requires_async_response: boolean;
|
|
229
186
|
} | {
|
|
230
187
|
kind: "memory_validation_failed";
|
|
231
188
|
turn: number;
|
|
232
|
-
category?: KernelEventCategory;
|
|
233
|
-
primitive?: KernelPrimitive;
|
|
234
189
|
memory_id: string;
|
|
235
190
|
error: string;
|
|
236
191
|
} | {
|
|
@@ -240,29 +195,35 @@ export type SessionEvent = {
|
|
|
240
195
|
} | {
|
|
241
196
|
kind: "workflow_node_completed";
|
|
242
197
|
turn: number;
|
|
243
|
-
category?: KernelEventCategory;
|
|
244
|
-
primitive?: KernelPrimitive;
|
|
245
198
|
agent_id: string;
|
|
246
199
|
termination: string;
|
|
200
|
+
/** W-1: result-borne control signals, persisted so resume replays control flow faithfully —
|
|
201
|
+
* a classifier re-prunes its rejected branches, a recorded loop stop is honored. */
|
|
202
|
+
classify_branch?: string;
|
|
203
|
+
tournament_winner?: string;
|
|
204
|
+
loop_continue?: boolean;
|
|
205
|
+
/** W-1: the node's final output text — resume re-seeds the driver's outputs map from it so
|
|
206
|
+
* post-resume reduce/judge/dependent nodes still see their dependencies' outputs. */
|
|
207
|
+
output?: string;
|
|
247
208
|
} | {
|
|
248
209
|
kind: "workflow_nodes_submitted";
|
|
249
210
|
turn: number;
|
|
250
|
-
category?: KernelEventCategory;
|
|
251
|
-
primitive?: KernelPrimitive;
|
|
252
211
|
/** Kernel-shape (snake_case) submitted node specs — persisted so resume can re-apply them. */
|
|
253
212
|
nodes: Record<string, unknown>[];
|
|
213
|
+
/** R3-1: graph base index the batch was appended at (from the kernel's
|
|
214
|
+
* WorkflowNodesSubmitted observation) — lets resume rebuild exact indices. */
|
|
215
|
+
base_index?: number;
|
|
216
|
+
/** W-N3: the submitting node's agent id (absent = host/bootstrap). Resume DROPS batches whose
|
|
217
|
+
* submitter re-runs — it will re-submit — instead of duplicating their nodes. */
|
|
218
|
+
submitter_agent_id?: string;
|
|
254
219
|
} | {
|
|
255
220
|
kind: "workflow_batch_spawned";
|
|
256
221
|
turn: number;
|
|
257
|
-
category?: KernelEventCategory;
|
|
258
|
-
primitive?: KernelPrimitive;
|
|
259
222
|
node_count: number;
|
|
260
223
|
node_ids: string[];
|
|
261
224
|
} | {
|
|
262
225
|
kind: "workflow_completed";
|
|
263
226
|
turn: number;
|
|
264
|
-
category?: KernelEventCategory;
|
|
265
|
-
primitive?: KernelPrimitive;
|
|
266
227
|
completed: string[];
|
|
267
228
|
failed: string[];
|
|
268
229
|
total_nodes: number;
|
|
@@ -279,10 +240,26 @@ export type SessionEvent = {
|
|
|
279
240
|
kind: "group_member_joined";
|
|
280
241
|
session_id: string;
|
|
281
242
|
role?: string;
|
|
243
|
+
member_kind?: "peer" | "vehicle";
|
|
282
244
|
} | {
|
|
283
245
|
kind: "group_budget_charged";
|
|
284
246
|
tokens: number;
|
|
285
247
|
subagents: number;
|
|
248
|
+
rounds?: number;
|
|
249
|
+
} | {
|
|
250
|
+
kind: "round_started";
|
|
251
|
+
/** 1-based round number within the loop. */
|
|
252
|
+
round: number;
|
|
253
|
+
goal: string;
|
|
254
|
+
} | {
|
|
255
|
+
kind: "round_paced";
|
|
256
|
+
round: number;
|
|
257
|
+
action: "continue" | "sleep" | "stop";
|
|
258
|
+
delay_ms?: number;
|
|
259
|
+
/** Absolute wake time for sleep — lets a stateless host re-arm from the log alone. */
|
|
260
|
+
wake_at_ms?: number;
|
|
261
|
+
reason: string;
|
|
262
|
+
coerced_from?: string;
|
|
286
263
|
};
|
|
287
264
|
export interface SessionLog {
|
|
288
265
|
append(sessionId: string, event: SessionEvent): Promise<number>;
|
|
@@ -41,34 +41,56 @@ export declare function buildRunTerminalEvent(input: {
|
|
|
41
41
|
}): Extract<SessionEvent, {
|
|
42
42
|
kind: "run_terminal";
|
|
43
43
|
}>;
|
|
44
|
-
/** Build workflow_node_completed for persistence after a node finishes.
|
|
44
|
+
/** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
|
|
45
|
+
* result-borne control signals + output so resume replays control flow and re-seeds outputs. */
|
|
45
46
|
export declare function buildWorkflowNodeCompletedEvent(input: {
|
|
46
47
|
turn: number;
|
|
47
48
|
agentId: string;
|
|
48
49
|
termination: string;
|
|
50
|
+
classifyBranch?: string;
|
|
51
|
+
tournamentWinner?: string;
|
|
52
|
+
loopContinue?: boolean;
|
|
53
|
+
output?: string;
|
|
49
54
|
}): Extract<SessionEvent, {
|
|
50
55
|
kind: "workflow_node_completed";
|
|
51
56
|
}>;
|
|
57
|
+
/** One recovered node completion: the agent id plus its persisted control signals and output. */
|
|
58
|
+
export interface RecoveredNodeCompletion {
|
|
59
|
+
agentId: string;
|
|
60
|
+
classifyBranch?: string;
|
|
61
|
+
tournamentWinner?: string;
|
|
62
|
+
loopContinue?: boolean;
|
|
63
|
+
output?: string;
|
|
64
|
+
}
|
|
52
65
|
/**
|
|
53
|
-
* Recover completed workflow node
|
|
54
|
-
*
|
|
55
|
-
*
|
|
66
|
+
* Recover completed workflow node records from a session event stream. Scans for
|
|
67
|
+
* workflow_node_completed events with termination "completed" and returns them WITH their
|
|
68
|
+
* result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
|
|
69
|
+
* `resumed_results` so a classifier re-prunes and a loop stop is honored, and re-seeds the
|
|
70
|
+
* driver's outputs map from the persisted output text.
|
|
56
71
|
*/
|
|
57
72
|
export declare function recoverCompletedWorkflowNodes(events: Array<{
|
|
58
73
|
seq: number;
|
|
59
74
|
event: SessionEvent;
|
|
60
|
-
}>):
|
|
75
|
+
}>): RecoveredNodeCompletion[];
|
|
61
76
|
/** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
|
|
62
77
|
* re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
|
|
63
78
|
export declare function buildWorkflowNodesSubmittedEvent(input: {
|
|
64
79
|
turn: number;
|
|
65
80
|
nodes: Record<string, unknown>[];
|
|
81
|
+
baseIndex?: number;
|
|
82
|
+
submitterAgentId?: string;
|
|
66
83
|
}): Extract<SessionEvent, {
|
|
67
84
|
kind: "workflow_nodes_submitted";
|
|
68
85
|
}>;
|
|
69
86
|
/** R3-1: recover the runtime submission batches (in order) from a session event stream, to rebuild
|
|
70
|
-
* `resumed_submissions` for resumeWorkflow so dynamically-appended nodes are reconstructed.
|
|
87
|
+
* `resumed_submissions` for resumeWorkflow so dynamically-appended nodes are reconstructed.
|
|
88
|
+
* `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
|
|
71
89
|
export declare function recoverSubmittedWorkflowNodes(events: Array<{
|
|
72
90
|
seq: number;
|
|
73
91
|
event: SessionEvent;
|
|
74
|
-
}>):
|
|
92
|
+
}>): {
|
|
93
|
+
submissions: Record<string, unknown>[][];
|
|
94
|
+
bases: number[];
|
|
95
|
+
submitters: Array<string | undefined>;
|
|
96
|
+
};
|
|
@@ -53,25 +53,38 @@ export function buildRunTerminalEvent(input) {
|
|
|
53
53
|
total_tokens: Math.max(0, input.totalTokens),
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
-
/** Build workflow_node_completed for persistence after a node finishes.
|
|
56
|
+
/** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
|
|
57
|
+
* result-borne control signals + output so resume replays control flow and re-seeds outputs. */
|
|
57
58
|
export function buildWorkflowNodeCompletedEvent(input) {
|
|
58
59
|
return {
|
|
59
60
|
kind: "workflow_node_completed",
|
|
60
61
|
turn: input.turn,
|
|
61
62
|
agent_id: input.agentId,
|
|
62
63
|
termination: input.termination,
|
|
64
|
+
...(input.classifyBranch !== undefined ? { classify_branch: input.classifyBranch } : {}),
|
|
65
|
+
...(input.tournamentWinner !== undefined ? { tournament_winner: input.tournamentWinner } : {}),
|
|
66
|
+
...(input.loopContinue !== undefined ? { loop_continue: input.loopContinue } : {}),
|
|
67
|
+
...(input.output ? { output: input.output } : {}),
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
66
|
-
* Recover completed workflow node
|
|
67
|
-
*
|
|
68
|
-
*
|
|
71
|
+
* Recover completed workflow node records from a session event stream. Scans for
|
|
72
|
+
* workflow_node_completed events with termination "completed" and returns them WITH their
|
|
73
|
+
* result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
|
|
74
|
+
* `resumed_results` so a classifier re-prunes and a loop stop is honored, and re-seeds the
|
|
75
|
+
* driver's outputs map from the persisted output text.
|
|
69
76
|
*/
|
|
70
77
|
export function recoverCompletedWorkflowNodes(events) {
|
|
71
78
|
const completed = [];
|
|
72
79
|
for (const { event } of events) {
|
|
73
80
|
if (event.kind === "workflow_node_completed" && event.termination === "completed") {
|
|
74
|
-
completed.push(
|
|
81
|
+
completed.push({
|
|
82
|
+
agentId: event.agent_id,
|
|
83
|
+
...(event.classify_branch !== undefined ? { classifyBranch: event.classify_branch } : {}),
|
|
84
|
+
...(event.tournament_winner !== undefined ? { tournamentWinner: event.tournament_winner } : {}),
|
|
85
|
+
...(event.loop_continue !== undefined ? { loopContinue: event.loop_continue } : {}),
|
|
86
|
+
...(event.output !== undefined ? { output: event.output } : {}),
|
|
87
|
+
});
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
return completed;
|
|
@@ -79,15 +92,30 @@ export function recoverCompletedWorkflowNodes(events) {
|
|
|
79
92
|
/** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
|
|
80
93
|
* re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
|
|
81
94
|
export function buildWorkflowNodesSubmittedEvent(input) {
|
|
82
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
kind: "workflow_nodes_submitted",
|
|
97
|
+
turn: input.turn,
|
|
98
|
+
nodes: input.nodes,
|
|
99
|
+
...(input.baseIndex !== undefined ? { base_index: input.baseIndex } : {}),
|
|
100
|
+
...(input.submitterAgentId !== undefined ? { submitter_agent_id: input.submitterAgentId } : {}),
|
|
101
|
+
};
|
|
83
102
|
}
|
|
84
103
|
/** R3-1: recover the runtime submission batches (in order) from a session event stream, to rebuild
|
|
85
|
-
* `resumed_submissions` for resumeWorkflow so dynamically-appended nodes are reconstructed.
|
|
104
|
+
* `resumed_submissions` for resumeWorkflow so dynamically-appended nodes are reconstructed.
|
|
105
|
+
* `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
|
|
86
106
|
export function recoverSubmittedWorkflowNodes(events) {
|
|
87
107
|
const submissions = [];
|
|
108
|
+
const bases = [];
|
|
109
|
+
const submitters = [];
|
|
88
110
|
for (const { event } of events) {
|
|
89
|
-
if (event.kind === "workflow_nodes_submitted")
|
|
111
|
+
if (event.kind === "workflow_nodes_submitted") {
|
|
90
112
|
submissions.push(event.nodes);
|
|
113
|
+
submitters.push(event.submitter_agent_id);
|
|
114
|
+
// Absent on legacy logs → order-only replay (bases array stays parallel-short only
|
|
115
|
+
// if ALL records carry it; a mixed log degrades to order-only for safety).
|
|
116
|
+
if (event.base_index !== undefined)
|
|
117
|
+
bases.push(event.base_index);
|
|
118
|
+
}
|
|
91
119
|
}
|
|
92
|
-
return submissions;
|
|
120
|
+
return { submissions, bases: bases.length === submissions.length ? bases : [], submitters };
|
|
93
121
|
}
|
|
@@ -16,6 +16,13 @@ export interface SubAgentRunContext {
|
|
|
16
16
|
* the child runner so a nested `start_workflow` FLATTENS to the parent kernel rather than
|
|
17
17
|
* auto-pivoting into its own bootstrap (which would fragment the one-kernel/one-quota governance). */
|
|
18
18
|
isWorkflowNode?: boolean;
|
|
19
|
+
/** W-N1 tool exposure. The kernel omits an EMPTY `permitted_capability_ids` on the wire, so a
|
|
20
|
+
* grant-less workflow node is indistinguishable from a zero-cap spawn at the manifest — the
|
|
21
|
+
* caller states intent instead: `"inherit"` = run on the parent's execution plane with its
|
|
22
|
+
* meta-tool availability (trusted workflow nodes — they carried no grant list by design, and
|
|
23
|
+
* filtering on the missing list ran every DAG node TOOL-LESS); `"filtered"` (default) = filter
|
|
24
|
+
* to the manifest grants, empty ⇒ deny-all (spawn path, quarantined nodes). */
|
|
25
|
+
toolAccess?: "inherit" | "filtered";
|
|
19
26
|
/** #2-B-ii: parent-controlled abort. When this fires (the kernel preempted this node via
|
|
20
27
|
* `InterruptNow` → `AgentPreempted`), the orchestrator interrupts the child runner, cancelling its
|
|
21
28
|
* in-flight LLM call. */
|
|
@@ -26,6 +33,11 @@ export interface SubAgentRunContext {
|
|
|
26
33
|
export declare function resolveProvider(opts: RuntimeOptions, modelHint?: string): RuntimeOptions["provider"];
|
|
27
34
|
/** Host-side driver for kernel-isolated sub-agent runs. */
|
|
28
35
|
export declare class SubAgentOrchestrator {
|
|
36
|
+
/** W-N4: the ONE child-runner construction shared by the direct-stream and harness paths — tool
|
|
37
|
+
* grant resolution (W-N1), worktree wrapping, inherited system prompt / events, per-node caps.
|
|
38
|
+
* The two used to be near-identical copies that had already drifted (the harness path silently
|
|
39
|
+
* dropped context inheritance). */
|
|
40
|
+
private buildChild;
|
|
29
41
|
stream(ctx: SubAgentRunContext): AsyncIterable<StreamEvent>;
|
|
30
42
|
run(ctx: SubAgentRunContext): Promise<SubAgentResult>;
|
|
31
43
|
}
|
|
@@ -10,7 +10,9 @@ function terminationFromStatus(status) {
|
|
|
10
10
|
normalized === "timeout" ||
|
|
11
11
|
normalized === "user_abort" ||
|
|
12
12
|
normalized === "error" ||
|
|
13
|
-
normalized === "milestone_exceeded"
|
|
13
|
+
normalized === "milestone_exceeded" ||
|
|
14
|
+
normalized === "context_overflow" ||
|
|
15
|
+
normalized === "no_progress") {
|
|
14
16
|
return normalized;
|
|
15
17
|
}
|
|
16
18
|
return status;
|
|
@@ -60,14 +62,38 @@ function deriveMetaTools(permitted, opts) {
|
|
|
60
62
|
metaTools.add("update_plan");
|
|
61
63
|
return metaTools;
|
|
62
64
|
}
|
|
65
|
+
/** W-N1: meta-tools by source availability alone — a `toolAccess: "inherit"` child gets the same
|
|
66
|
+
* meta surface a top-level run of these options would. */
|
|
67
|
+
function availableMetaTools(opts) {
|
|
68
|
+
const metaTools = new Set();
|
|
69
|
+
if (opts.skillDir)
|
|
70
|
+
metaTools.add("skill");
|
|
71
|
+
if (opts.dreamStore)
|
|
72
|
+
metaTools.add("memory");
|
|
73
|
+
if (opts.knowledgeSource)
|
|
74
|
+
metaTools.add("knowledge");
|
|
75
|
+
if (opts.enablePlanTool)
|
|
76
|
+
metaTools.add("update_plan");
|
|
77
|
+
return metaTools;
|
|
78
|
+
}
|
|
63
79
|
/** Host-side driver for kernel-isolated sub-agent runs. */
|
|
64
80
|
export class SubAgentOrchestrator {
|
|
65
|
-
|
|
81
|
+
/** W-N4: the ONE child-runner construction shared by the direct-stream and harness paths — tool
|
|
82
|
+
* grant resolution (W-N1), worktree wrapping, inherited system prompt / events, per-node caps.
|
|
83
|
+
* The two used to be near-identical copies that had already drifted (the harness path silently
|
|
84
|
+
* dropped context inheritance). */
|
|
85
|
+
async buildChild(ctx) {
|
|
86
|
+
// W-N1: "inherit" runs the child on the parent's plane with availability-derived meta-tools
|
|
87
|
+
// (trusted workflow nodes); "filtered" (default) filters to the manifest grants, empty ⇒
|
|
88
|
+
// deny-all (spawn path, quarantined nodes).
|
|
89
|
+
const inherit = ctx.toolAccess === "inherit";
|
|
66
90
|
const permitted = new Set(ctx.manifest.permitted_capability_ids ?? []);
|
|
67
|
-
const metaTools = deriveMetaTools(permitted, ctx.parentOpts);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
const metaTools = inherit ? availableMetaTools(ctx.parentOpts) : deriveMetaTools(permitted, ctx.parentOpts);
|
|
92
|
+
const basePlane = inherit
|
|
93
|
+
? ctx.parentOpts.executionPlane
|
|
94
|
+
: new FilteredExecutionPlane(ctx.parentOpts.executionPlane, permitted, metaTools);
|
|
95
|
+
// M3/G4: a worktree node runs inside its own git worktree (created here, removed by the caller).
|
|
96
|
+
const { plane: execPlane, cleanup: cleanupWorktree } = withWorktree(ctx, basePlane);
|
|
71
97
|
let systemPrompt = ctx.parentOpts.systemPrompt;
|
|
72
98
|
let inheritEvents;
|
|
73
99
|
if (ctx.manifest.context_inheritance === "full") {
|
|
@@ -87,6 +113,9 @@ export class SubAgentOrchestrator {
|
|
|
87
113
|
provider: resolveProvider(ctx.parentOpts, ctx.spec.modelHint),
|
|
88
114
|
// M4/G5: cap the child run at the node's token budget (falls back to the inherited cap).
|
|
89
115
|
maxTotalTokens: ctx.spec.tokenBudget ?? ctx.parentOpts.maxTotalTokens,
|
|
116
|
+
// O3: per-child turn / wall-clock caps (fall back to the inherited limits).
|
|
117
|
+
maxTurns: ctx.spec.maxTurns ?? ctx.parentOpts.maxTurns,
|
|
118
|
+
timeoutMs: ctx.spec.maxWallMs ?? ctx.parentOpts.timeoutMs,
|
|
90
119
|
executionPlane: execPlane,
|
|
91
120
|
agentId: ctx.spec.identity.agentId,
|
|
92
121
|
systemPrompt,
|
|
@@ -97,10 +126,21 @@ export class SubAgentOrchestrator {
|
|
|
97
126
|
enablePlanTool: metaTools.has("update_plan") ? ctx.parentOpts.enablePlanTool : undefined,
|
|
98
127
|
// M5 v2.1: a workflow node's `start_workflow` flattens to the parent kernel (no nested pivot).
|
|
99
128
|
isWorkflowNode: ctx.isWorkflowNode,
|
|
129
|
+
// The child runs under ITS OWN spec, never the parent's: the spread above would otherwise
|
|
130
|
+
// leak the parent's `runSpec` (identity, capability filter — and a LoopDriver's armed
|
|
131
|
+
// `loopRound`, giving every child a phantom pace tool). A loop-node iteration carries its
|
|
132
|
+
// own minimal spec to arm the pacing trap (DW-3); everything else runs spec-less as before.
|
|
133
|
+
runSpec: ctx.spec.loopRound
|
|
134
|
+
? { identity: ctx.spec.identity, role: ctx.spec.role, goal: ctx.spec.goal, loopRound: ctx.spec.loopRound }
|
|
135
|
+
: undefined,
|
|
100
136
|
});
|
|
101
137
|
// #2-B-ii: when the parent preempts this node (kernel `AgentPreempted`), interrupt the child —
|
|
102
138
|
// cancelling its in-flight LLM call. Handle an already-aborted signal too (creation race).
|
|
103
139
|
linkAbort(ctx.abortSignal, childRunner);
|
|
140
|
+
return { childRunner, inheritEvents, cleanupWorktree };
|
|
141
|
+
}
|
|
142
|
+
async *stream(ctx) {
|
|
143
|
+
const { childRunner, inheritEvents, cleanupWorktree } = await this.buildChild(ctx);
|
|
104
144
|
try {
|
|
105
145
|
yield* childRunner.run({
|
|
106
146
|
sessionId: ctx.spec.identity.sessionId,
|
|
@@ -114,31 +154,12 @@ export class SubAgentOrchestrator {
|
|
|
114
154
|
}
|
|
115
155
|
async run(ctx) {
|
|
116
156
|
if (ctx.harness) {
|
|
117
|
-
const { RuntimeRunner } = await import("./runner.js");
|
|
118
157
|
const { HarnessLoop } = await import("../harness/harness.js");
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
//
|
|
123
|
-
const {
|
|
124
|
-
const childRunner = new RuntimeRunner({
|
|
125
|
-
...ctx.parentOpts,
|
|
126
|
-
// M1/G3: route to the node's hinted model (falls back to the parent provider).
|
|
127
|
-
provider: resolveProvider(ctx.parentOpts, ctx.spec.modelHint),
|
|
128
|
-
// M4/G5: cap the child run at the node's token budget (falls back to the inherited cap).
|
|
129
|
-
maxTotalTokens: ctx.spec.tokenBudget ?? ctx.parentOpts.maxTotalTokens,
|
|
130
|
-
executionPlane: execPlane,
|
|
131
|
-
agentId: ctx.spec.identity.agentId,
|
|
132
|
-
sessionLog: ctx.sessionLog,
|
|
133
|
-
skillDir: metaTools.has("skill") ? ctx.parentOpts.skillDir : undefined,
|
|
134
|
-
dreamStore: metaTools.has("memory") ? ctx.parentOpts.dreamStore : undefined,
|
|
135
|
-
knowledgeSource: metaTools.has("knowledge") ? ctx.parentOpts.knowledgeSource : undefined,
|
|
136
|
-
enablePlanTool: metaTools.has("update_plan") ? ctx.parentOpts.enablePlanTool : undefined,
|
|
137
|
-
// M5 v2.1: a workflow node's `start_workflow` flattens to the parent kernel (no nested pivot).
|
|
138
|
-
isWorkflowNode: ctx.isWorkflowNode,
|
|
139
|
-
});
|
|
140
|
-
// #2-B-ii: parent preempt → interrupt the child (cancels its in-flight LLM call).
|
|
141
|
-
linkAbort(ctx.abortSignal, childRunner);
|
|
158
|
+
// W-N4: same construction as the direct path — the harness child now honors the inherited
|
|
159
|
+
// system prompt (`system_only`). NOTE: `full` inheritance is NOT consumable under a harness
|
|
160
|
+
// (HarnessLoop mints its own per-attempt session ids, so parent events can't be replayed) —
|
|
161
|
+
// the inherited events are dropped here by construction, not by drift.
|
|
162
|
+
const { childRunner, cleanupWorktree } = await this.buildChild(ctx);
|
|
142
163
|
const loop = new HarnessLoop(childRunner, ctx.harness.evalProvider, {
|
|
143
164
|
maxAttempts: ctx.harness.maxAttempts ?? 3,
|
|
144
165
|
});
|
|
@@ -186,6 +207,9 @@ export class SubAgentOrchestrator {
|
|
|
186
207
|
turnsUsed: done?.iterations ?? 0,
|
|
187
208
|
totalTokensUsed: done?.totalTokens ?? 0,
|
|
188
209
|
...(finalText ? { finalMessage: { role: "assistant", content: finalText, toolCalls: [] } } : {}),
|
|
210
|
+
// DW-3: surface the kernel-adjudicated pace decision (loop-node iterations consume it as the
|
|
211
|
+
// continuation vocabulary). SDK-internal; stripped by subAgentResultToKernel.
|
|
212
|
+
...(done?.paceDecision ? { paceDecision: done.paceDecision } : {}),
|
|
189
213
|
};
|
|
190
214
|
return {
|
|
191
215
|
agentId: ctx.spec.identity.agentId,
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
/** Instruction appended to a loop
|
|
2
|
-
|
|
1
|
+
/** Instruction appended to a loop iteration's goal. DW-3: the continuation verb is the
|
|
2
|
+
* kernel-adjudicated `pace` meta-tool (armed on every iteration run), not a text blob — one
|
|
3
|
+
* vocabulary shared with the round-level loop agent. Iterations share one session, so "the work
|
|
4
|
+
* so far" is simply the visible transcript. */
|
|
5
|
+
export declare function loopInstruction(maxIters: number, iteration?: number): string;
|
|
6
|
+
/** W-N2: dependency outputs appended to a dependent node's goal — a DAG edge carries data, not
|
|
7
|
+
* just ordering (fan-out→synthesize was an uninformed synthesis without this). Each dependency's
|
|
8
|
+
* output is clipped so a chain of large nodes can't blow the child's context; empty/unknown
|
|
9
|
+
* outputs are skipped. Returns "" when the node has no dependencies. */
|
|
10
|
+
export declare function dependencyOutputsNote(inputAgentIds: string[] | undefined, outputs: Map<string, string> | undefined, maxPerDep?: number): string;
|
|
3
11
|
/** Instruction appended to a classify node's goal: pick exactly one of the kernel's branch labels. */
|
|
4
12
|
export declare function classifyInstruction(labels: string[]): string;
|
|
5
13
|
/** Build a tournament judge's goal: the controller's criterion + the two candidates to compare. */
|
|
@@ -6,12 +6,33 @@
|
|
|
6
6
|
//! decision from the node's agent and extracts the matching result signal (`loopContinue` /
|
|
7
7
|
//! `classifyBranch` / `tournamentWinner`) the kernel reads back.
|
|
8
8
|
import { extractJsonValue } from "./output-schema.js";
|
|
9
|
-
/** Instruction appended to a loop
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
/** Instruction appended to a loop iteration's goal. DW-3: the continuation verb is the
|
|
10
|
+
* kernel-adjudicated `pace` meta-tool (armed on every iteration run), not a text blob — one
|
|
11
|
+
* vocabulary shared with the round-level loop agent. Iterations share one session, so "the work
|
|
12
|
+
* so far" is simply the visible transcript. */
|
|
13
|
+
export function loopInstruction(maxIters, iteration = 0) {
|
|
14
|
+
return (`This task runs as a LOOP — this is iteration ${iteration + 1} of up to ${maxIters}. Your prior ` +
|
|
15
|
+
`iterations' work (if any) is visible above; do the NEXT increment now. Then call the \`pace\` tool: ` +
|
|
16
|
+
`\`{"next": "continue"}\` to request another iteration, or \`{"next": "stop"}\` when the overall ` +
|
|
17
|
+
`task is complete. Ending without calling \`pace\` also completes the loop.`);
|
|
18
|
+
}
|
|
19
|
+
/** W-N2: dependency outputs appended to a dependent node's goal — a DAG edge carries data, not
|
|
20
|
+
* just ordering (fan-out→synthesize was an uninformed synthesis without this). Each dependency's
|
|
21
|
+
* output is clipped so a chain of large nodes can't blow the child's context; empty/unknown
|
|
22
|
+
* outputs are skipped. Returns "" when the node has no dependencies. */
|
|
23
|
+
export function dependencyOutputsNote(inputAgentIds, outputs, maxPerDep = 8_000) {
|
|
24
|
+
if (!inputAgentIds?.length || !outputs)
|
|
25
|
+
return "";
|
|
26
|
+
const blocks = inputAgentIds
|
|
27
|
+
.map(id => {
|
|
28
|
+
const out = outputs.get(id) ?? "";
|
|
29
|
+
if (!out)
|
|
30
|
+
return "";
|
|
31
|
+
const clipped = out.length > maxPerDep ? `${out.slice(0, maxPerDep)}\n…[truncated]` : out;
|
|
32
|
+
return `[dependency ${id} output]\n${clipped}`;
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
return blocks.join("\n\n");
|
|
15
36
|
}
|
|
16
37
|
/** Instruction appended to a classify node's goal: pick exactly one of the kernel's branch labels. */
|
|
17
38
|
export function classifyInstruction(labels) {
|
|
@@ -23,8 +23,10 @@ export declare class SignalGateway implements SignalSource {
|
|
|
23
23
|
* shared gateway can serve N peer loops. Omit ⇒ legacy FIFO drain (any signal).
|
|
24
24
|
*/
|
|
25
25
|
nextSignal(recipient?: string): Promise<RuntimeSignal | null>;
|
|
26
|
-
/** Register a listener that is called synchronously whenever a signal is emitted.
|
|
27
|
-
|
|
26
|
+
/** Register a listener that is called synchronously whenever a signal is emitted.
|
|
27
|
+
* Returns an unsubscribe function — long-lived consumers (e.g. a loop's
|
|
28
|
+
* `signalAwareSleeper`, re-registered per sleep) must call it or the listener leaks. */
|
|
29
|
+
onSignal(listener: (sig: RuntimeSignal) => void): () => void;
|
|
28
30
|
/** Schedule a ScheduledPrompt to fire at its `runAtMs`. Idempotent by goal+time. */
|
|
29
31
|
schedule(prompt: ScheduledPrompt): void;
|
|
30
32
|
/** Cancel a scheduled prompt before it fires. */
|
package/dist/signals/gateway.js
CHANGED
|
@@ -30,9 +30,16 @@ export class SignalGateway {
|
|
|
30
30
|
return this.queue.splice(idx, 1)[0];
|
|
31
31
|
}
|
|
32
32
|
// ── Push API ────────────────────────────────────────────────────────────────
|
|
33
|
-
/** Register a listener that is called synchronously whenever a signal is emitted.
|
|
33
|
+
/** Register a listener that is called synchronously whenever a signal is emitted.
|
|
34
|
+
* Returns an unsubscribe function — long-lived consumers (e.g. a loop's
|
|
35
|
+
* `signalAwareSleeper`, re-registered per sleep) must call it or the listener leaks. */
|
|
34
36
|
onSignal(listener) {
|
|
35
37
|
this.listeners.push(listener);
|
|
38
|
+
return () => {
|
|
39
|
+
const idx = this.listeners.indexOf(listener);
|
|
40
|
+
if (idx !== -1)
|
|
41
|
+
this.listeners.splice(idx, 1);
|
|
42
|
+
};
|
|
36
43
|
}
|
|
37
44
|
/** Schedule a ScheduledPrompt to fire at its `runAtMs`. Idempotent by goal+time. */
|
|
38
45
|
schedule(prompt) {
|