wave-agent-sdk 0.17.1 → 0.17.3
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/builtin/skills/deep-research/SKILL.md +90 -0
- package/builtin/skills/settings/ENV.md +6 -3
- package/dist/agent.d.ts +28 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +128 -34
- package/dist/constants/goalPrompts.d.ts +2 -0
- package/dist/constants/goalPrompts.d.ts.map +1 -0
- package/dist/constants/goalPrompts.js +10 -0
- package/dist/constants/tools.d.ts +1 -0
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +1 -0
- package/dist/managers/aiManager.d.ts +7 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +77 -41
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +10 -2
- package/dist/managers/goalManager.d.ts +43 -0
- package/dist/managers/goalManager.d.ts.map +1 -0
- package/dist/managers/goalManager.js +177 -0
- package/dist/managers/messageManager.d.ts +2 -2
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageQueue.d.ts +10 -0
- package/dist/managers/messageQueue.d.ts.map +1 -1
- package/dist/managers/messageQueue.js +53 -1
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +7 -1
- package/dist/managers/skillManager.d.ts +2 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +19 -9
- package/dist/managers/slashCommandManager.d.ts +6 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +105 -0
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +5 -0
- package/dist/managers/workflowManager.d.ts +65 -0
- package/dist/managers/workflowManager.d.ts.map +1 -0
- package/dist/managers/workflowManager.js +380 -0
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +3 -3
- package/dist/services/MarketplaceService.d.ts +2 -2
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +11 -32
- package/dist/services/aiService.d.ts +23 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +102 -9
- package/dist/services/configurationService.d.ts +1 -1
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +3 -16
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +4 -0
- package/dist/services/session.d.ts +9 -1
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +28 -1
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +49 -7
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +1 -1
- package/dist/tools/taskManagementTools.d.ts.map +1 -1
- package/dist/tools/taskManagementTools.js +103 -157
- package/dist/tools/types.d.ts +2 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts.map +1 -1
- package/dist/tools/webFetchTool.js +0 -9
- package/dist/tools/workflowTool.d.ts +11 -0
- package/dist/tools/workflowTool.d.ts.map +1 -0
- package/dist/tools/workflowTool.js +190 -0
- package/dist/types/agent.d.ts +2 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/commands.d.ts +4 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/config.d.ts +2 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/core.d.ts +1 -1
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +2 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/messaging.d.ts +2 -2
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/processes.d.ts +6 -2
- package/dist/types/processes.d.ts.map +1 -1
- package/dist/types/workflow.d.ts +2 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +1 -0
- package/dist/utils/cacheControlUtils.d.ts +13 -8
- package/dist/utils/cacheControlUtils.d.ts.map +1 -1
- package/dist/utils/cacheControlUtils.js +73 -102
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +7 -0
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +21 -6
- package/dist/utils/messageOperations.d.ts +2 -2
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/notificationXml.d.ts.map +1 -1
- package/dist/workflow/budgetTracker.d.ts +12 -0
- package/dist/workflow/budgetTracker.d.ts.map +1 -0
- package/dist/workflow/budgetTracker.js +30 -0
- package/dist/workflow/concurrencyLimiter.d.ts +14 -0
- package/dist/workflow/concurrencyLimiter.d.ts.map +1 -0
- package/dist/workflow/concurrencyLimiter.js +39 -0
- package/dist/workflow/journal.d.ts +19 -0
- package/dist/workflow/journal.d.ts.map +1 -0
- package/dist/workflow/journal.js +74 -0
- package/dist/workflow/progressReporter.d.ts +21 -0
- package/dist/workflow/progressReporter.d.ts.map +1 -0
- package/dist/workflow/progressReporter.js +118 -0
- package/dist/workflow/runState.d.ts +16 -0
- package/dist/workflow/runState.d.ts.map +1 -0
- package/dist/workflow/runState.js +57 -0
- package/dist/workflow/scriptRuntime.d.ts +35 -0
- package/dist/workflow/scriptRuntime.d.ts.map +1 -0
- package/dist/workflow/scriptRuntime.js +196 -0
- package/dist/workflow/structuredOutput.d.ts +27 -0
- package/dist/workflow/structuredOutput.d.ts.map +1 -0
- package/dist/workflow/structuredOutput.js +106 -0
- package/dist/workflow/types.d.ts +81 -0
- package/dist/workflow/types.d.ts.map +1 -0
- package/dist/workflow/types.js +1 -0
- package/dist/workflow/workflowApis.d.ts +46 -0
- package/dist/workflow/workflowApis.d.ts.map +1 -0
- package/dist/workflow/workflowApis.js +280 -0
- package/package.json +1 -1
- package/src/agent.ts +144 -34
- package/src/constants/goalPrompts.ts +10 -0
- package/src/constants/tools.ts +1 -0
- package/src/managers/aiManager.ts +91 -47
- package/src/managers/backgroundTaskManager.ts +16 -4
- package/src/managers/goalManager.ts +232 -0
- package/src/managers/messageManager.ts +2 -2
- package/src/managers/messageQueue.ts +59 -1
- package/src/managers/pluginManager.ts +8 -1
- package/src/managers/skillManager.ts +20 -9
- package/src/managers/slashCommandManager.ts +119 -0
- package/src/managers/toolManager.ts +7 -0
- package/src/managers/workflowManager.ts +491 -0
- package/src/prompts/index.ts +4 -2
- package/src/services/MarketplaceService.ts +14 -38
- package/src/services/aiService.ts +166 -12
- package/src/services/configurationService.ts +2 -22
- package/src/services/hook.ts +5 -0
- package/src/services/session.ts +42 -2
- package/src/tools/bashTool.ts +64 -9
- package/src/tools/readTool.ts +1 -2
- package/src/tools/taskManagementTools.ts +146 -195
- package/src/tools/types.ts +2 -0
- package/src/tools/webFetchTool.ts +0 -12
- package/src/tools/workflowTool.ts +205 -0
- package/src/types/agent.ts +6 -0
- package/src/types/commands.ts +4 -0
- package/src/types/config.ts +2 -2
- package/src/types/core.ts +3 -3
- package/src/types/hooks.ts +2 -0
- package/src/types/index.ts +1 -0
- package/src/types/messaging.ts +2 -2
- package/src/types/processes.ts +10 -2
- package/src/types/workflow.ts +5 -0
- package/src/utils/cacheControlUtils.ts +106 -131
- package/src/utils/containerSetup.ts +9 -0
- package/src/utils/markdownParser.ts +26 -8
- package/src/utils/messageOperations.ts +2 -2
- package/src/utils/notificationXml.ts +6 -1
- package/src/workflow/budgetTracker.ts +34 -0
- package/src/workflow/concurrencyLimiter.ts +47 -0
- package/src/workflow/journal.ts +95 -0
- package/src/workflow/progressReporter.ts +141 -0
- package/src/workflow/runState.ts +65 -0
- package/src/workflow/scriptRuntime.ts +274 -0
- package/src/workflow/structuredOutput.ts +123 -0
- package/src/workflow/types.ts +95 -0
- package/src/workflow/workflowApis.ts +412 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { createStructuredOutputPrompt, createStructuredOutputTool, extractStructuredResult, } from "./structuredOutput.js";
|
|
4
|
+
import { AGENT_TOOL_NAME, WORKFLOW_TOOL_NAME } from "../constants/tools.js";
|
|
5
|
+
import { logger } from "../utils/globalLogger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Attempt to parse args if it was passed as a JSON string.
|
|
8
|
+
* LLMs may serialize array/object args as a string (e.g. "[3,8,15,42,7]")
|
|
9
|
+
* which would cause `for...of` to iterate character-by-character.
|
|
10
|
+
*/
|
|
11
|
+
function parseArgsIfNeeded(args) {
|
|
12
|
+
if (typeof args === "string") {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(args);
|
|
15
|
+
// Only accept parsed arrays/objects, not primitives like "42" → 42
|
|
16
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Not valid JSON — use as-is (string)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return args;
|
|
25
|
+
}
|
|
26
|
+
const MAX_TOTAL_AGENTS = 1000;
|
|
27
|
+
const MAX_ITEMS_PER_CALL = 4096;
|
|
28
|
+
export function createWorkflowApis(ctx) {
|
|
29
|
+
let agentCounter = ctx.initialAgentCount ?? 0;
|
|
30
|
+
const agent = async (prompt, opts) => {
|
|
31
|
+
const index = agentCounter++;
|
|
32
|
+
// Check agent limit
|
|
33
|
+
if (index >= MAX_TOTAL_AGENTS) {
|
|
34
|
+
throw new Error(`Workflow exceeded maximum agent count of ${MAX_TOTAL_AGENTS}`);
|
|
35
|
+
}
|
|
36
|
+
// Check abort
|
|
37
|
+
if (ctx.abortSignal.aborted) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Check budget
|
|
41
|
+
if (ctx.budgetTracker.isExceeded()) {
|
|
42
|
+
throw new Error("Workflow token budget exceeded");
|
|
43
|
+
}
|
|
44
|
+
// Check journal for cached result (resume)
|
|
45
|
+
const cached = ctx.journal.getCachedResult(index);
|
|
46
|
+
if (cached !== undefined) {
|
|
47
|
+
logger.debug(`[Workflow] agent(${index}): using cached result`);
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
// Acquire concurrency slot
|
|
51
|
+
await ctx.concurrencyLimiter.acquire();
|
|
52
|
+
// Create per-agent abort controller linked to the run's signal
|
|
53
|
+
const agentController = new AbortController();
|
|
54
|
+
ctx.agentControllers.set(index, agentController);
|
|
55
|
+
// If the run is already aborted, abort this agent immediately
|
|
56
|
+
if (ctx.abortSignal.aborted) {
|
|
57
|
+
agentController.abort();
|
|
58
|
+
}
|
|
59
|
+
// Propagate run abort to agent abort
|
|
60
|
+
const onRunAbort = () => agentController.abort();
|
|
61
|
+
ctx.abortSignal.addEventListener("abort", onRunAbort);
|
|
62
|
+
try {
|
|
63
|
+
// Resolve subagent type
|
|
64
|
+
const subagentType = opts?.agentType || "general-purpose";
|
|
65
|
+
let configuration = await ctx.subagentManager.findSubagent(subagentType);
|
|
66
|
+
if (!configuration) {
|
|
67
|
+
logger.warn(`[Workflow] agent(${index}): subagent type "${subagentType}" not found, falling back to general-purpose`);
|
|
68
|
+
configuration =
|
|
69
|
+
await ctx.subagentManager.findSubagent("general-purpose");
|
|
70
|
+
if (!configuration) {
|
|
71
|
+
throw new Error(`No subagent type available for agent call`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Build the effective prompt
|
|
75
|
+
let effectivePrompt = prompt;
|
|
76
|
+
if (opts?.schema) {
|
|
77
|
+
effectivePrompt += createStructuredOutputPrompt(opts.schema);
|
|
78
|
+
}
|
|
79
|
+
// Set phase if specified
|
|
80
|
+
if (opts?.phase) {
|
|
81
|
+
ctx.progressReporter.setPhase(opts.phase);
|
|
82
|
+
}
|
|
83
|
+
ctx.progressReporter.agentStarted();
|
|
84
|
+
// Create subagent instance
|
|
85
|
+
const instance = await ctx.subagentManager.createInstance(configuration, {
|
|
86
|
+
description: opts?.label || `workflow-agent-${index}`,
|
|
87
|
+
prompt: effectivePrompt,
|
|
88
|
+
subagent_type: subagentType,
|
|
89
|
+
model: opts?.model,
|
|
90
|
+
});
|
|
91
|
+
// Capture subagent linkage info
|
|
92
|
+
const subagentId = instance.subagentId;
|
|
93
|
+
const transcriptPath = instance.messageManager.getTranscriptPath();
|
|
94
|
+
// If schema provided, register StructuredOutput tool on the subagent's tool manager
|
|
95
|
+
// and force the model to call it via tool_choice
|
|
96
|
+
if (opts?.schema) {
|
|
97
|
+
const structuredTool = createStructuredOutputTool(opts.schema);
|
|
98
|
+
instance.toolManager.register(structuredTool);
|
|
99
|
+
instance.aiManager.toolChoiceOverride = {
|
|
100
|
+
type: "function",
|
|
101
|
+
function: { name: "StructuredOutput" },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Deny Agent and Workflow tools in workflow subagents
|
|
105
|
+
// (prevent infinite recursion)
|
|
106
|
+
instance.permissionManager.addTemporaryRules([
|
|
107
|
+
`${AGENT_TOOL_NAME}:deny`,
|
|
108
|
+
`${WORKFLOW_TOOL_NAME}:deny`,
|
|
109
|
+
]);
|
|
110
|
+
// Execute agent
|
|
111
|
+
const result = await ctx.subagentManager.executeAgent(instance, effectivePrompt, agentController.signal, false);
|
|
112
|
+
// Track token usage — sum from assistant messages' usage field
|
|
113
|
+
// (getUsages() is empty because onUsageAdded targets the parent agent)
|
|
114
|
+
const messages = instance.messageManager.getMessages();
|
|
115
|
+
const tokens = messages.reduce((sum, msg) => {
|
|
116
|
+
if (msg.role !== "assistant" || !msg.usage)
|
|
117
|
+
return sum;
|
|
118
|
+
const u = msg.usage;
|
|
119
|
+
return (sum +
|
|
120
|
+
(u.total_tokens || 0) +
|
|
121
|
+
(u.cache_read_input_tokens || 0) +
|
|
122
|
+
(u.cache_creation_input_tokens || 0));
|
|
123
|
+
}, 0);
|
|
124
|
+
ctx.budgetTracker.addUsage(tokens);
|
|
125
|
+
ctx.progressReporter.agentCompleted(tokens);
|
|
126
|
+
// Extract structured result if schema was provided
|
|
127
|
+
let finalResult;
|
|
128
|
+
if (opts?.schema) {
|
|
129
|
+
const messages = instance.messageManager.getMessages();
|
|
130
|
+
// Build the format extractStructuredResult expects:
|
|
131
|
+
// Look for StructuredOutput tool calls in ToolBlocks
|
|
132
|
+
const structuredToolBlocks = messages
|
|
133
|
+
.filter((m) => m.role === "assistant")
|
|
134
|
+
.flatMap((m) => m.blocks)
|
|
135
|
+
.filter((b) => b.type === "tool" &&
|
|
136
|
+
b.name === "StructuredOutput" &&
|
|
137
|
+
b.stage === "end");
|
|
138
|
+
if (structuredToolBlocks.length > 0) {
|
|
139
|
+
// Parse the StructuredOutput tool call parameters
|
|
140
|
+
const lastBlock = structuredToolBlocks[structuredToolBlocks.length - 1];
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(lastBlock.parameters || "{}");
|
|
143
|
+
finalResult = parsed;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Parameters parse failed, try result
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(lastBlock.result || "null");
|
|
149
|
+
finalResult = parsed;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
finalResult = result;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Fallback: try extractStructuredResult with message content
|
|
158
|
+
finalResult = extractStructuredResult(messages.map((m) => {
|
|
159
|
+
const textBlock = m.blocks.find((b) => b.type === "text");
|
|
160
|
+
return {
|
|
161
|
+
role: m.role,
|
|
162
|
+
content: textBlock?.content,
|
|
163
|
+
tool_calls: m.tool_calls,
|
|
164
|
+
};
|
|
165
|
+
}), opts.schema);
|
|
166
|
+
if (finalResult === null) {
|
|
167
|
+
// Schema enforcement failed — return the raw text
|
|
168
|
+
finalResult = result;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
finalResult = result;
|
|
174
|
+
}
|
|
175
|
+
// Append to journal
|
|
176
|
+
ctx.journal.append({
|
|
177
|
+
agentIndex: index,
|
|
178
|
+
prompt,
|
|
179
|
+
opts: { ...opts },
|
|
180
|
+
result: finalResult,
|
|
181
|
+
tokens,
|
|
182
|
+
subagentId,
|
|
183
|
+
transcriptPath,
|
|
184
|
+
});
|
|
185
|
+
// Write agent metadata sidecar
|
|
186
|
+
try {
|
|
187
|
+
const agentMeta = {
|
|
188
|
+
agentType: subagentType,
|
|
189
|
+
subagentId,
|
|
190
|
+
transcriptPath,
|
|
191
|
+
label: opts?.label,
|
|
192
|
+
phase: opts?.phase,
|
|
193
|
+
};
|
|
194
|
+
const metaPath = path.join(ctx.runDir, "agents", `${index}.meta.json`);
|
|
195
|
+
await fs.promises.writeFile(metaPath, JSON.stringify(agentMeta, null, 2), "utf-8");
|
|
196
|
+
}
|
|
197
|
+
catch (metaErr) {
|
|
198
|
+
logger.warn(`[Workflow] Failed to write agent meta for ${index}: ${metaErr instanceof Error ? metaErr.message : String(metaErr)}`);
|
|
199
|
+
}
|
|
200
|
+
// Cleanup
|
|
201
|
+
ctx.subagentManager.cleanupInstance(instance.subagentId);
|
|
202
|
+
return finalResult;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
// Agent errors are logged but don't crash the workflow
|
|
206
|
+
// Return null so the caller can filter with .filter(Boolean)
|
|
207
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
208
|
+
logger.warn(`[Workflow] agent(${index}) failed: ${errorMsg}`);
|
|
209
|
+
// Write agent_failed entry to journal for skip/retry tracking
|
|
210
|
+
ctx.journal.append({
|
|
211
|
+
type: "agent_failed",
|
|
212
|
+
agentIndex: index,
|
|
213
|
+
error: errorMsg,
|
|
214
|
+
});
|
|
215
|
+
// Emit progress event for agent failure
|
|
216
|
+
ctx.progressReporter.agentFailed(index);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
ctx.agentControllers.delete(index);
|
|
221
|
+
ctx.abortSignal.removeEventListener("abort", onRunAbort);
|
|
222
|
+
ctx.concurrencyLimiter.release();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const parallel = async (thunks) => {
|
|
226
|
+
if (thunks.length > MAX_ITEMS_PER_CALL) {
|
|
227
|
+
throw new Error(`parallel() accepts at most ${MAX_ITEMS_PER_CALL} items, got ${thunks.length}`);
|
|
228
|
+
}
|
|
229
|
+
// Note: thunks typically call agent() which acquires its own slot,
|
|
230
|
+
// so parallel does NOT acquire a slot per thunk to avoid deadlock.
|
|
231
|
+
const results = await Promise.allSettled(thunks.map(async (thunk) => {
|
|
232
|
+
try {
|
|
233
|
+
return await thunk();
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}));
|
|
239
|
+
// Convert rejected promises to null
|
|
240
|
+
return results.map((r) => (r.status === "fulfilled" ? r.value : null));
|
|
241
|
+
};
|
|
242
|
+
const pipeline = async (items, ...stages) => {
|
|
243
|
+
if (items.length > MAX_ITEMS_PER_CALL) {
|
|
244
|
+
throw new Error(`pipeline() accepts at most ${MAX_ITEMS_PER_CALL} items, got ${items.length}`);
|
|
245
|
+
}
|
|
246
|
+
// Run each item through all stages, items are independent.
|
|
247
|
+
// Note: agent() inside stages acquires its own concurrency slot,
|
|
248
|
+
// so pipeline does NOT acquire a slot per item to avoid deadlock.
|
|
249
|
+
const results = await Promise.allSettled(items.map(async (item, index) => {
|
|
250
|
+
try {
|
|
251
|
+
let result = undefined;
|
|
252
|
+
for (const stage of stages) {
|
|
253
|
+
result = await stage(result, item, index);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
logger.warn(`[Workflow] pipeline item ${index} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}));
|
|
262
|
+
return results.map((r) => (r.status === "fulfilled" ? r.value : null));
|
|
263
|
+
};
|
|
264
|
+
const phase = (title) => {
|
|
265
|
+
ctx.progressReporter.setPhase(title);
|
|
266
|
+
};
|
|
267
|
+
const log = (message) => {
|
|
268
|
+
ctx.journal.appendLog(message);
|
|
269
|
+
ctx.onLog?.(message);
|
|
270
|
+
};
|
|
271
|
+
return {
|
|
272
|
+
agent,
|
|
273
|
+
parallel,
|
|
274
|
+
pipeline,
|
|
275
|
+
phase,
|
|
276
|
+
log,
|
|
277
|
+
args: parseArgsIfNeeded(ctx.args),
|
|
278
|
+
budget: ctx.budgetTracker.toBudgetInfo(),
|
|
279
|
+
};
|
|
280
|
+
}
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { McpManager } from "./managers/mcpManager.js";
|
|
|
8
8
|
import { LspManager } from "./managers/lspManager.js";
|
|
9
9
|
import { BangManager } from "./managers/bangManager.js";
|
|
10
10
|
import { CronManager } from "./managers/cronManager.js";
|
|
11
|
+
import { GoalManager } from "./managers/goalManager.js";
|
|
11
12
|
import { BackgroundTaskManager } from "./managers/backgroundTaskManager.js";
|
|
12
13
|
import { NotificationQueue } from "./managers/notificationQueue.js";
|
|
13
14
|
import { MessageQueue, type QueuedMessage } from "./managers/messageQueue.js";
|
|
@@ -71,6 +72,7 @@ export class Agent {
|
|
|
71
72
|
private pluginManager: PluginManager; // Add plugin manager instance
|
|
72
73
|
private skillManager: SkillManager; // Add skill manager instance
|
|
73
74
|
private cronManager: CronManager; // Add cron manager instance
|
|
75
|
+
private goalManager: GoalManager; // Add goal manager instance
|
|
74
76
|
private hookManager: HookManager; // Add hooks manager instance
|
|
75
77
|
private reversionManager: ReversionManager;
|
|
76
78
|
private notificationQueue: NotificationQueue; // Add notification queue instance
|
|
@@ -202,6 +204,7 @@ export class Agent {
|
|
|
202
204
|
this.pluginManager = this.container.get("PluginManager")!;
|
|
203
205
|
this.bangManager = this.container.get("BangManager")!;
|
|
204
206
|
this.cronManager = this.container.get("CronManager")!;
|
|
207
|
+
this.goalManager = this.container.get("GoalManager")!;
|
|
205
208
|
this.notificationQueue = this.container.get("NotificationQueue")!;
|
|
206
209
|
this.messageQueue = this.container.get("MessageQueue")!;
|
|
207
210
|
|
|
@@ -229,39 +232,33 @@ export class Agent {
|
|
|
229
232
|
});
|
|
230
233
|
|
|
231
234
|
// Wire up message queue to process when agent becomes idle
|
|
232
|
-
this.messageQueue.onMessageEnqueued = () =>
|
|
233
|
-
// If the AI is NOT loading and command is not running, trigger dequeue
|
|
234
|
-
if (!this.aiManager.isLoading && !this.isCommandRunning) {
|
|
235
|
-
this.processQueuedMessage().catch((error) => {
|
|
236
|
-
this.logger?.error("Failed to process queued message:", error);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
};
|
|
235
|
+
this.messageQueue.onMessageEnqueued = () => this.tryDequeue();
|
|
240
236
|
|
|
241
237
|
// Wire up AI loading changes to process queue when AI becomes idle
|
|
242
238
|
this.aiManager.onLoadingChange = (loading: boolean) => {
|
|
243
|
-
if (!loading
|
|
244
|
-
this.processQueuedMessage().catch((error) => {
|
|
245
|
-
this.logger?.error("Failed to process queued message:", error);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
239
|
+
if (!loading) this.tryDequeue();
|
|
248
240
|
};
|
|
249
241
|
|
|
250
242
|
// Wire up bang manager callback for command running changes
|
|
251
243
|
this.bangManager.onCommandRunningChange = (running: boolean) => {
|
|
252
244
|
this.options.callbacks?.onCommandRunningChange?.(running);
|
|
253
|
-
|
|
254
|
-
if (!running && !this.aiManager.isLoading) {
|
|
255
|
-
this.processQueuedMessage().catch((error) => {
|
|
256
|
-
this.logger?.error("Failed to process queued message:", error);
|
|
257
|
-
});
|
|
258
|
-
}
|
|
245
|
+
if (!running) this.tryDequeue();
|
|
259
246
|
};
|
|
260
247
|
|
|
261
248
|
// Set initial permission mode if provided
|
|
262
249
|
if (options.permissionMode) {
|
|
263
250
|
this.setPermissionMode(options.permissionMode);
|
|
264
251
|
}
|
|
252
|
+
|
|
253
|
+
// Wire up goal state change callback
|
|
254
|
+
this.goalManager.setOnGoalStateChange((active, condition, elapsed) => {
|
|
255
|
+
this.options.callbacks?.onGoalStateChange?.(active, condition, elapsed);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Wire up goal evaluating callback
|
|
259
|
+
this.goalManager.setOnGoalEvaluating((evaluating) => {
|
|
260
|
+
this.options.callbacks?.onGoalEvaluating?.(evaluating);
|
|
261
|
+
});
|
|
265
262
|
}
|
|
266
263
|
|
|
267
264
|
// Public getter methods
|
|
@@ -343,6 +340,16 @@ export class Agent {
|
|
|
343
340
|
return this.messageQueue.getQueue();
|
|
344
341
|
}
|
|
345
342
|
|
|
343
|
+
/** Get goal status string */
|
|
344
|
+
public get goalStatus(): string {
|
|
345
|
+
return this.goalManager.getStatusString();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Check if a goal is active */
|
|
349
|
+
public get isGoalActive(): boolean {
|
|
350
|
+
return this.goalManager.isGoalActive();
|
|
351
|
+
}
|
|
352
|
+
|
|
346
353
|
/**
|
|
347
354
|
* Remove a queued message by index
|
|
348
355
|
* @param index - The index of the message to remove
|
|
@@ -356,14 +363,60 @@ export class Agent {
|
|
|
356
363
|
return removed;
|
|
357
364
|
}
|
|
358
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Recall the last editable queued message (for inline editing)
|
|
368
|
+
* @returns The recalled message, or null if no editable message exists
|
|
369
|
+
*/
|
|
370
|
+
public recallQueuedMessage(): QueuedMessage | null {
|
|
371
|
+
const msg = this.messageQueue.popLastEditable();
|
|
372
|
+
if (msg) {
|
|
373
|
+
this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
|
|
374
|
+
}
|
|
375
|
+
return msg;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Remove a queued message by its ID
|
|
380
|
+
* @param id - The ID of the message to remove
|
|
381
|
+
* @returns true if the message was removed, false if not found
|
|
382
|
+
*/
|
|
383
|
+
public removeQueuedMessageById(id: string): boolean {
|
|
384
|
+
const removed = this.messageQueue.removeById(id);
|
|
385
|
+
if (removed) {
|
|
386
|
+
this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
|
|
387
|
+
}
|
|
388
|
+
return removed;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Unified dequeue trigger — checks state machine before processing.
|
|
393
|
+
* Called from onMessageEnqueued, onLoadingChange(false), and
|
|
394
|
+
* onCommandRunningChange(false).
|
|
395
|
+
*/
|
|
396
|
+
private tryDequeue(): void {
|
|
397
|
+
if (this.messageQueue.state !== "idle") return;
|
|
398
|
+
if (!this.messageQueue.hasPending()) return;
|
|
399
|
+
if (this.aiManager.isLoading || this.isCommandRunning) return;
|
|
400
|
+
|
|
401
|
+
this.messageQueue.transitionTo("dispatching");
|
|
402
|
+
this.processQueuedMessage()
|
|
403
|
+
.catch((error) => {
|
|
404
|
+
this.logger?.error("Failed to process queued message:", error);
|
|
405
|
+
})
|
|
406
|
+
.finally(() => {
|
|
407
|
+
this.messageQueue.transitionTo("idle");
|
|
408
|
+
this.tryDequeue(); // Re-check after processing
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
359
412
|
/**
|
|
360
413
|
* Process the next queued message when the agent becomes idle.
|
|
361
|
-
* Dequeues the next message and handles it based on type.
|
|
362
414
|
*/
|
|
363
415
|
private async processQueuedMessage(): Promise<void> {
|
|
364
416
|
const next = this.messageQueue.dequeue();
|
|
365
417
|
if (!next) return;
|
|
366
418
|
|
|
419
|
+
this.messageQueue.transitionTo("running");
|
|
367
420
|
this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
|
|
368
421
|
|
|
369
422
|
if (next.type === "bang") {
|
|
@@ -414,6 +467,40 @@ export class Agent {
|
|
|
414
467
|
return this.backgroundTaskManager.stopTask(id);
|
|
415
468
|
}
|
|
416
469
|
|
|
470
|
+
/** Get all workflow runs */
|
|
471
|
+
public async getWorkflowRuns(): Promise<
|
|
472
|
+
import("./workflow/types.js").WorkflowRun[]
|
|
473
|
+
> {
|
|
474
|
+
if (!this.container.has("WorkflowManager")) return [];
|
|
475
|
+
return this.container
|
|
476
|
+
.get<
|
|
477
|
+
import("./managers/workflowManager.js").WorkflowManager
|
|
478
|
+
>("WorkflowManager")!
|
|
479
|
+
.listRuns();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Get a specific workflow run by ID */
|
|
483
|
+
public getWorkflowRun(
|
|
484
|
+
runId: string,
|
|
485
|
+
): import("./workflow/types.js").WorkflowRun | undefined {
|
|
486
|
+
if (!this.container.has("WorkflowManager")) return undefined;
|
|
487
|
+
return this.container
|
|
488
|
+
.get<
|
|
489
|
+
import("./managers/workflowManager.js").WorkflowManager
|
|
490
|
+
>("WorkflowManager")!
|
|
491
|
+
.getRun(runId);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Stop a workflow run */
|
|
495
|
+
public stopWorkflowRun(runId: string): void {
|
|
496
|
+
if (!this.container.has("WorkflowManager")) return;
|
|
497
|
+
this.container
|
|
498
|
+
.get<
|
|
499
|
+
import("./managers/workflowManager.js").WorkflowManager
|
|
500
|
+
>("WorkflowManager")!
|
|
501
|
+
.stopRun(runId);
|
|
502
|
+
}
|
|
503
|
+
|
|
417
504
|
/**
|
|
418
505
|
* Static async factory method for creating Agent instances
|
|
419
506
|
*
|
|
@@ -518,12 +605,18 @@ export class Agent {
|
|
|
518
605
|
});
|
|
519
606
|
|
|
520
607
|
// Log session_start event
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
608
|
+
try {
|
|
609
|
+
const modelConfig = this.getModelConfig();
|
|
610
|
+
if (modelConfig.model) {
|
|
611
|
+
logOTelEvent("session_start", {
|
|
612
|
+
sessionId: this.messageManager.getSessionId(),
|
|
613
|
+
model: modelConfig.model,
|
|
614
|
+
workdir: this.workdir,
|
|
615
|
+
}).catch(() => {}); // Non-blocking
|
|
616
|
+
}
|
|
617
|
+
} catch {
|
|
618
|
+
// Model not configured yet - will be available after SSO login
|
|
619
|
+
}
|
|
527
620
|
}
|
|
528
621
|
|
|
529
622
|
/**
|
|
@@ -597,10 +690,13 @@ export class Agent {
|
|
|
597
690
|
|
|
598
691
|
/** Unified interrupt method, interrupts both AI messages and command execution */
|
|
599
692
|
public abortMessage(): void {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
693
|
+
if (this.aiManager.isLoading || this.isCommandRunning) {
|
|
694
|
+
// Clear queue first to prevent processQueuedMessage from dequeuing
|
|
695
|
+
// when abortAIMessage triggers onLoadingChange(false)
|
|
696
|
+
this.messageQueue.clear();
|
|
697
|
+
this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
|
|
698
|
+
}
|
|
699
|
+
this.messageQueue.transitionTo("idle"); // Reset state on abort
|
|
604
700
|
this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
|
|
605
701
|
this.abortBashCommand();
|
|
606
702
|
this.abortSlashCommand();
|
|
@@ -796,11 +892,25 @@ export class Agent {
|
|
|
796
892
|
content: string,
|
|
797
893
|
images?: Array<{ path: string; mimeType: string }>,
|
|
798
894
|
): Promise<void> {
|
|
799
|
-
// If the agent is busy, enqueue the message
|
|
895
|
+
// If the agent is busy, enqueue the message — unless it's an immediate
|
|
896
|
+
// slash command (e.g., /goal clear, /clear, /compact) that should execute
|
|
897
|
+
// right away even while AI is processing
|
|
800
898
|
if (this.aiManager.isLoading || this.isCommandRunning) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
899
|
+
const trimmed = content.trim();
|
|
900
|
+
const isImmediate =
|
|
901
|
+
trimmed.startsWith("/") &&
|
|
902
|
+
trimmed !== "/" &&
|
|
903
|
+
this.slashCommandManager.isImmediateCommand(trimmed);
|
|
904
|
+
|
|
905
|
+
if (!isImmediate) {
|
|
906
|
+
this.messageQueue.enqueue({
|
|
907
|
+
content,
|
|
908
|
+
images,
|
|
909
|
+
});
|
|
910
|
+
this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
// Immediate slash command: fall through to InteractionService
|
|
804
914
|
}
|
|
805
915
|
|
|
806
916
|
await InteractionService.sendMessage(
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const GOAL_EVALUATION_SYSTEM_PROMPT = `You are a goal evaluator. Given a goal condition and a conversation, determine whether the goal has been achieved.
|
|
2
|
+
|
|
3
|
+
Rules:
|
|
4
|
+
- Judge ONLY based on what the conversation shows — tool outputs, test results, file contents, etc.
|
|
5
|
+
- Do NOT assume work is done without evidence in the conversation.
|
|
6
|
+
- Be strict and conservative: when uncertain, return met: false.
|
|
7
|
+
- Be concise: your reason should be 1-2 sentences.
|
|
8
|
+
|
|
9
|
+
Respond with JSON only, no other text:
|
|
10
|
+
{"met": boolean, "reason": "short explanation"}`;
|
package/src/constants/tools.ts
CHANGED