wave-agent-sdk 0.14.4 → 0.15.1
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/settings/SKILLS.md +31 -6
- package/dist/agent.d.ts +4 -5
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -15
- package/dist/constants/toolLimits.d.ts +10 -0
- package/dist/constants/toolLimits.d.ts.map +1 -0
- package/dist/constants/toolLimits.js +9 -0
- package/dist/managers/aiManager.d.ts +0 -5
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +0 -22
- package/dist/managers/hookManager.d.ts +0 -4
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +0 -25
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +7 -6
- package/dist/managers/permissionManager.d.ts +1 -1
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +5 -5
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +16 -4
- package/dist/managers/subagentManager.d.ts +6 -1
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +17 -18
- package/dist/prompts/index.d.ts +1 -3
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +3 -6
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +10 -8
- package/dist/services/hook.d.ts +0 -4
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +0 -10
- package/dist/services/jsonlHandler.d.ts +4 -4
- package/dist/services/jsonlHandler.d.ts.map +1 -1
- package/dist/services/jsonlHandler.js +4 -13
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +7 -13
- package/dist/tools/agentTool.d.ts.map +1 -1
- package/dist/tools/agentTool.js +16 -4
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +4 -50
- package/dist/tools/editTool.js +1 -1
- package/dist/tools/skillTool.d.ts.map +1 -1
- package/dist/tools/skillTool.js +16 -4
- package/dist/tools/types.d.ts +0 -3
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/agent.d.ts +0 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +1 -5
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +0 -1
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/session.d.ts +0 -4
- package/dist/types/session.d.ts.map +1 -1
- package/dist/utils/editUtils.d.ts +5 -2
- package/dist/utils/editUtils.d.ts.map +1 -1
- package/dist/utils/editUtils.js +3 -57
- package/dist/utils/markdownParser.d.ts +8 -1
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +64 -11
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +5 -0
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +0 -11
- package/package.json +1 -1
- package/src/agent.ts +12 -17
- package/src/constants/toolLimits.ts +12 -0
- package/src/managers/aiManager.ts +0 -38
- package/src/managers/hookManager.ts +0 -32
- package/src/managers/messageManager.ts +7 -8
- package/src/managers/permissionManager.ts +6 -6
- package/src/managers/slashCommandManager.ts +24 -5
- package/src/managers/subagentManager.ts +28 -23
- package/src/prompts/index.ts +3 -8
- package/src/services/aiService.ts +10 -12
- package/src/services/hook.ts +0 -15
- package/src/services/jsonlHandler.ts +12 -24
- package/src/services/session.ts +9 -15
- package/src/tools/agentTool.ts +24 -5
- package/src/tools/bashTool.ts +4 -56
- package/src/tools/editTool.ts +1 -1
- package/src/tools/skillTool.ts +24 -4
- package/src/tools/types.ts +0 -3
- package/src/types/agent.ts +0 -1
- package/src/types/hooks.ts +1 -7
- package/src/types/messaging.ts +1 -0
- package/src/types/session.ts +0 -8
- package/src/utils/editUtils.ts +3 -73
- package/src/utils/markdownParser.ts +85 -11
- package/src/utils/messageOperations.ts +5 -0
- package/src/utils/openaiClient.ts +0 -11
|
@@ -131,7 +131,7 @@ export class PermissionManager {
|
|
|
131
131
|
private planFilePath?: string;
|
|
132
132
|
private worktreeName?: string;
|
|
133
133
|
private mainRepoRoot?: string;
|
|
134
|
-
private
|
|
134
|
+
private workdir?: string;
|
|
135
135
|
private onConfiguredPermissionModeChange?: (mode: PermissionMode) => void;
|
|
136
136
|
private _logger?: Logger;
|
|
137
137
|
|
|
@@ -153,7 +153,7 @@ export class PermissionManager {
|
|
|
153
153
|
|
|
154
154
|
this.worktreeName = this.container.get<string>("WorktreeName");
|
|
155
155
|
this.mainRepoRoot = this.container.get<string>("MainRepoRoot");
|
|
156
|
-
this.
|
|
156
|
+
this.workdir = this.container.get<string>("Workdir");
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
@@ -277,7 +277,7 @@ export class PermissionManager {
|
|
|
277
277
|
* Update the additional directories (e.g., when configuration reloads)
|
|
278
278
|
*/
|
|
279
279
|
updateAdditionalDirectories(directories: string[]): void {
|
|
280
|
-
const workdir = this.
|
|
280
|
+
const workdir = this.workdir;
|
|
281
281
|
this.additionalDirectories = directories.map((dir) => {
|
|
282
282
|
if (workdir && !path.isAbsolute(dir)) {
|
|
283
283
|
return path.resolve(workdir, dir);
|
|
@@ -290,7 +290,7 @@ export class PermissionManager {
|
|
|
290
290
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
291
291
|
*/
|
|
292
292
|
public addSystemAdditionalDirectory(directory: string): void {
|
|
293
|
-
const workdir = this.
|
|
293
|
+
const workdir = this.workdir;
|
|
294
294
|
const resolvedPath =
|
|
295
295
|
workdir && !path.isAbsolute(directory)
|
|
296
296
|
? path.resolve(workdir, directory)
|
|
@@ -329,7 +329,7 @@ export class PermissionManager {
|
|
|
329
329
|
targetPath: string,
|
|
330
330
|
workdir?: string,
|
|
331
331
|
): { isInside: boolean; resolvedPath: string } {
|
|
332
|
-
const effectiveWorkdir = this.
|
|
332
|
+
const effectiveWorkdir = this.workdir || workdir;
|
|
333
333
|
|
|
334
334
|
// Resolve the target path relative to effectiveWorkdir if it's not absolute
|
|
335
335
|
const absolutePath =
|
|
@@ -1068,7 +1068,7 @@ export class PermissionManager {
|
|
|
1068
1068
|
* @param rule - The rule to add (e.g., "Bash(ls)")
|
|
1069
1069
|
*/
|
|
1070
1070
|
public async addPermissionRule(rule: string): Promise<void> {
|
|
1071
|
-
const workdir = this.
|
|
1071
|
+
const workdir = this.workdir;
|
|
1072
1072
|
if (!workdir) {
|
|
1073
1073
|
throw new Error("Working directory not set in PermissionManager");
|
|
1074
1074
|
}
|
|
@@ -224,20 +224,39 @@ export class SlashCommandManager {
|
|
|
224
224
|
const messages = subagent.messages;
|
|
225
225
|
const tokens =
|
|
226
226
|
subagent.messageManager.getLatestTotalTokens();
|
|
227
|
-
const
|
|
227
|
+
const usedTools = subagent.usedTools;
|
|
228
228
|
|
|
229
229
|
const toolCount = countToolBlocks(messages);
|
|
230
230
|
const summary = formatToolTokenSummary(toolCount, tokens);
|
|
231
231
|
|
|
232
|
+
const getDisplayParam = (t: {
|
|
233
|
+
name: string;
|
|
234
|
+
parameters: string;
|
|
235
|
+
compactParams?: string;
|
|
236
|
+
stage?: string;
|
|
237
|
+
}) => {
|
|
238
|
+
if (
|
|
239
|
+
(t.stage === "end" || t.stage === "running") &&
|
|
240
|
+
t.compactParams
|
|
241
|
+
) {
|
|
242
|
+
return t.compactParams;
|
|
243
|
+
}
|
|
244
|
+
const flat = t.parameters.replace(/\n/g, "\\n");
|
|
245
|
+
return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
|
|
246
|
+
};
|
|
247
|
+
|
|
232
248
|
let shortResult = "";
|
|
233
249
|
if (toolCount > 2) {
|
|
234
250
|
shortResult += "... ";
|
|
235
251
|
}
|
|
236
|
-
if (lastTools.length > 0) {
|
|
237
|
-
shortResult += `${lastTools.join(", ")} `;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
252
|
shortResult += summary;
|
|
253
|
+
if (usedTools.length > 0) {
|
|
254
|
+
shortResult +=
|
|
255
|
+
"\n" +
|
|
256
|
+
usedTools
|
|
257
|
+
.map((t) => `${t.name} ${getDisplayParam(t)}`)
|
|
258
|
+
.join("\n");
|
|
259
|
+
}
|
|
241
260
|
|
|
242
261
|
this.messageManager.updateToolBlock({
|
|
243
262
|
id: toolBlockId,
|
|
@@ -68,7 +68,12 @@ export interface SubagentInstance {
|
|
|
68
68
|
toolManager: ToolManager;
|
|
69
69
|
status: "initializing" | "active" | "completed" | "error" | "aborted";
|
|
70
70
|
messages: Message[];
|
|
71
|
-
|
|
71
|
+
usedTools: {
|
|
72
|
+
name: string;
|
|
73
|
+
parameters: string;
|
|
74
|
+
compactParams?: string;
|
|
75
|
+
stage?: string;
|
|
76
|
+
}[]; // Track tools with display info
|
|
72
77
|
subagentType: string; // Store the subagent type for hook context
|
|
73
78
|
description: string; // Store the AI-generated description
|
|
74
79
|
allowedTools?: string[]; // Optional permission rules (e.g. git:*)
|
|
@@ -388,7 +393,7 @@ export class SubagentManager {
|
|
|
388
393
|
toolManager,
|
|
389
394
|
status: "initializing",
|
|
390
395
|
messages: [],
|
|
391
|
-
|
|
396
|
+
usedTools: [], // Initialize usedTools
|
|
392
397
|
subagentType: parameters.subagent_type, // Store the subagent type
|
|
393
398
|
description: parameters.description, // Store the AI-generated description
|
|
394
399
|
allowedTools: parameters.allowedTools, // Store optional permission rules
|
|
@@ -790,27 +795,16 @@ export class SubagentManager {
|
|
|
790
795
|
},
|
|
791
796
|
|
|
792
797
|
onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
if (instance) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
instance.onUpdate?.();
|
|
804
|
-
|
|
805
|
-
// Log tool execution to file
|
|
806
|
-
if (instance.logStream) {
|
|
807
|
-
const displayParams =
|
|
808
|
-
params.compactParams ||
|
|
809
|
-
(params.parameters || "{}").substring(0, 100);
|
|
810
|
-
instance.logStream.write(
|
|
811
|
-
`[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
|
|
812
|
-
);
|
|
813
|
-
}
|
|
798
|
+
const instance = this.instances.get(subagentId);
|
|
799
|
+
if (instance) {
|
|
800
|
+
// Log tool execution to file only when finalized
|
|
801
|
+
if (instance.logStream && params.stage === "end") {
|
|
802
|
+
const displayParams =
|
|
803
|
+
params.compactParams ||
|
|
804
|
+
(params.parameters || "").substring(0, 100);
|
|
805
|
+
instance.logStream.write(
|
|
806
|
+
`[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
|
|
807
|
+
);
|
|
814
808
|
}
|
|
815
809
|
}
|
|
816
810
|
|
|
@@ -825,6 +819,17 @@ export class SubagentManager {
|
|
|
825
819
|
const instance = this.instances.get(subagentId);
|
|
826
820
|
if (instance) {
|
|
827
821
|
instance.messages = messages;
|
|
822
|
+
// Compute usedTools from messages (last 2 tool blocks)
|
|
823
|
+
const toolBlocks = messages.flatMap(
|
|
824
|
+
(m) => m.blocks?.filter((b) => b.type === "tool") ?? [],
|
|
825
|
+
);
|
|
826
|
+
const last2 = toolBlocks.slice(-2);
|
|
827
|
+
instance.usedTools = last2.map((tb) => ({
|
|
828
|
+
name: tb.name ?? "",
|
|
829
|
+
parameters: tb.parameters ?? "",
|
|
830
|
+
compactParams: tb.compactParams,
|
|
831
|
+
stage: tb.stage,
|
|
832
|
+
}));
|
|
828
833
|
// Trigger the onUpdate callback if provided
|
|
829
834
|
instance.onUpdate?.();
|
|
830
835
|
// Forward subagent message changes to parent via callbacks
|
package/src/prompts/index.ts
CHANGED
|
@@ -19,8 +19,6 @@ import {
|
|
|
19
19
|
GREP_TOOL_NAME,
|
|
20
20
|
} from "../constants/tools.js";
|
|
21
21
|
|
|
22
|
-
export const MAX_PARALLEL_TOOL_CALLS = 3;
|
|
23
|
-
|
|
24
22
|
export const BASE_SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
|
|
25
23
|
|
|
26
24
|
export const DOING_TASKS_PROMPT = `# Doing tasks
|
|
@@ -60,7 +58,6 @@ export const TOOL_POLICY = `# Using your tools
|
|
|
60
58
|
- To search the content of files, use ${GREP_TOOL_NAME} instead of grep or rg
|
|
61
59
|
- Reserve using the ${BASH_TOOL_NAME} exclusively for system commands and terminal operations that require shell execution. If you are unsure and there is a relevant dedicated tool, default to using the dedicated tool and only fallback on using the ${BASH_TOOL_NAME} tool for these if it is absolutely necessary.
|
|
62
60
|
- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency.
|
|
63
|
-
- **Limit**: You MUST NOT call more than ${MAX_PARALLEL_TOOL_CALLS} tools in parallel in a single response.
|
|
64
61
|
- However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
|
|
65
62
|
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks.`;
|
|
66
63
|
|
|
@@ -238,7 +235,6 @@ export function buildSystemPrompt(
|
|
|
238
235
|
tools: ToolPlugin[],
|
|
239
236
|
options: {
|
|
240
237
|
workdir?: string;
|
|
241
|
-
originalWorkdir?: string;
|
|
242
238
|
memory?: string;
|
|
243
239
|
language?: string;
|
|
244
240
|
isSubagent?: boolean;
|
|
@@ -276,9 +272,8 @@ export function buildSystemPrompt(
|
|
|
276
272
|
prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists, options.isSubagent)}`;
|
|
277
273
|
}
|
|
278
274
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const isGitRepo = isGitRepository(workdirForPrompt);
|
|
275
|
+
if (options.workdir) {
|
|
276
|
+
const isGitRepo = isGitRepository(options.workdir);
|
|
282
277
|
const platform = os.platform();
|
|
283
278
|
const osVersion = `${os.type()} ${os.release()}`;
|
|
284
279
|
const today = new Date().toISOString().split("T")[0];
|
|
@@ -293,7 +288,7 @@ export function buildSystemPrompt(
|
|
|
293
288
|
|
|
294
289
|
Here is useful information about the environment you are running in:
|
|
295
290
|
<env>
|
|
296
|
-
Working directory: ${
|
|
291
|
+
Working directory: ${options.workdir}
|
|
297
292
|
Is directory a git repo: ${isGitRepo}
|
|
298
293
|
Platform: ${platform}
|
|
299
294
|
Shell: ${shellName}
|
|
@@ -377,10 +377,7 @@ export async function callAgent(
|
|
|
377
377
|
result.content = finalContent;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
if (
|
|
381
|
-
typeof finalReasoningContent === "string" &&
|
|
382
|
-
finalReasoningContent.length > 0
|
|
383
|
-
) {
|
|
380
|
+
if (typeof finalReasoningContent === "string") {
|
|
384
381
|
result.reasoning_content = finalReasoningContent;
|
|
385
382
|
}
|
|
386
383
|
|
|
@@ -544,6 +541,7 @@ async function processStreamingResponse(
|
|
|
544
541
|
): Promise<CallAgentResult> {
|
|
545
542
|
let accumulatedContent = "";
|
|
546
543
|
let accumulatedReasoningContent = "";
|
|
544
|
+
let hasReasoningContent = false;
|
|
547
545
|
const toolCalls: {
|
|
548
546
|
id: string;
|
|
549
547
|
type: "function";
|
|
@@ -618,13 +616,13 @@ async function processStreamingResponse(
|
|
|
618
616
|
}
|
|
619
617
|
}
|
|
620
618
|
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
reasoning_content.length > 0
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
619
|
+
if (typeof reasoning_content === "string") {
|
|
620
|
+
hasReasoningContent = true;
|
|
621
|
+
if (reasoning_content.length > 0) {
|
|
622
|
+
accumulatedReasoningContent += reasoning_content;
|
|
623
|
+
if (onReasoningUpdate) {
|
|
624
|
+
onReasoningUpdate(accumulatedReasoningContent);
|
|
625
|
+
}
|
|
628
626
|
}
|
|
629
627
|
}
|
|
630
628
|
|
|
@@ -716,7 +714,7 @@ async function processStreamingResponse(
|
|
|
716
714
|
result.content = accumulatedContent.trim();
|
|
717
715
|
}
|
|
718
716
|
|
|
719
|
-
if (
|
|
717
|
+
if (hasReasoningContent) {
|
|
720
718
|
result.reasoning_content = accumulatedReasoningContent.trim();
|
|
721
719
|
}
|
|
722
720
|
|
package/src/services/hook.ts
CHANGED
|
@@ -277,21 +277,6 @@ export async function executeCommands(
|
|
|
277
277
|
return results;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
/**
|
|
281
|
-
* Execute a CwdChanged hook
|
|
282
|
-
*/
|
|
283
|
-
export async function executeCwdChangedHooks(
|
|
284
|
-
oldCwd: string,
|
|
285
|
-
newCwd: string,
|
|
286
|
-
context: ExtendedHookExecutionContext,
|
|
287
|
-
): Promise<HookExecutionResult[]> {
|
|
288
|
-
// CwdChanged hooks are executed through HookManager.executeCwdChangedHooks()
|
|
289
|
-
void context;
|
|
290
|
-
void oldCwd;
|
|
291
|
-
void newCwd;
|
|
292
|
-
return [];
|
|
293
|
-
}
|
|
294
|
-
|
|
295
280
|
/**
|
|
296
281
|
* Validate command safety (basic checks)
|
|
297
282
|
*/
|
|
@@ -8,7 +8,7 @@ import { dirname } from "path";
|
|
|
8
8
|
import { getLastLine } from "../utils/fileUtils.js";
|
|
9
9
|
|
|
10
10
|
import type { Message } from "../types/index.js";
|
|
11
|
-
import type {
|
|
11
|
+
import type { SessionFilename } from "../types/session.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* JSONL write options
|
|
@@ -56,13 +56,7 @@ export class JsonlHandler {
|
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
const sessionMessages: SessionMessage[] = messages.map((message) => ({
|
|
61
|
-
...message,
|
|
62
|
-
timestamp: new Date().toISOString(),
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
return this.append(filePath, sessionMessages);
|
|
59
|
+
return this.append(filePath, messages);
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
/**
|
|
@@ -70,7 +64,7 @@ export class JsonlHandler {
|
|
|
70
64
|
*/
|
|
71
65
|
async append(
|
|
72
66
|
filePath: string,
|
|
73
|
-
messages:
|
|
67
|
+
messages: Message[],
|
|
74
68
|
options?: JsonlWriteOptions,
|
|
75
69
|
): Promise<void> {
|
|
76
70
|
if (messages.length === 0) {
|
|
@@ -85,16 +79,10 @@ export class JsonlHandler {
|
|
|
85
79
|
// Ensure directory exists
|
|
86
80
|
await this.ensureDirectory(dirname(filePath));
|
|
87
81
|
|
|
88
|
-
// Convert messages to JSONL lines (
|
|
82
|
+
// Convert messages to JSONL lines (compact JSON, timestamp first)
|
|
89
83
|
const lines = messages.map((message) => {
|
|
90
|
-
const { timestamp
|
|
91
|
-
|
|
92
|
-
const messageWithTimestamp = {
|
|
93
|
-
timestamp: existingTimestamp || new Date().toISOString(),
|
|
94
|
-
...messageWithoutTimestamp,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
return JSON.stringify(messageWithTimestamp);
|
|
84
|
+
const { timestamp, ...rest } = message;
|
|
85
|
+
return JSON.stringify({ timestamp, ...rest });
|
|
98
86
|
});
|
|
99
87
|
|
|
100
88
|
const content = lines.join("\n") + "\n";
|
|
@@ -116,7 +104,7 @@ export class JsonlHandler {
|
|
|
116
104
|
/**
|
|
117
105
|
* Read all messages from JSONL file (simplified - no metadata handling)
|
|
118
106
|
*/
|
|
119
|
-
async read(filePath: string): Promise<
|
|
107
|
+
async read(filePath: string): Promise<Message[]> {
|
|
120
108
|
try {
|
|
121
109
|
const content = await readFile(filePath, "utf8");
|
|
122
110
|
const lines = content
|
|
@@ -128,14 +116,14 @@ export class JsonlHandler {
|
|
|
128
116
|
return [];
|
|
129
117
|
}
|
|
130
118
|
|
|
131
|
-
const allMessages:
|
|
119
|
+
const allMessages: Message[] = [];
|
|
132
120
|
|
|
133
121
|
// Parse all messages (no metadata line to skip)
|
|
134
122
|
for (let i = 0; i < lines.length; i++) {
|
|
135
123
|
const line = lines[i];
|
|
136
124
|
|
|
137
125
|
try {
|
|
138
|
-
const message = JSON.parse(line) as
|
|
126
|
+
const message = JSON.parse(line) as Message;
|
|
139
127
|
if (message.timestamp) allMessages.push(message);
|
|
140
128
|
} catch (error) {
|
|
141
129
|
// Throw error for invalid JSON lines with line number
|
|
@@ -155,7 +143,7 @@ export class JsonlHandler {
|
|
|
155
143
|
/**
|
|
156
144
|
* Get the last message from JSONL file using efficient file reading (simplified)
|
|
157
145
|
*/
|
|
158
|
-
async getLastMessage(filePath: string): Promise<
|
|
146
|
+
async getLastMessage(filePath: string): Promise<Message | null> {
|
|
159
147
|
try {
|
|
160
148
|
// First check if file exists
|
|
161
149
|
try {
|
|
@@ -176,7 +164,7 @@ export class JsonlHandler {
|
|
|
176
164
|
|
|
177
165
|
try {
|
|
178
166
|
const parsed = JSON.parse(lastLine);
|
|
179
|
-
return parsed as
|
|
167
|
+
return parsed as Message;
|
|
180
168
|
} catch (error) {
|
|
181
169
|
throw new Error(`Invalid JSON in last line of "${filePath}": ${error}`);
|
|
182
170
|
}
|
|
@@ -190,7 +178,7 @@ export class JsonlHandler {
|
|
|
190
178
|
/**
|
|
191
179
|
* Validate messages before writing
|
|
192
180
|
*/
|
|
193
|
-
private validateMessages(messages:
|
|
181
|
+
private validateMessages(messages: Message[]): void {
|
|
194
182
|
for (let i = 0; i < messages.length; i++) {
|
|
195
183
|
const message = messages[i];
|
|
196
184
|
|
package/src/services/session.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { join } from "path";
|
|
|
20
20
|
import { homedir } from "os";
|
|
21
21
|
import { randomUUID } from "crypto";
|
|
22
22
|
import type { Message } from "../types/index.js";
|
|
23
|
-
import type { SessionMessage } from "../types/session.js";
|
|
24
23
|
import { PathEncoder } from "../utils/pathEncoder.js";
|
|
25
24
|
import { JsonlHandler } from "../services/jsonlHandler.js";
|
|
26
25
|
import { extractLatestTotalTokens } from "../utils/tokenCalculation.js";
|
|
@@ -228,19 +227,14 @@ export async function appendMessages(
|
|
|
228
227
|
);
|
|
229
228
|
}
|
|
230
229
|
|
|
231
|
-
|
|
232
|
-
timestamp: new Date().toISOString(),
|
|
233
|
-
...msg,
|
|
234
|
-
}));
|
|
235
|
-
|
|
236
|
-
await jsonlHandler.append(filePath, messagesWithTimestamp, {
|
|
230
|
+
await jsonlHandler.append(filePath, newMessages, {
|
|
237
231
|
atomic: false,
|
|
238
232
|
});
|
|
239
233
|
|
|
240
234
|
// Update index
|
|
241
235
|
const encoder = new PathEncoder();
|
|
242
236
|
const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
|
|
243
|
-
const lastMessage =
|
|
237
|
+
const lastMessage = newMessages[newMessages.length - 1];
|
|
244
238
|
|
|
245
239
|
// Get first message content if it's a new session or we don't have it
|
|
246
240
|
let firstMessage: string | undefined;
|
|
@@ -333,12 +327,7 @@ export async function loadSessionFromJsonl(
|
|
|
333
327
|
id: sessionId,
|
|
334
328
|
rootSessionId: rootSessionId || sessionId,
|
|
335
329
|
parentSessionId,
|
|
336
|
-
messages
|
|
337
|
-
// Remove timestamp property for backward compatibility
|
|
338
|
-
const { timestamp: _ignored, ...messageWithoutTimestamp } = msg;
|
|
339
|
-
void _ignored; // Use the variable to avoid eslint error
|
|
340
|
-
return messageWithoutTimestamp;
|
|
341
|
-
}),
|
|
330
|
+
messages,
|
|
342
331
|
metadata: {
|
|
343
332
|
workdir,
|
|
344
333
|
lastActiveAt: lastMessage
|
|
@@ -953,7 +942,12 @@ export async function handleSessionRestoration(
|
|
|
953
942
|
// Use only JSONL format - no legacy support
|
|
954
943
|
sessionToRestore = await loadSessionFromJsonl(restoreSessionId, workdir);
|
|
955
944
|
if (!sessionToRestore) {
|
|
956
|
-
|
|
945
|
+
// Session doesn't exist on disk (e.g. new project with no messages saved yet).
|
|
946
|
+
// Gracefully fall back to starting fresh instead of throwing.
|
|
947
|
+
logger?.warn(
|
|
948
|
+
`Session ${restoreSessionId} not found on disk, starting fresh session`,
|
|
949
|
+
);
|
|
950
|
+
return;
|
|
957
951
|
}
|
|
958
952
|
} else if (continueLastSession) {
|
|
959
953
|
// Use only JSONL format - no legacy support
|
package/src/tools/agentTool.ts
CHANGED
|
@@ -159,20 +159,39 @@ When using the Agent tool, you must specify a subagent_type parameter to select
|
|
|
159
159
|
|
|
160
160
|
const messages = instance.messageManager.getMessages();
|
|
161
161
|
const tokens = instance.messageManager.getLatestTotalTokens();
|
|
162
|
-
const
|
|
162
|
+
const usedTools = instance.usedTools;
|
|
163
163
|
|
|
164
164
|
const toolCount = countToolBlocks(messages);
|
|
165
165
|
const summary = formatToolTokenSummary(toolCount, tokens);
|
|
166
166
|
|
|
167
|
+
const getDisplayParam = (t: {
|
|
168
|
+
name: string;
|
|
169
|
+
parameters: string;
|
|
170
|
+
compactParams?: string;
|
|
171
|
+
stage?: string;
|
|
172
|
+
}) => {
|
|
173
|
+
if (
|
|
174
|
+
(t.stage === "end" || t.stage === "running") &&
|
|
175
|
+
t.compactParams
|
|
176
|
+
) {
|
|
177
|
+
return t.compactParams;
|
|
178
|
+
}
|
|
179
|
+
const flat = t.parameters.replace(/\n/g, "\\n");
|
|
180
|
+
return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
|
|
181
|
+
};
|
|
182
|
+
|
|
167
183
|
let shortResult = "";
|
|
168
184
|
if (toolCount > 2) {
|
|
169
185
|
shortResult += "... ";
|
|
170
186
|
}
|
|
171
|
-
if (lastTools.length > 0) {
|
|
172
|
-
shortResult += `${lastTools.join(", ")} `;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
187
|
shortResult += summary;
|
|
188
|
+
if (usedTools.length > 0) {
|
|
189
|
+
shortResult +=
|
|
190
|
+
"\n" +
|
|
191
|
+
usedTools
|
|
192
|
+
.map((t) => `${t.name} ${getDisplayParam(t)}`)
|
|
193
|
+
.join("\n");
|
|
194
|
+
}
|
|
176
195
|
|
|
177
196
|
context.onShortResultUpdate?.(shortResult);
|
|
178
197
|
},
|
package/src/tools/bashTool.ts
CHANGED
|
@@ -80,7 +80,7 @@ export const bashTool: ToolPlugin = {
|
|
|
80
80
|
},
|
|
81
81
|
},
|
|
82
82
|
prompt: () => `
|
|
83
|
-
Executes a given bash command
|
|
83
|
+
Executes a given bash command with optional timeout, ensuring proper handling and security measures. Each invocation runs in a fresh shell process starting from the project root.
|
|
84
84
|
|
|
85
85
|
IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
|
|
86
86
|
|
|
@@ -139,10 +139,7 @@ Use the gh command via the Bash tool for GitHub-related tasks including working
|
|
|
139
139
|
- Do not retry failing commands in a sleep loop — diagnose the root cause.
|
|
140
140
|
- If waiting for a background task you started with \`run_in_background\`, you will be notified when it completes — do not poll.
|
|
141
141
|
- If you must poll an external process, use a check command (e.g. \`gh run view\`) rather than sleeping first.
|
|
142
|
-
- If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user
|
|
143
|
-
|
|
144
|
-
# CWD management
|
|
145
|
-
Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it. When you use \`cd\`, the shell working directory will be reset to the original working directory after the command completes.`,
|
|
142
|
+
- If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.`,
|
|
146
143
|
execute: async (
|
|
147
144
|
args: Record<string, unknown>,
|
|
148
145
|
context: ToolContext,
|
|
@@ -240,14 +237,7 @@ Try to maintain your current working directory throughout the session by using a
|
|
|
240
237
|
|
|
241
238
|
// Foreground execution (original behavior)
|
|
242
239
|
return new Promise((resolve) => {
|
|
243
|
-
|
|
244
|
-
const tempCwdFile = path.join(
|
|
245
|
-
os.tmpdir(),
|
|
246
|
-
`wave_cwd_${Date.now()}_${Math.random().toString(36).substring(2, 11)}.tmp`,
|
|
247
|
-
);
|
|
248
|
-
const wrappedCommand = `${command} && pwd -P >| ${tempCwdFile}`;
|
|
249
|
-
|
|
250
|
-
const child: ChildProcess = spawn(wrappedCommand, {
|
|
240
|
+
const child: ChildProcess = spawn(command, {
|
|
251
241
|
shell: true,
|
|
252
242
|
stdio: "pipe",
|
|
253
243
|
cwd: context.workdir,
|
|
@@ -431,55 +421,13 @@ Try to maintain your current working directory throughout the session by using a
|
|
|
431
421
|
clearTimeout(timeoutHandle);
|
|
432
422
|
}
|
|
433
423
|
|
|
434
|
-
// Read the new CWD from the temporary file
|
|
435
|
-
let newCwd: string | undefined;
|
|
436
|
-
try {
|
|
437
|
-
if (fs.existsSync(tempCwdFile)) {
|
|
438
|
-
newCwd = fs.readFileSync(tempCwdFile, "utf8").trim();
|
|
439
|
-
// Validate the path exists before calling the callback
|
|
440
|
-
fs.accessSync(newCwd, fs.constants.F_OK);
|
|
441
|
-
}
|
|
442
|
-
} catch (fileError) {
|
|
443
|
-
logger.warn(
|
|
444
|
-
`Could not read or validate new CWD from temp file ${tempCwdFile}:`,
|
|
445
|
-
fileError,
|
|
446
|
-
);
|
|
447
|
-
newCwd = undefined;
|
|
448
|
-
} finally {
|
|
449
|
-
// Ensure temp file is cleaned up even if reading fails
|
|
450
|
-
try {
|
|
451
|
-
if (fs.existsSync(tempCwdFile)) {
|
|
452
|
-
fs.unlinkSync(tempCwdFile);
|
|
453
|
-
}
|
|
454
|
-
} catch (fileError) {
|
|
455
|
-
logger.error("Failed to clean up temp CWD file:", fileError);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// If CWD changed, call the onCwdChange callback and add notification
|
|
460
|
-
let cwdChangedNotification = "";
|
|
461
|
-
if (newCwd && newCwd !== context.workdir && context.onCwdChange) {
|
|
462
|
-
const isInSafeZone =
|
|
463
|
-
context.permissionManager?.isPathInSafeZone?.(newCwd) ?? true;
|
|
464
|
-
|
|
465
|
-
if (isInSafeZone) {
|
|
466
|
-
context.onCwdChange(newCwd);
|
|
467
|
-
} else if (context.originalWorkdir) {
|
|
468
|
-
context.onCwdChange(context.originalWorkdir);
|
|
469
|
-
cwdChangedNotification = `Shell cwd was reset to ${context.originalWorkdir}\n`;
|
|
470
|
-
} else {
|
|
471
|
-
context.onCwdChange(newCwd);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
424
|
const exitCode = code ?? 0;
|
|
476
425
|
const combinedOutput =
|
|
477
426
|
outputBuffer + (errorBuffer ? "\n" + errorBuffer : "");
|
|
478
427
|
|
|
479
428
|
// Handle large output by truncation and persistence if needed
|
|
480
429
|
const finalOutput =
|
|
481
|
-
|
|
482
|
-
(combinedOutput || `Command executed with exit code: ${exitCode}`);
|
|
430
|
+
combinedOutput || `Command executed with exit code: ${exitCode}`;
|
|
483
431
|
const content = processOutput(finalOutput);
|
|
484
432
|
|
|
485
433
|
const lines = combinedOutput.trim().split("\n");
|
package/src/tools/editTool.ts
CHANGED
package/src/tools/skillTool.ts
CHANGED
|
@@ -148,19 +148,39 @@ export const skillTool: ToolPlugin = {
|
|
|
148
148
|
// Update shortResult
|
|
149
149
|
const messages = instance.messageManager.getMessages();
|
|
150
150
|
const tokens = instance.messageManager.getLatestTotalTokens();
|
|
151
|
-
const
|
|
151
|
+
const usedTools = instance.usedTools;
|
|
152
152
|
|
|
153
153
|
const toolCount = countToolBlocks(messages);
|
|
154
154
|
const summary = formatToolTokenSummary(toolCount, tokens);
|
|
155
155
|
|
|
156
|
+
const getDisplayParam = (t: {
|
|
157
|
+
name: string;
|
|
158
|
+
parameters: string;
|
|
159
|
+
compactParams?: string;
|
|
160
|
+
stage?: string;
|
|
161
|
+
}) => {
|
|
162
|
+
if (
|
|
163
|
+
(t.stage === "end" || t.stage === "running") &&
|
|
164
|
+
t.compactParams
|
|
165
|
+
) {
|
|
166
|
+
return t.compactParams;
|
|
167
|
+
}
|
|
168
|
+
const flat = t.parameters.replace(/\n/g, "\\n");
|
|
169
|
+
return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
|
|
170
|
+
};
|
|
171
|
+
|
|
156
172
|
let shortResult = "";
|
|
157
173
|
if (toolCount > 2) {
|
|
158
174
|
shortResult += "... ";
|
|
159
175
|
}
|
|
160
|
-
if (lastTools.length > 0) {
|
|
161
|
-
shortResult += `${lastTools.join(", ")} `;
|
|
162
|
-
}
|
|
163
176
|
shortResult += summary;
|
|
177
|
+
if (usedTools.length > 0) {
|
|
178
|
+
shortResult +=
|
|
179
|
+
"\n" +
|
|
180
|
+
usedTools
|
|
181
|
+
.map((t) => `${t.name} ${getDisplayParam(t)}`)
|
|
182
|
+
.join("\n");
|
|
183
|
+
}
|
|
164
184
|
|
|
165
185
|
context.onShortResultUpdate?.(shortResult);
|
|
166
186
|
},
|
package/src/tools/types.ts
CHANGED
|
@@ -58,7 +58,6 @@ export interface ToolContext {
|
|
|
58
58
|
abortSignal?: AbortSignal;
|
|
59
59
|
backgroundTaskManager?: import("../managers/backgroundTaskManager.js").BackgroundTaskManager;
|
|
60
60
|
workdir: string;
|
|
61
|
-
originalWorkdir?: string;
|
|
62
61
|
/** Permission mode for this tool execution */
|
|
63
62
|
permissionMode?: PermissionMode;
|
|
64
63
|
/** Custom permission callback */
|
|
@@ -104,6 +103,4 @@ export interface ToolContext {
|
|
|
104
103
|
};
|
|
105
104
|
/** State of files read in the current session for deduplication */
|
|
106
105
|
readFileState?: Map<string, { mtime: number; hash: string }>;
|
|
107
|
-
/** Callback to notify when the current working directory changes */
|
|
108
|
-
onCwdChange?: (newCwd: string) => void;
|
|
109
106
|
}
|