wave-agent-sdk 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +9 -6
- package/dist/agent.js +35 -33
- package/dist/builtin/index.d.ts +1 -0
- package/dist/builtin/index.js +20 -0
- package/dist/builtin/plugins.d.ts +1 -0
- package/dist/builtin/plugins.js +225 -0
- package/dist/builtin/skills/artifact.d.ts +1 -0
- package/dist/builtin/skills/artifact.js +18 -0
- package/dist/builtin/skills/code-review.d.ts +1 -0
- package/{builtin/skills/code-review/SKILL.md → dist/builtin/skills/code-review.js} +23 -19
- package/dist/builtin/skills/deep-research.d.ts +1 -0
- package/{builtin/skills/deep-research/SKILL.md → dist/builtin/skills/deep-research.js} +18 -14
- package/dist/builtin/skills/init.d.ts +1 -0
- package/{builtin/skills/init/SKILL.md → dist/builtin/skills/init.js} +6 -3
- package/dist/builtin/skills/loop.d.ts +1 -0
- package/dist/builtin/skills/loop.js +83 -0
- package/dist/builtin/skills/settings.d.ts +1 -0
- package/dist/builtin/skills/settings.js +1249 -0
- package/dist/builtin/skills/simplify.d.ts +1 -0
- package/{builtin/skills/simplify/SKILL.md → dist/builtin/skills/simplify.js} +7 -3
- package/dist/builtin/subagents.d.ts +1 -0
- package/dist/builtin/subagents.js +164 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/managers/aiManager.d.ts +10 -0
- package/dist/managers/aiManager.js +49 -5
- package/dist/managers/mcpManager.js +1 -1
- package/dist/managers/messageManager.d.ts +6 -0
- package/dist/managers/messageManager.js +33 -0
- package/dist/managers/subagentManager.d.ts +9 -0
- package/dist/managers/subagentManager.js +62 -3
- package/dist/services/hook.js +41 -7
- package/dist/services/initializationService.js +0 -21
- package/dist/services/jsonlHandler.d.ts +37 -2
- package/dist/services/jsonlHandler.js +55 -6
- package/dist/services/session.d.ts +35 -4
- package/dist/services/session.js +233 -36
- package/dist/services/worktreeHooks.d.ts +45 -0
- package/dist/services/worktreeHooks.js +133 -0
- package/dist/tools/agentTool.js +36 -1
- package/dist/tools/bashTool.js +120 -57
- package/dist/tools/editTool.js +1 -0
- package/dist/tools/enterWorktreeTool.d.ts +1 -1
- package/dist/tools/enterWorktreeTool.js +44 -31
- package/dist/tools/exitWorktreeTool.js +21 -26
- package/dist/tools/readTool.js +6 -3
- package/dist/tools/types.d.ts +1 -0
- package/dist/tools/writeTool.js +1 -0
- package/dist/types/agent.d.ts +5 -0
- package/dist/types/hooks.d.ts +3 -2
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/skills.d.ts +0 -1
- package/dist/types/skills.js +0 -1
- package/dist/utils/asyncWorkRegistry.d.ts +32 -0
- package/dist/utils/asyncWorkRegistry.js +81 -0
- package/dist/utils/builtinEmbed.d.ts +21 -0
- package/dist/utils/builtinEmbed.js +53 -0
- package/dist/utils/configPaths.d.ts +0 -1
- package/dist/utils/configPaths.js +5 -26
- package/dist/utils/containerSetup.js +5 -0
- package/dist/utils/convertMessagesForAPI.js +21 -2
- package/dist/utils/messageOperations.d.ts +1 -0
- package/dist/utils/skillParser.js +3 -6
- package/dist/utils/toolImagePersistence.d.ts +25 -0
- package/dist/utils/toolImagePersistence.js +56 -0
- package/dist/utils/windowsPaths.d.ts +28 -0
- package/dist/utils/windowsPaths.js +47 -0
- package/dist/utils/worktreeSession.d.ts +6 -0
- package/dist/utils/worktreeUtils.d.ts +7 -0
- package/dist/utils/worktreeUtils.js +88 -40
- package/package.json +5 -5
- package/builtin/plugins/sdd/.wave-plugin/plugin.json +0 -8
- package/builtin/plugins/sdd/hooks/hooks.json +0 -14
- package/builtin/plugins/sdd/scripts/session-start.js +0 -24
- package/builtin/plugins/sdd/scripts/spec-count.js +0 -77
- package/builtin/plugins/sdd/skills/specify/SKILL.md +0 -47
- package/builtin/plugins/sdd/skills/specify/templates/spec-template.md +0 -47
- package/builtin/skills/artifact/SKILL.md +0 -14
- package/builtin/skills/loop/SKILL.md +0 -79
- package/builtin/skills/settings/ENV.md +0 -78
- package/builtin/skills/settings/HOOKS.md +0 -227
- package/builtin/skills/settings/MCP.md +0 -137
- package/builtin/skills/settings/MEMORY.md +0 -76
- package/builtin/skills/settings/MODELS.md +0 -119
- package/builtin/skills/settings/PERMISSIONS.md +0 -88
- package/builtin/skills/settings/PLUGINS.md +0 -171
- package/builtin/skills/settings/SKILL.md +0 -132
- package/builtin/skills/settings/SKILLS.md +0 -107
- package/builtin/skills/settings/SUBAGENTS.md +0 -77
- package/builtin/subagents/bash.md +0 -19
- package/builtin/subagents/explore.md +0 -43
- package/builtin/subagents/general-purpose.md +0 -20
- package/builtin/subagents/plan.md +0 -56
- package/builtin/subagents/vision.md +0 -18
package/dist/services/hook.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { spawn } from "child_process";
|
|
9
9
|
import { generateSessionFilePath } from "./session.js";
|
|
10
|
+
import { resolveShellPath } from "../utils/shellResolver.js";
|
|
11
|
+
import { toPosixCommand } from "../utils/windowsPaths.js";
|
|
10
12
|
// =============================================================================
|
|
11
13
|
// Hook Execution Functions
|
|
12
14
|
// =============================================================================
|
|
@@ -56,10 +58,10 @@ async function buildHookJsonInput(context) {
|
|
|
56
58
|
if (context.subagentType !== undefined) {
|
|
57
59
|
jsonInput.subagent_type = context.subagentType;
|
|
58
60
|
}
|
|
59
|
-
// Add name field for WorktreeCreate events
|
|
61
|
+
// Add name field for WorktreeCreate events (aligned with Claude Code)
|
|
60
62
|
if (context.event === "WorktreeCreate") {
|
|
61
|
-
if (context.
|
|
62
|
-
jsonInput.name = context.
|
|
63
|
+
if (context.name !== undefined) {
|
|
64
|
+
jsonInput.name = context.name;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
// Add worktree_path field for WorktreeRemove events
|
|
@@ -133,11 +135,43 @@ export async function executeCommand(command, context, options) {
|
|
|
133
135
|
let stdout = "";
|
|
134
136
|
let stderr = "";
|
|
135
137
|
let timedOut = false;
|
|
136
|
-
// Parse command for shell execution
|
|
138
|
+
// Parse command for shell execution.
|
|
139
|
+
//
|
|
140
|
+
// Windows uses Git Bash (POSIX shell) instead of cmd.exe: cmd.exe's `/c`
|
|
141
|
+
// does not strip quotes from arguments after the first token, so commands
|
|
142
|
+
// like `node "C:\path\script.js"` silently fail (issue #1773). Git Bash
|
|
143
|
+
// parses quotes correctly, and Windows paths in the command are converted
|
|
144
|
+
// to POSIX form first (Git Bash resolves `/c/Users/...` and translates it
|
|
145
|
+
// back to a Windows path when spawning native executables like node.exe).
|
|
146
|
+
// Falls back to cmd.exe when no Git Bash is installed.
|
|
137
147
|
const isWindows = process.platform === "win32";
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
const bashPath = isWindows ? resolveShellPath() : undefined;
|
|
149
|
+
let shell;
|
|
150
|
+
let shellFlag;
|
|
151
|
+
let finalCommand;
|
|
152
|
+
if (bashPath) {
|
|
153
|
+
shell = bashPath;
|
|
154
|
+
shellFlag = "-c";
|
|
155
|
+
finalCommand = toPosixCommand(command);
|
|
156
|
+
// Windows .sh scripts aren't directly executable — run them via bash
|
|
157
|
+
// when the command itself is a .sh file (e.g. `${WAVE_PLUGIN_ROOT}/x.sh`).
|
|
158
|
+
const trimmed = finalCommand.trim();
|
|
159
|
+
const firstToken = trimmed.match(/^("([^"]*)"|'([^']*)'|\S+)/)?.[0] ?? "";
|
|
160
|
+
if (firstToken.endsWith(".sh") && !trimmed.startsWith("bash ")) {
|
|
161
|
+
finalCommand = `bash ${finalCommand}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (isWindows) {
|
|
165
|
+
shell = "cmd.exe";
|
|
166
|
+
shellFlag = "/c";
|
|
167
|
+
finalCommand = command;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
shell = "/bin/sh";
|
|
171
|
+
shellFlag = "-c";
|
|
172
|
+
finalCommand = command;
|
|
173
|
+
}
|
|
174
|
+
const childProcess = spawn(shell, [shellFlag, finalCommand], {
|
|
141
175
|
stdio: ["pipe", "pipe", "pipe"],
|
|
142
176
|
cwd: context.projectDir,
|
|
143
177
|
env: {
|
|
@@ -127,27 +127,6 @@ export class InitializationService {
|
|
|
127
127
|
catch (error) {
|
|
128
128
|
logger?.warn("SessionStart hooks execution failed:", error);
|
|
129
129
|
}
|
|
130
|
-
// Trigger WorktreeCreate hook if this is a new worktree
|
|
131
|
-
if (agentOptions.isNewWorktree && hookManager) {
|
|
132
|
-
try {
|
|
133
|
-
logger?.info(`Triggering WorktreeCreate hook for ${agentOptions.worktreeName}...`);
|
|
134
|
-
const hookResults = await hookManager.executeHooks("WorktreeCreate", {
|
|
135
|
-
event: "WorktreeCreate",
|
|
136
|
-
projectDir: workdir,
|
|
137
|
-
timestamp: new Date(),
|
|
138
|
-
sessionId: messageManager.getSessionId(),
|
|
139
|
-
transcriptPath: messageManager.getTranscriptPath(),
|
|
140
|
-
cwd: workdir,
|
|
141
|
-
worktreeName: agentOptions.worktreeName,
|
|
142
|
-
env: Object.fromEntries(Object.entries(configurationService.getMergedEnv()).filter((e) => e[1] !== undefined)),
|
|
143
|
-
});
|
|
144
|
-
// Process hook results
|
|
145
|
-
hookManager.processHookResults("WorktreeCreate", hookResults, messageManager);
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
logger?.warn("WorktreeCreate hooks execution failed:", error);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
130
|
// Resolve and validate configuration after loading settings.json
|
|
152
131
|
resolveAndValidateConfig();
|
|
153
132
|
// Initialize auto-memory directory
|
|
@@ -10,6 +10,20 @@ import type { SessionFilename } from "../types/session.js";
|
|
|
10
10
|
export interface JsonlWriteOptions {
|
|
11
11
|
atomic?: boolean;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Creation-time metadata persisted in the session file's header line
|
|
15
|
+
* (`{"type":"metadata",...}`). The header is append-only — written once on
|
|
16
|
+
* session creation and never rewritten — so only fields that are fixed at
|
|
17
|
+
* creation time belong here.
|
|
18
|
+
*/
|
|
19
|
+
export interface SessionMetadataHeader {
|
|
20
|
+
/** Real working directory (the encoded project dir name is lossy for paths containing "-"). */
|
|
21
|
+
workdir?: string;
|
|
22
|
+
/** ISO 8601 creation timestamp. */
|
|
23
|
+
createdAt?: string;
|
|
24
|
+
/** Git branch at creation time (`git branch --show-current`), when the directory is a git repo. */
|
|
25
|
+
gitBranch?: string;
|
|
26
|
+
}
|
|
13
27
|
/**
|
|
14
28
|
* JSONL handler class for message persistence operations
|
|
15
29
|
*/
|
|
@@ -17,9 +31,20 @@ export declare class JsonlHandler {
|
|
|
17
31
|
private readonly defaultWriteOptions;
|
|
18
32
|
constructor();
|
|
19
33
|
/**
|
|
20
|
-
* Create a new session file
|
|
34
|
+
* Create a new session file.
|
|
35
|
+
*
|
|
36
|
+
* When `metadata` is provided, the first line is a metadata header
|
|
37
|
+
* recording creation-time facts about the session:
|
|
38
|
+
* `{"type":"metadata","workdir":...,"createdAt":...,"gitBranch":...}`. The
|
|
39
|
+
* encoded project dir name is lossy for paths containing "-", so persisting
|
|
40
|
+
* the real path lets session listing show it without decoding; `createdAt`
|
|
41
|
+
* and `gitBranch` similarly avoid lossy/fabricated reconstruction later.
|
|
42
|
+
* The header carries no `timestamp`, so message readers filter it out
|
|
43
|
+
* naturally.
|
|
44
|
+
*
|
|
45
|
+
* Legacy callers that omit `metadata` still get an empty file.
|
|
21
46
|
*/
|
|
22
|
-
createSession(filePath: string): Promise<void>;
|
|
47
|
+
createSession(filePath: string, metadata?: SessionMetadataHeader): Promise<void>;
|
|
23
48
|
/**
|
|
24
49
|
* Append a single message to JSONL file
|
|
25
50
|
*/
|
|
@@ -40,6 +65,16 @@ export declare class JsonlHandler {
|
|
|
40
65
|
* Get the last message from JSONL file using efficient file reading (simplified)
|
|
41
66
|
*/
|
|
42
67
|
getLastMessage(filePath: string): Promise<Message | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Read the creation-time metadata from the session file's header line.
|
|
70
|
+
*
|
|
71
|
+
* Newer session files start with a `{"type":"metadata",...}` line (see
|
|
72
|
+
* `createSession`). Legacy files have no header.
|
|
73
|
+
*
|
|
74
|
+
* @param filePath - Path to the session JSONL file
|
|
75
|
+
* @returns The persisted metadata, or null when the file has no header
|
|
76
|
+
*/
|
|
77
|
+
readMetadata(filePath: string): Promise<SessionMetadataHeader | null>;
|
|
43
78
|
/**
|
|
44
79
|
* Validate messages before writing
|
|
45
80
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { appendFile, readFile, writeFile, stat, mkdir } from "fs/promises";
|
|
6
6
|
import { dirname } from "path";
|
|
7
|
-
import { getLastLine } from "../utils/fileUtils.js";
|
|
7
|
+
import { getLastLine, readFirstNLines } from "../utils/fileUtils.js";
|
|
8
8
|
/**
|
|
9
9
|
* JSONL handler class for message persistence operations
|
|
10
10
|
*/
|
|
@@ -15,13 +15,26 @@ export class JsonlHandler {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Create a new session file
|
|
18
|
+
* Create a new session file.
|
|
19
|
+
*
|
|
20
|
+
* When `metadata` is provided, the first line is a metadata header
|
|
21
|
+
* recording creation-time facts about the session:
|
|
22
|
+
* `{"type":"metadata","workdir":...,"createdAt":...,"gitBranch":...}`. The
|
|
23
|
+
* encoded project dir name is lossy for paths containing "-", so persisting
|
|
24
|
+
* the real path lets session listing show it without decoding; `createdAt`
|
|
25
|
+
* and `gitBranch` similarly avoid lossy/fabricated reconstruction later.
|
|
26
|
+
* The header carries no `timestamp`, so message readers filter it out
|
|
27
|
+
* naturally.
|
|
28
|
+
*
|
|
29
|
+
* Legacy callers that omit `metadata` still get an empty file.
|
|
19
30
|
*/
|
|
20
|
-
async createSession(filePath) {
|
|
31
|
+
async createSession(filePath, metadata) {
|
|
21
32
|
// Ensure directory exists
|
|
22
33
|
await this.ensureDirectory(dirname(filePath));
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
const content = metadata && Object.keys(metadata).length > 0
|
|
35
|
+
? `${JSON.stringify({ type: "metadata", ...metadata })}\n`
|
|
36
|
+
: "";
|
|
37
|
+
await writeFile(filePath, content, "utf8");
|
|
25
38
|
}
|
|
26
39
|
/**
|
|
27
40
|
* Append a single message to JSONL file
|
|
@@ -83,11 +96,14 @@ export class JsonlHandler {
|
|
|
83
96
|
return [];
|
|
84
97
|
}
|
|
85
98
|
const allMessages = [];
|
|
86
|
-
// Parse all messages
|
|
99
|
+
// Parse all messages, skipping the metadata header line (if any)
|
|
87
100
|
for (let i = 0; i < lines.length; i++) {
|
|
88
101
|
const line = lines[i];
|
|
89
102
|
try {
|
|
90
103
|
const message = JSON.parse(line);
|
|
104
|
+
// Metadata header line: not a message, skip
|
|
105
|
+
if (message.type === "metadata")
|
|
106
|
+
continue;
|
|
91
107
|
if (message.timestamp)
|
|
92
108
|
allMessages.push(message);
|
|
93
109
|
}
|
|
@@ -127,6 +143,10 @@ export class JsonlHandler {
|
|
|
127
143
|
}
|
|
128
144
|
try {
|
|
129
145
|
const parsed = JSON.parse(lastLine);
|
|
146
|
+
// A file whose only line is the metadata header has no messages yet
|
|
147
|
+
if (parsed.type === "metadata") {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
130
150
|
return parsed;
|
|
131
151
|
}
|
|
132
152
|
catch (error) {
|
|
@@ -137,6 +157,35 @@ export class JsonlHandler {
|
|
|
137
157
|
throw new Error(`Failed to get last message from "${filePath}": ${error}`);
|
|
138
158
|
}
|
|
139
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Read the creation-time metadata from the session file's header line.
|
|
162
|
+
*
|
|
163
|
+
* Newer session files start with a `{"type":"metadata",...}` line (see
|
|
164
|
+
* `createSession`). Legacy files have no header.
|
|
165
|
+
*
|
|
166
|
+
* @param filePath - Path to the session JSONL file
|
|
167
|
+
* @returns The persisted metadata, or null when the file has no header
|
|
168
|
+
*/
|
|
169
|
+
async readMetadata(filePath) {
|
|
170
|
+
try {
|
|
171
|
+
const lines = await readFirstNLines(filePath, 1);
|
|
172
|
+
if (lines.length === 0) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const header = JSON.parse(lines[0]);
|
|
176
|
+
if (header?.type === "metadata") {
|
|
177
|
+
return {
|
|
178
|
+
workdir: header.workdir,
|
|
179
|
+
createdAt: header.createdAt,
|
|
180
|
+
gitBranch: header.gitBranch,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Unreadable or invalid first line — treat as a legacy file
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
140
189
|
/**
|
|
141
190
|
* Validate messages before writing
|
|
142
191
|
*/
|
|
@@ -33,6 +33,8 @@ export interface SessionMetadata {
|
|
|
33
33
|
lastActiveAt: Date;
|
|
34
34
|
latestTotalTokens: number;
|
|
35
35
|
firstMessage?: string;
|
|
36
|
+
/** Git branch at session creation time (from the metadata header). */
|
|
37
|
+
branch?: string;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Generate a new session ID using Node.js native crypto.randomUUID()
|
|
@@ -123,11 +125,31 @@ export declare function listSessions(workdir: string): Promise<SessionMetadata[]
|
|
|
123
125
|
*/
|
|
124
126
|
export declare function listSessionsFromJsonl(workdir: string): Promise<SessionMetadata[]>;
|
|
125
127
|
/**
|
|
126
|
-
* List all sessions across all project directories
|
|
128
|
+
* List all sessions across all project directories.
|
|
127
129
|
*
|
|
128
|
-
*
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
* When `worktreePaths` is provided, only project directories that match a
|
|
131
|
+
* same-repo git worktree (plus the current working directory's own project
|
|
132
|
+
* dir) are scanned — used by the `wave -r` picker's worktree aggregation
|
|
133
|
+
* (`Ctrl+W`). When omitted, every project directory under the sessions dir
|
|
134
|
+
* is scanned (all-projects mode, `Ctrl+A`).
|
|
135
|
+
*
|
|
136
|
+
* No time-based filtering is applied — all sessions whose files still exist
|
|
137
|
+
* are listed, regardless of how long ago they were last active.
|
|
138
|
+
*
|
|
139
|
+
* @param options.worktreePaths - Absolute paths of same-repo worktrees
|
|
140
|
+
* (from `git worktree list`). When provided, scanning is limited to
|
|
141
|
+
* matching project directories.
|
|
142
|
+
* @param options.workdir - Current working directory. Its own project dir is
|
|
143
|
+
* always included in worktree mode so sessions created from a subdirectory
|
|
144
|
+
* inside a worktree are not missed.
|
|
145
|
+
* @returns Promise that resolves to array of session metadata objects,
|
|
146
|
+
* deduplicated by sessionId (newest lastActiveAt wins), sorted by
|
|
147
|
+
* lastActiveAt descending.
|
|
148
|
+
*/
|
|
149
|
+
export declare function listAllSessions(options?: {
|
|
150
|
+
worktreePaths?: string[];
|
|
151
|
+
workdir?: string;
|
|
152
|
+
}): Promise<SessionMetadata[]>;
|
|
131
153
|
/**
|
|
132
154
|
* Clean up expired sessions older than 14 days based on file modification time
|
|
133
155
|
*
|
|
@@ -161,6 +183,15 @@ export declare function cleanupMetaOnlySessions(): Promise<number>;
|
|
|
161
183
|
* @returns Promise that resolves to true if session exists, false otherwise
|
|
162
184
|
*/
|
|
163
185
|
export declare function sessionExistsInJsonl(sessionId: string, workdir: string, sessionType?: "main" | "subagent"): Promise<boolean>;
|
|
186
|
+
/**
|
|
187
|
+
* Get the content of the first non-meta message in a session file
|
|
188
|
+
* For user role: get text block content
|
|
189
|
+
* For assistant role: get compact block content
|
|
190
|
+
* Skips meta messages (isMeta: true) to find the first meaningful message
|
|
191
|
+
* @param filePath - Path to the session JSONL file
|
|
192
|
+
* @returns Promise that resolves to the first non-meta message content or null if not found
|
|
193
|
+
*/
|
|
194
|
+
export declare function getFirstMessageContentFromFile(filePath: string): Promise<string | null>;
|
|
164
195
|
/**
|
|
165
196
|
* Get the content of the first non-meta message in a session
|
|
166
197
|
* For user role: get text block content
|