wave-agent-sdk 0.17.1 → 0.17.2
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/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/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
package/src/prompts/index.ts
CHANGED
|
@@ -236,6 +236,7 @@ export function buildSystemPrompt(
|
|
|
236
236
|
tools: ToolPlugin[],
|
|
237
237
|
options: {
|
|
238
238
|
workdir?: string;
|
|
239
|
+
originalWorkdir?: string;
|
|
239
240
|
memory?: string;
|
|
240
241
|
language?: string;
|
|
241
242
|
isSubagent?: boolean;
|
|
@@ -283,7 +284,7 @@ export function buildSystemPrompt(
|
|
|
283
284
|
|
|
284
285
|
Here is useful information about the environment you are running in:
|
|
285
286
|
<env>
|
|
286
|
-
|
|
287
|
+
Primary working directory: ${options.originalWorkdir ?? options.workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
|
|
287
288
|
Is directory a git repo: ${isGitRepo}
|
|
288
289
|
Platform: ${platform}
|
|
289
290
|
Shell: ${shellName}
|
|
@@ -310,6 +311,7 @@ Today's date: ${today}
|
|
|
310
311
|
export function enhanceSystemPromptWithEnvDetails(
|
|
311
312
|
existingSystemPrompt: string,
|
|
312
313
|
workdir: string,
|
|
314
|
+
originalWorkdir?: string,
|
|
313
315
|
): string {
|
|
314
316
|
const isGitRepo = isGitRepository(workdir);
|
|
315
317
|
const platform = os.platform();
|
|
@@ -336,7 +338,7 @@ ${notes}
|
|
|
336
338
|
|
|
337
339
|
Here is useful information about the environment you are running in:
|
|
338
340
|
<env>
|
|
339
|
-
|
|
341
|
+
Primary working directory: ${originalWorkdir ?? workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
|
|
340
342
|
Is directory a git repo: ${isGitRepo}
|
|
341
343
|
Platform: ${platform}
|
|
342
344
|
Shell: ${shellName}
|
|
@@ -10,6 +10,7 @@ import { OpenAIClient } from "../utils/openaiClient.js";
|
|
|
10
10
|
import { logger } from "../utils/globalLogger.js";
|
|
11
11
|
import { addOnceAbortListener } from "../utils/abortUtils.js";
|
|
12
12
|
import type { GatewayConfig, ModelConfig } from "../types/index.js";
|
|
13
|
+
import { ConfigurationError, CONFIG_ERRORS } from "../types/index.js";
|
|
13
14
|
import {
|
|
14
15
|
transformMessagesForClaudeCache,
|
|
15
16
|
addCacheControlToLastTool,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
WEB_CONTENT_SYSTEM_PROMPT,
|
|
28
29
|
BTW_SYSTEM_PROMPT,
|
|
29
30
|
} from "../prompts/index.js";
|
|
31
|
+
import { GOAL_EVALUATION_SYSTEM_PROMPT } from "../constants/goalPrompts.js";
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Interface for debug data saved during 400 errors
|
|
@@ -160,6 +162,11 @@ export interface CallAgentOptions {
|
|
|
160
162
|
model?: string; // Custom model
|
|
161
163
|
systemPrompt?: string; // Custom system prompt
|
|
162
164
|
maxTokens?: number; // Maximum output tokens
|
|
165
|
+
toolChoice?:
|
|
166
|
+
| "auto"
|
|
167
|
+
| "none"
|
|
168
|
+
| "required"
|
|
169
|
+
| { type: "function"; function: { name: string } }; // Force tool selection
|
|
163
170
|
|
|
164
171
|
// NEW: Streaming callbacks
|
|
165
172
|
onContentUpdate?: (content: string) => void;
|
|
@@ -189,6 +196,27 @@ export interface CallAgentResult {
|
|
|
189
196
|
additionalFields?: Record<string, unknown>;
|
|
190
197
|
}
|
|
191
198
|
|
|
199
|
+
function validateModelConfig(
|
|
200
|
+
modelConfig: ModelConfig,
|
|
201
|
+
): asserts modelConfig is ModelConfig & { model: string; fastModel: string } {
|
|
202
|
+
if (!modelConfig.model) {
|
|
203
|
+
throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
|
|
204
|
+
constructor: undefined,
|
|
205
|
+
environment: process.env.WAVE_MODEL,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (!modelConfig.fastModel) {
|
|
209
|
+
throw new ConfigurationError(
|
|
210
|
+
CONFIG_ERRORS.MISSING_FAST_MODEL,
|
|
211
|
+
"fastModel",
|
|
212
|
+
{
|
|
213
|
+
constructor: undefined,
|
|
214
|
+
environment: process.env.WAVE_FAST_MODEL,
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
192
220
|
export async function callAgent(
|
|
193
221
|
options: CallAgentOptions,
|
|
194
222
|
): Promise<CallAgentResult> {
|
|
@@ -206,6 +234,9 @@ export async function callAgent(
|
|
|
206
234
|
onReasoningUpdate,
|
|
207
235
|
} = options;
|
|
208
236
|
|
|
237
|
+
// Validate model config at call time
|
|
238
|
+
validateModelConfig(modelConfig);
|
|
239
|
+
|
|
209
240
|
// Apply global 1 QPS rate limit
|
|
210
241
|
if (
|
|
211
242
|
process.env.NODE_ENV !== "test" ||
|
|
@@ -301,6 +332,11 @@ export async function callAgent(
|
|
|
301
332
|
createParams.tools = processedTools;
|
|
302
333
|
}
|
|
303
334
|
|
|
335
|
+
// Add tool_choice if specified
|
|
336
|
+
if (options.toolChoice) {
|
|
337
|
+
createParams.tool_choice = options.toolChoice;
|
|
338
|
+
}
|
|
339
|
+
|
|
304
340
|
if (isStreaming) {
|
|
305
341
|
// Handle streaming response
|
|
306
342
|
const { data: stream, response } = await openai.chat.completions
|
|
@@ -322,7 +358,6 @@ export async function callAgent(
|
|
|
322
358
|
onReasoningUpdate,
|
|
323
359
|
abortSignal,
|
|
324
360
|
responseHeaders,
|
|
325
|
-
currentModel,
|
|
326
361
|
);
|
|
327
362
|
} else {
|
|
328
363
|
// Handle non-streaming response
|
|
@@ -350,8 +385,8 @@ export async function callAgent(
|
|
|
350
385
|
}
|
|
351
386
|
: undefined;
|
|
352
387
|
|
|
353
|
-
// Extend usage with cache metrics
|
|
354
|
-
if (totalUsage &&
|
|
388
|
+
// Extend usage with cache metrics (Claude top-level + OpenAI prompt_tokens_details)
|
|
389
|
+
if (totalUsage && response.usage) {
|
|
355
390
|
totalUsage = extendUsageWithCacheMetrics(
|
|
356
391
|
totalUsage,
|
|
357
392
|
response.usage as Partial<ClaudeUsage>,
|
|
@@ -521,7 +556,6 @@ export async function callAgent(
|
|
|
521
556
|
* @param onToolUpdate Callback for tool updates
|
|
522
557
|
* @param abortSignal Optional abort signal
|
|
523
558
|
* @param responseHeaders Response headers from the initial request
|
|
524
|
-
* @param modelName Model name for cache control processing
|
|
525
559
|
* @returns Final result with accumulated content and tool calls
|
|
526
560
|
*/
|
|
527
561
|
async function processStreamingResponse(
|
|
@@ -537,7 +571,6 @@ async function processStreamingResponse(
|
|
|
537
571
|
onReasoningUpdate?: (content: string) => void,
|
|
538
572
|
abortSignal?: AbortSignal,
|
|
539
573
|
responseHeaders?: Record<string, string>,
|
|
540
|
-
modelName?: string,
|
|
541
574
|
): Promise<CallAgentResult> {
|
|
542
575
|
let accumulatedContent = "";
|
|
543
576
|
let accumulatedReasoningContent = "";
|
|
@@ -569,13 +602,11 @@ async function processStreamingResponse(
|
|
|
569
602
|
total_tokens: chunk.usage.total_tokens,
|
|
570
603
|
};
|
|
571
604
|
|
|
572
|
-
// Extend usage with cache metrics
|
|
573
|
-
|
|
574
|
-
chunkUsage
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
);
|
|
578
|
-
}
|
|
605
|
+
// Extend usage with cache metrics (Claude top-level + OpenAI prompt_tokens_details)
|
|
606
|
+
chunkUsage = extendUsageWithCacheMetrics(
|
|
607
|
+
chunkUsage,
|
|
608
|
+
chunk.usage as Partial<ClaudeUsage>,
|
|
609
|
+
);
|
|
579
610
|
|
|
580
611
|
usage = chunkUsage;
|
|
581
612
|
}
|
|
@@ -775,6 +806,9 @@ export async function compactMessages(
|
|
|
775
806
|
): Promise<CompactMessagesResult> {
|
|
776
807
|
const { gatewayConfig, modelConfig, messages, abortSignal } = options;
|
|
777
808
|
|
|
809
|
+
// Validate model config at call time
|
|
810
|
+
validateModelConfig(modelConfig);
|
|
811
|
+
|
|
778
812
|
// Apply global 1 QPS rate limit
|
|
779
813
|
if (
|
|
780
814
|
process.env.NODE_ENV !== "test" ||
|
|
@@ -901,6 +935,9 @@ export async function processWebContent(
|
|
|
901
935
|
): Promise<ProcessWebContentResult> {
|
|
902
936
|
const { gatewayConfig, modelConfig, content, prompt, abortSignal } = options;
|
|
903
937
|
|
|
938
|
+
// Validate model config at call time
|
|
939
|
+
validateModelConfig(modelConfig);
|
|
940
|
+
|
|
904
941
|
// Apply global 1 QPS rate limit
|
|
905
942
|
if (
|
|
906
943
|
process.env.NODE_ENV !== "test" ||
|
|
@@ -1008,6 +1045,9 @@ export async function btw(options: BtwOptions): Promise<BtwResult> {
|
|
|
1008
1045
|
const { gatewayConfig, modelConfig, messages, question, abortSignal } =
|
|
1009
1046
|
options;
|
|
1010
1047
|
|
|
1048
|
+
// Validate model config at call time
|
|
1049
|
+
validateModelConfig(modelConfig);
|
|
1050
|
+
|
|
1011
1051
|
// Apply global 1 QPS rate limit
|
|
1012
1052
|
if (
|
|
1013
1053
|
process.env.NODE_ENV !== "test" ||
|
|
@@ -1092,3 +1132,117 @@ export async function btw(options: BtwOptions): Promise<BtwResult> {
|
|
|
1092
1132
|
throw error;
|
|
1093
1133
|
}
|
|
1094
1134
|
}
|
|
1135
|
+
|
|
1136
|
+
export interface EvaluateGoalOptions {
|
|
1137
|
+
gatewayConfig: GatewayConfig;
|
|
1138
|
+
modelConfig: ModelConfig;
|
|
1139
|
+
model: string;
|
|
1140
|
+
goalCondition: string;
|
|
1141
|
+
messages: ChatCompletionMessageParam[];
|
|
1142
|
+
abortSignal?: AbortSignal;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export interface EvaluateGoalResult {
|
|
1146
|
+
content: string;
|
|
1147
|
+
usage?: {
|
|
1148
|
+
prompt_tokens: number;
|
|
1149
|
+
completion_tokens: number;
|
|
1150
|
+
total_tokens: number;
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
export async function evaluateGoal(
|
|
1155
|
+
options: EvaluateGoalOptions,
|
|
1156
|
+
): Promise<EvaluateGoalResult> {
|
|
1157
|
+
const {
|
|
1158
|
+
gatewayConfig,
|
|
1159
|
+
modelConfig,
|
|
1160
|
+
model,
|
|
1161
|
+
goalCondition,
|
|
1162
|
+
messages,
|
|
1163
|
+
abortSignal,
|
|
1164
|
+
} = options;
|
|
1165
|
+
|
|
1166
|
+
// Create OpenAI client with injected configuration (no rate limiter — bypasses 1 QPS)
|
|
1167
|
+
const openai = new OpenAIClient({
|
|
1168
|
+
apiKey: gatewayConfig.apiKey,
|
|
1169
|
+
baseURL: gatewayConfig.baseURL,
|
|
1170
|
+
defaultHeaders: gatewayConfig.defaultHeaders,
|
|
1171
|
+
fetchOptions: gatewayConfig.fetchOptions,
|
|
1172
|
+
fetch: gatewayConfig.fetch,
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
const {
|
|
1176
|
+
model: _model,
|
|
1177
|
+
fastModel: _fastModel,
|
|
1178
|
+
maxTokens: _maxTokens,
|
|
1179
|
+
permissionMode: _permissionMode,
|
|
1180
|
+
...extraParams
|
|
1181
|
+
} = modelConfig;
|
|
1182
|
+
void _model;
|
|
1183
|
+
void _fastModel;
|
|
1184
|
+
void _maxTokens;
|
|
1185
|
+
void _permissionMode;
|
|
1186
|
+
|
|
1187
|
+
const openaiModelConfig = getModelConfig(model, {
|
|
1188
|
+
temperature: 0,
|
|
1189
|
+
max_tokens: 200,
|
|
1190
|
+
...extraParams,
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
// Strip images from messages to reduce token usage (same as compact)
|
|
1194
|
+
const cleanedMessages = messages.map((msg) => {
|
|
1195
|
+
if (Array.isArray(msg.content)) {
|
|
1196
|
+
const textParts = msg.content.filter(
|
|
1197
|
+
(part) => part.type === "text",
|
|
1198
|
+
) as import("openai/resources.js").ChatCompletionContentPartText[];
|
|
1199
|
+
const text = textParts.map((p) => p.text).join("\n");
|
|
1200
|
+
return { ...msg, content: text || "(empty message)" };
|
|
1201
|
+
}
|
|
1202
|
+
return msg;
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
try {
|
|
1206
|
+
const response = await openai.chat.completions.create(
|
|
1207
|
+
{
|
|
1208
|
+
...openaiModelConfig,
|
|
1209
|
+
messages: [
|
|
1210
|
+
{
|
|
1211
|
+
role: "system",
|
|
1212
|
+
content: GOAL_EVALUATION_SYSTEM_PROMPT,
|
|
1213
|
+
},
|
|
1214
|
+
...cleanedMessages,
|
|
1215
|
+
{
|
|
1216
|
+
role: "user",
|
|
1217
|
+
content: `Goal condition: ${goalCondition}\n\nHas this goal been achieved based on the conversation above?`,
|
|
1218
|
+
},
|
|
1219
|
+
],
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
signal: abortSignal,
|
|
1223
|
+
},
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
const result = response.choices[0]?.message?.content?.trim();
|
|
1227
|
+
if (!result) {
|
|
1228
|
+
throw new Error("Goal evaluation returned empty response");
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const usage = response.usage
|
|
1232
|
+
? {
|
|
1233
|
+
prompt_tokens: response.usage.prompt_tokens,
|
|
1234
|
+
completion_tokens: response.usage.completion_tokens,
|
|
1235
|
+
total_tokens: response.usage.total_tokens,
|
|
1236
|
+
}
|
|
1237
|
+
: undefined;
|
|
1238
|
+
|
|
1239
|
+
return { content: result, usage };
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
if ((error as Error).name === "AbortError") {
|
|
1242
|
+
logger.info("Goal evaluation was aborted");
|
|
1243
|
+
throw new Error("Goal evaluation was aborted");
|
|
1244
|
+
}
|
|
1245
|
+
logger.error("Goal evaluation failed:", error);
|
|
1246
|
+
throw error;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
@@ -33,8 +33,6 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
GatewayConfig,
|
|
35
35
|
ModelConfig,
|
|
36
|
-
ConfigurationError,
|
|
37
|
-
CONFIG_ERRORS,
|
|
38
36
|
PermissionMode,
|
|
39
37
|
AgentOptions,
|
|
40
38
|
} from "../types/index.js";
|
|
@@ -410,7 +408,7 @@ export class ConfigurationService {
|
|
|
410
408
|
* @param fetchOptions - Fetch options override (optional)
|
|
411
409
|
* @param fetch - Custom fetch implementation override (optional)
|
|
412
410
|
* @returns Resolved gateway configuration
|
|
413
|
-
* @
|
|
411
|
+
* @returns Resolved model configuration (model/fastModel may be undefined if not yet configured)
|
|
414
412
|
*/
|
|
415
413
|
resolveGatewayConfig(
|
|
416
414
|
apiKey?: string,
|
|
@@ -524,25 +522,6 @@ export class ConfigurationService {
|
|
|
524
522
|
const resolvedFastModel =
|
|
525
523
|
fastModel || this.options.fastModel || process.env.WAVE_FAST_MODEL;
|
|
526
524
|
|
|
527
|
-
// Validate required fields
|
|
528
|
-
if (!resolvedAgentModel) {
|
|
529
|
-
throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
|
|
530
|
-
constructor: model,
|
|
531
|
-
environment: process.env.WAVE_MODEL,
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!resolvedFastModel) {
|
|
536
|
-
throw new ConfigurationError(
|
|
537
|
-
CONFIG_ERRORS.MISSING_FAST_MODEL,
|
|
538
|
-
"fastModel",
|
|
539
|
-
{
|
|
540
|
-
constructor: fastModel,
|
|
541
|
-
environment: process.env.WAVE_FAST_MODEL,
|
|
542
|
-
},
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
525
|
// Resolve max output tokens
|
|
547
526
|
const resolvedMaxTokens = this.resolveMaxOutputTokens(maxTokens);
|
|
548
527
|
|
|
@@ -555,6 +534,7 @@ export class ConfigurationService {
|
|
|
555
534
|
|
|
556
535
|
// Merge model-specific settings from configuration
|
|
557
536
|
const modelSpecificConfig =
|
|
537
|
+
resolvedAgentModel &&
|
|
558
538
|
this.currentConfiguration?.models?.[resolvedAgentModel];
|
|
559
539
|
|
|
560
540
|
if (modelSpecificConfig) {
|
package/src/services/hook.ts
CHANGED
|
@@ -72,6 +72,11 @@ async function buildHookJsonInput(
|
|
|
72
72
|
jsonInput.user_prompt = context.userPrompt;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Add plan_file_path if present
|
|
76
|
+
if (context.planFilePath !== undefined) {
|
|
77
|
+
jsonInput.plan_file_path = context.planFilePath;
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
// Add subagent_type if present
|
|
76
81
|
if (context.subagentType !== undefined) {
|
|
77
82
|
jsonInput.subagent_type = context.subagentType;
|
package/src/services/session.ts
CHANGED
|
@@ -45,6 +45,7 @@ export interface SessionMetadata {
|
|
|
45
45
|
sessionType: "main" | "subagent";
|
|
46
46
|
subagentType?: string;
|
|
47
47
|
workdir: string;
|
|
48
|
+
createdAt: Date;
|
|
48
49
|
lastActiveAt: Date;
|
|
49
50
|
latestTotalTokens: number;
|
|
50
51
|
firstMessage?: string;
|
|
@@ -53,7 +54,10 @@ export interface SessionMetadata {
|
|
|
53
54
|
export interface SessionIndex {
|
|
54
55
|
sessions: Record<
|
|
55
56
|
string,
|
|
56
|
-
Omit<SessionMetadata, "id" | "lastActiveAt"> & {
|
|
57
|
+
Omit<SessionMetadata, "id" | "lastActiveAt" | "createdAt"> & {
|
|
58
|
+
lastActiveAt: string;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
}
|
|
57
61
|
>;
|
|
58
62
|
lastUpdated: string;
|
|
59
63
|
}
|
|
@@ -77,6 +81,27 @@ export function generateSessionId(): string {
|
|
|
77
81
|
return `${ts}-${shortId}`;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Parse the timestamp prefix from a session ID back into a Date
|
|
86
|
+
* Format: {YYYYMMDDHHmmss}-{8hex} (e.g. 20260527143025-a1b2c3d4)
|
|
87
|
+
* @returns Date from the session ID timestamp prefix
|
|
88
|
+
*/
|
|
89
|
+
export function parseSessionIdTimestamp(sessionId: string): Date {
|
|
90
|
+
const match = sessionId.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
|
|
91
|
+
if (!match) {
|
|
92
|
+
return new Date(); // Fallback to now for legacy UUID-format session IDs
|
|
93
|
+
}
|
|
94
|
+
const [, year, month, day, hour, minute, second] = match;
|
|
95
|
+
return new Date(
|
|
96
|
+
Number(year),
|
|
97
|
+
Number(month) - 1,
|
|
98
|
+
Number(day),
|
|
99
|
+
Number(hour),
|
|
100
|
+
Number(minute),
|
|
101
|
+
Number(second),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
80
105
|
/**
|
|
81
106
|
* Generate filename for subagent sessions
|
|
82
107
|
* @param sessionId - UUID session identifier
|
|
@@ -114,6 +139,7 @@ async function updateSessionIndex(
|
|
|
114
139
|
const { id, ...rest } = metadata;
|
|
115
140
|
index.sessions[id] = {
|
|
116
141
|
...rest,
|
|
142
|
+
createdAt: metadata.createdAt.toISOString(),
|
|
117
143
|
lastActiveAt: metadata.lastActiveAt.toISOString(),
|
|
118
144
|
firstMessage: metadata.firstMessage || index.sessions[id]?.firstMessage,
|
|
119
145
|
parentSessionId: metadata.parentSessionId,
|
|
@@ -247,8 +273,9 @@ export async function appendMessages(
|
|
|
247
273
|
const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
|
|
248
274
|
const lastMessage = newMessages[newMessages.length - 1];
|
|
249
275
|
|
|
250
|
-
// Get first message content
|
|
276
|
+
// Get first message content and createdAt from existing index
|
|
251
277
|
let firstMessage: string | undefined;
|
|
278
|
+
let createdAt: Date | undefined;
|
|
252
279
|
try {
|
|
253
280
|
const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
|
|
254
281
|
const content = await fs.readFile(indexPath, "utf8");
|
|
@@ -257,18 +284,27 @@ export async function appendMessages(
|
|
|
257
284
|
firstMessage =
|
|
258
285
|
(await getFirstMessageContent(sessionId, workdir)) || undefined;
|
|
259
286
|
}
|
|
287
|
+
if (index.sessions[sessionId]?.createdAt) {
|
|
288
|
+
createdAt = new Date(index.sessions[sessionId].createdAt);
|
|
289
|
+
}
|
|
260
290
|
} catch {
|
|
261
291
|
// If index doesn't exist, this might be the first message
|
|
262
292
|
firstMessage =
|
|
263
293
|
(await getFirstMessageContent(sessionId, workdir)) || undefined;
|
|
264
294
|
}
|
|
265
295
|
|
|
296
|
+
// Derive createdAt from session ID timestamp if not in index
|
|
297
|
+
if (!createdAt) {
|
|
298
|
+
createdAt = new Date(parseSessionIdTimestamp(sessionId));
|
|
299
|
+
}
|
|
300
|
+
|
|
266
301
|
await updateSessionIndex(projectDir.encodedPath, {
|
|
267
302
|
id: sessionId,
|
|
268
303
|
rootSessionId,
|
|
269
304
|
parentSessionId,
|
|
270
305
|
sessionType,
|
|
271
306
|
workdir,
|
|
307
|
+
createdAt,
|
|
272
308
|
lastActiveAt: new Date(lastMessage.timestamp),
|
|
273
309
|
latestTotalTokens: lastMessage.usage
|
|
274
310
|
? extractLatestTotalTokens([lastMessage])
|
|
@@ -447,6 +483,7 @@ export async function listSessionsFromJsonl(
|
|
|
447
483
|
.map(([id, meta]) => ({
|
|
448
484
|
id,
|
|
449
485
|
...meta,
|
|
486
|
+
createdAt: new Date(meta.createdAt),
|
|
450
487
|
lastActiveAt: new Date(meta.lastActiveAt),
|
|
451
488
|
}));
|
|
452
489
|
|
|
@@ -520,6 +557,7 @@ export async function listSessionsFromJsonl(
|
|
|
520
557
|
sessionType: "main",
|
|
521
558
|
subagentType: undefined, // No longer stored in metadata
|
|
522
559
|
workdir: projectDir.originalPath,
|
|
560
|
+
createdAt: new Date(parseSessionIdTimestamp(sessionId)),
|
|
523
561
|
lastActiveAt,
|
|
524
562
|
latestTotalTokens: lastMessage?.usage
|
|
525
563
|
? extractLatestTotalTokens([lastMessage])
|
|
@@ -558,6 +596,7 @@ export async function listSessionsFromJsonl(
|
|
|
558
596
|
const { id, ...rest } = session;
|
|
559
597
|
index.sessions[id] = {
|
|
560
598
|
...rest,
|
|
599
|
+
createdAt: session.createdAt.toISOString(),
|
|
561
600
|
lastActiveAt: session.lastActiveAt.toISOString(),
|
|
562
601
|
};
|
|
563
602
|
}
|
|
@@ -613,6 +652,7 @@ export async function listAllSessions(): Promise<SessionMetadata[]> {
|
|
|
613
652
|
allSessions.push({
|
|
614
653
|
id,
|
|
615
654
|
...meta,
|
|
655
|
+
createdAt: new Date(meta.createdAt),
|
|
616
656
|
lastActiveAt,
|
|
617
657
|
});
|
|
618
658
|
}
|
package/src/tools/bashTool.ts
CHANGED
|
@@ -18,6 +18,17 @@ import {
|
|
|
18
18
|
|
|
19
19
|
const BASH_DEFAULT_TIMEOUT_MS = 120000;
|
|
20
20
|
|
|
21
|
+
// Commands that should not be auto-backgrounded on timeout (e.g. sleep should just be killed)
|
|
22
|
+
const DISALLOWED_AUTO_BACKGROUND_COMMANDS = ["sleep"];
|
|
23
|
+
|
|
24
|
+
function isAutobackgroundingAllowed(command: string): boolean {
|
|
25
|
+
const trimmed = command.trim();
|
|
26
|
+
// Get the first word of the command
|
|
27
|
+
const baseCommand = trimmed.split(/\s+/)[0];
|
|
28
|
+
if (!baseCommand) return true;
|
|
29
|
+
return !DISALLOWED_AUTO_BACKGROUND_COMMANDS.includes(baseCommand);
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
/**
|
|
22
33
|
* Bash command execution tool - supports both foreground and background execution
|
|
23
34
|
*/
|
|
@@ -125,9 +136,12 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
125
136
|
const runInBackground = args.run_in_background as boolean | undefined;
|
|
126
137
|
const description = args.description as string | undefined;
|
|
127
138
|
// Set default timeout: BASH_DEFAULT_TIMEOUT_MS for foreground, no timeout for background
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
// When run_in_background is explicitly set, cancel any timeout — the intent is to
|
|
140
|
+
// let the process run to completion (matching Claude Code behavior where background()
|
|
141
|
+
// clears the timeout via cleanupListeners).
|
|
142
|
+
const timeout = runInBackground
|
|
143
|
+
? undefined
|
|
144
|
+
: ((args.timeout as number | undefined) ?? BASH_DEFAULT_TIMEOUT_MS);
|
|
131
145
|
|
|
132
146
|
if (!command || typeof command !== "string") {
|
|
133
147
|
return {
|
|
@@ -195,7 +209,7 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
195
209
|
};
|
|
196
210
|
}
|
|
197
211
|
|
|
198
|
-
const { id: taskId } = backgroundTaskManager.startShell(command
|
|
212
|
+
const { id: taskId } = backgroundTaskManager.startShell(command);
|
|
199
213
|
const task = backgroundTaskManager.getTask(taskId);
|
|
200
214
|
const outputPath = task?.outputPath;
|
|
201
215
|
const backgroundMsg = [
|
|
@@ -300,12 +314,47 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
300
314
|
});
|
|
301
315
|
}
|
|
302
316
|
|
|
303
|
-
// Set up timeout
|
|
317
|
+
// Set up timeout — auto-background if allowed, otherwise kill
|
|
304
318
|
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
319
|
+
const shouldAutoBackground =
|
|
320
|
+
!!context.backgroundTaskManager && isAutobackgroundingAllowed(command);
|
|
305
321
|
if (timeout && timeout > 0) {
|
|
306
322
|
timeoutHandle = setTimeout(() => {
|
|
307
|
-
if (!isAborted) {
|
|
308
|
-
|
|
323
|
+
if (!isAborted && !isBackgrounded) {
|
|
324
|
+
if (shouldAutoBackground) {
|
|
325
|
+
// Auto-background: move the process to background task manager instead of killing it
|
|
326
|
+
isBackgrounded = true;
|
|
327
|
+
if (timeoutHandle) {
|
|
328
|
+
clearTimeout(timeoutHandle);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Unregister foreground task since it's now backgrounded
|
|
332
|
+
if (context.foregroundTaskManager) {
|
|
333
|
+
context.foregroundTaskManager.unregisterForegroundTask(
|
|
334
|
+
foregroundTaskId,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const backgroundTaskManager = context.backgroundTaskManager!;
|
|
339
|
+
const taskId = backgroundTaskManager.adoptProcess(
|
|
340
|
+
child,
|
|
341
|
+
command,
|
|
342
|
+
outputBuffer,
|
|
343
|
+
errorBuffer,
|
|
344
|
+
);
|
|
345
|
+
const task = backgroundTaskManager.getTask(taskId);
|
|
346
|
+
const outputPath = task?.outputPath;
|
|
347
|
+
logger.info(
|
|
348
|
+
`[Bash] Command timed out after ${timeout}ms, auto-backgrounded as ${taskId}`,
|
|
349
|
+
);
|
|
350
|
+
resolve({
|
|
351
|
+
success: true,
|
|
352
|
+
content: `Command timed out after ${timeout / 1000} seconds and was moved to background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
|
|
353
|
+
shortResult: `Process ${taskId} auto-backgrounded (timeout)`,
|
|
354
|
+
});
|
|
355
|
+
} else {
|
|
356
|
+
handleAbort("Command timed out");
|
|
357
|
+
}
|
|
309
358
|
}
|
|
310
359
|
}, timeout);
|
|
311
360
|
}
|
|
@@ -330,8 +379,14 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
330
379
|
if (child.pid && !child.killed) {
|
|
331
380
|
try {
|
|
332
381
|
process.kill(-child.pid, "SIGKILL");
|
|
333
|
-
} catch (killError) {
|
|
334
|
-
|
|
382
|
+
} catch (killError: unknown) {
|
|
383
|
+
// ESRCH means the process already exited — not an error
|
|
384
|
+
if (
|
|
385
|
+
!(killError instanceof Error) ||
|
|
386
|
+
(killError as NodeJS.ErrnoException).code !== "ESRCH"
|
|
387
|
+
) {
|
|
388
|
+
logger.error("Failed to force kill process:", killError);
|
|
389
|
+
}
|
|
335
390
|
}
|
|
336
391
|
}
|
|
337
392
|
}, 1000);
|
package/src/tools/readTool.ts
CHANGED
|
@@ -295,8 +295,7 @@ Usage:
|
|
|
295
295
|
logger.warn(`File ${filePath} exists but has empty contents`);
|
|
296
296
|
return {
|
|
297
297
|
success: true,
|
|
298
|
-
content:
|
|
299
|
-
"⚠️ System reminder: This file exists but has empty contents.",
|
|
298
|
+
content: "System reminder: This file exists but has empty contents.",
|
|
300
299
|
shortResult: "Empty file",
|
|
301
300
|
metadata: {
|
|
302
301
|
type: "text",
|