wave-agent-sdk 0.19.7 → 0.19.8
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 +7 -0
- package/dist/agent.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +1 -0
- package/dist/managers/aiManager.js +3 -0
- package/dist/managers/subagentManager.js +6 -0
- package/dist/services/configurationService.d.ts +6 -0
- package/dist/services/configurationService.js +31 -0
- package/dist/services/remoteSettingsService.js +2 -0
- package/dist/tools/enterWorktreeTool.js +2 -1
- package/dist/types/configuration.d.ts +5 -0
- package/dist/types/permissions.d.ts +0 -2
- package/dist/types/processes.d.ts +27 -0
- package/dist/types/workflow.d.ts +1 -1
- package/dist/utils/containerSetup.js +0 -9
- package/dist/utils/worktreeSession.d.ts +1 -1
- package/dist/utils/worktreeSession.js +1 -1
- package/dist/utils/worktreeUtils.d.ts +7 -1
- package/dist/utils/worktreeUtils.js +10 -4
- package/dist/workflow/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/agent.ts +10 -0
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +4 -0
- package/src/managers/subagentManager.ts +6 -0
- package/src/services/configurationService.ts +37 -0
- package/src/services/remoteSettingsService.ts +1 -0
- package/src/tools/enterWorktreeTool.ts +2 -1
- package/src/types/configuration.ts +5 -0
- package/src/types/permissions.ts +0 -2
- package/src/types/processes.ts +29 -0
- package/src/types/workflow.ts +1 -0
- package/src/utils/containerSetup.ts +0 -11
- package/src/utils/worktreeSession.ts +1 -1
- package/src/utils/worktreeUtils.ts +14 -4
- package/src/workflow/types.ts +6 -0
package/dist/agent.d.ts
CHANGED
|
@@ -390,4 +390,11 @@ export declare class Agent {
|
|
|
390
390
|
* Check if there are any running background tasks or active subagents
|
|
391
391
|
*/
|
|
392
392
|
get hasRunningBackgroundWork(): boolean;
|
|
393
|
+
/**
|
|
394
|
+
* Check if there are pending items (messages, bang commands, or background
|
|
395
|
+
* task notifications) in the message queue. Background task completion
|
|
396
|
+
* notifications are enqueued before the main agent's dispatch consumes them,
|
|
397
|
+
* so callers waiting for the agent to fully settle must also wait on this.
|
|
398
|
+
*/
|
|
399
|
+
get hasPendingMessages(): boolean;
|
|
393
400
|
}
|
package/dist/agent.js
CHANGED
|
@@ -978,4 +978,13 @@ export class Agent {
|
|
|
978
978
|
const activeSubagents = this.subagentManager.getActiveInstances().length > 0;
|
|
979
979
|
return runningTasks || activeSubagents;
|
|
980
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Check if there are pending items (messages, bang commands, or background
|
|
983
|
+
* task notifications) in the message queue. Background task completion
|
|
984
|
+
* notifications are enqueued before the main agent's dispatch consumes them,
|
|
985
|
+
* so callers waiting for the agent to fully settle must also wait on this.
|
|
986
|
+
*/
|
|
987
|
+
get hasPendingMessages() {
|
|
988
|
+
return this.messageQueue.hasPending();
|
|
989
|
+
}
|
|
981
990
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from "./utils/tokenCalculation.js";
|
|
|
22
22
|
export * from "./utils/gitUtils.js";
|
|
23
23
|
export * from "./utils/nameGenerator.js";
|
|
24
24
|
export * from "./utils/worktreeSession.js";
|
|
25
|
+
export { loadMergedWaveConfig } from "./services/configurationService.js";
|
|
25
26
|
export * from "./types/index.js";
|
|
26
27
|
export * from "./tools/buildTool.js";
|
|
27
28
|
export type { ToolPlugin, ToolResult, ToolContext } from "./tools/types.js";
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export * from "./utils/tokenCalculation.js";
|
|
|
26
26
|
export * from "./utils/gitUtils.js";
|
|
27
27
|
export * from "./utils/nameGenerator.js";
|
|
28
28
|
export * from "./utils/worktreeSession.js";
|
|
29
|
+
export { loadMergedWaveConfig } from "./services/configurationService.js";
|
|
29
30
|
export * from "./types/index.js";
|
|
30
31
|
// Export tool building utilities
|
|
31
32
|
export * from "./tools/buildTool.js";
|
|
@@ -128,6 +128,9 @@ export class AIManager {
|
|
|
128
128
|
getAutoMemoryEnabled() {
|
|
129
129
|
return this.configurationService.resolveAutoMemoryEnabled();
|
|
130
130
|
}
|
|
131
|
+
getWorktreeBaseRef() {
|
|
132
|
+
return this.configurationService.resolveWorktreeBaseRef();
|
|
133
|
+
}
|
|
131
134
|
getWorkdir() {
|
|
132
135
|
return this.container.get("Workdir") ?? process.cwd();
|
|
133
136
|
}
|
|
@@ -8,6 +8,7 @@ import { ToolManager } from "./toolManager.js";
|
|
|
8
8
|
import { AGENT_TOOL_NAME, TASK_CREATE_TOOL_NAME, TASK_GET_TOOL_NAME, TASK_LIST_TOOL_NAME, TASK_UPDATE_TOOL_NAME, } from "../constants/tools.js";
|
|
9
9
|
import { addConsolidatedAbortListener, createAbortPromise, } from "../utils/abortUtils.js";
|
|
10
10
|
import { BackgroundTaskManager } from "./backgroundTaskManager.js";
|
|
11
|
+
import { MessageQueue } from "./messageQueue.js";
|
|
11
12
|
import { logger } from "../utils/globalLogger.js";
|
|
12
13
|
export class SubagentManager {
|
|
13
14
|
constructor(container, options) {
|
|
@@ -135,6 +136,11 @@ export class SubagentManager {
|
|
|
135
136
|
const subagentId = randomUUID();
|
|
136
137
|
// Create a child container for the subagent to isolate its managers
|
|
137
138
|
const subagentContainer = this.container.createChild();
|
|
139
|
+
// Register an independent MessageQueue so the subagent's AIManager drains its
|
|
140
|
+
// own (empty) queue instead of falling back to the parent container's queue.
|
|
141
|
+
// Without this, concurrent background subagents steal sibling completion
|
|
142
|
+
// notifications from the parent queue, causing the main agent to exit early.
|
|
143
|
+
subagentContainer.register("MessageQueue", new MessageQueue());
|
|
138
144
|
// Register a modified AgentOptions without onLoadingChange to prevent subagent loading
|
|
139
145
|
// from affecting the parent agent's loading state
|
|
140
146
|
const parentOptions = this.container.get("AgentOptions");
|
|
@@ -85,6 +85,12 @@ export declare class ConfigurationService {
|
|
|
85
85
|
* @returns Resolved auto-memory enabled state
|
|
86
86
|
*/
|
|
87
87
|
resolveAutoMemoryEnabled(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Resolves worktree base ref with fallbacks
|
|
90
|
+
* Resolution priority: settings.json > default ("fresh")
|
|
91
|
+
* @returns Resolved worktree base ref
|
|
92
|
+
*/
|
|
93
|
+
resolveWorktreeBaseRef(): "fresh" | "head";
|
|
88
94
|
/**
|
|
89
95
|
* Resolves auto-memory extraction frequency with fallbacks
|
|
90
96
|
* Resolution priority: settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
|
|
@@ -241,6 +241,19 @@ export class ConfigurationService {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
+
// Validate worktree if present
|
|
245
|
+
if (config.worktree !== undefined) {
|
|
246
|
+
if (typeof config.worktree !== "object" || config.worktree === null) {
|
|
247
|
+
result.isValid = false;
|
|
248
|
+
result.errors.push("worktree configuration must be an object");
|
|
249
|
+
}
|
|
250
|
+
else if (config.worktree.baseRef !== undefined &&
|
|
251
|
+
config.worktree.baseRef !== "fresh" &&
|
|
252
|
+
config.worktree.baseRef !== "head") {
|
|
253
|
+
result.isValid = false;
|
|
254
|
+
result.errors.push(`Invalid worktree.baseRef: "${config.worktree.baseRef}". Must be "fresh" or "head".`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
244
257
|
return result;
|
|
245
258
|
}
|
|
246
259
|
/**
|
|
@@ -506,6 +519,18 @@ export class ConfigurationService {
|
|
|
506
519
|
// 3. Default (true)
|
|
507
520
|
return true;
|
|
508
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Resolves worktree base ref with fallbacks
|
|
524
|
+
* Resolution priority: settings.json > default ("fresh")
|
|
525
|
+
* @returns Resolved worktree base ref
|
|
526
|
+
*/
|
|
527
|
+
resolveWorktreeBaseRef() {
|
|
528
|
+
const baseRef = this.currentConfiguration?.worktree?.baseRef;
|
|
529
|
+
if (baseRef === "head") {
|
|
530
|
+
return "head";
|
|
531
|
+
}
|
|
532
|
+
return "fresh";
|
|
533
|
+
}
|
|
509
534
|
/**
|
|
510
535
|
* Resolves auto-memory extraction frequency with fallbacks
|
|
511
536
|
* Resolution priority: settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
|
|
@@ -944,6 +969,7 @@ export function loadWaveConfigFromFile(filePath) {
|
|
|
944
969
|
: undefined,
|
|
945
970
|
models: config.models || undefined,
|
|
946
971
|
marketplaces: config.marketplaces || undefined,
|
|
972
|
+
worktree: config.worktree || undefined,
|
|
947
973
|
};
|
|
948
974
|
}
|
|
949
975
|
catch (error) {
|
|
@@ -1074,6 +1100,10 @@ export function loadMergedWaveConfig(workdir) {
|
|
|
1074
1100
|
mergedConfig.marketplaces = {};
|
|
1075
1101
|
Object.assign(mergedConfig.marketplaces, config.marketplaces);
|
|
1076
1102
|
}
|
|
1103
|
+
// Merge worktree (last one wins)
|
|
1104
|
+
if (config.worktree !== undefined) {
|
|
1105
|
+
mergedConfig.worktree = config.worktree;
|
|
1106
|
+
}
|
|
1077
1107
|
// Merge models
|
|
1078
1108
|
if (config.models) {
|
|
1079
1109
|
if (!mergedConfig.models)
|
|
@@ -1111,5 +1141,6 @@ export function loadMergedWaveConfig(workdir) {
|
|
|
1111
1141
|
models: mergedConfig.models && Object.keys(mergedConfig.models).length > 0
|
|
1112
1142
|
? mergedConfig.models
|
|
1113
1143
|
: undefined,
|
|
1144
|
+
worktree: mergedConfig.worktree,
|
|
1114
1145
|
};
|
|
1115
1146
|
}
|
|
@@ -286,6 +286,8 @@ export function mergeRemoteSettings(localMerged, remote) {
|
|
|
286
286
|
result.autoMemoryEnabled = remote.autoMemoryEnabled;
|
|
287
287
|
if (remote.autoMemoryFrequency !== undefined)
|
|
288
288
|
result.autoMemoryFrequency = remote.autoMemoryFrequency;
|
|
289
|
+
if (remote.worktree !== undefined)
|
|
290
|
+
result.worktree = remote.worktree;
|
|
289
291
|
if (remote.models !== undefined)
|
|
290
292
|
result.models = remote.models;
|
|
291
293
|
if (remote.marketplaces !== undefined)
|
|
@@ -83,7 +83,8 @@ export const enterWorktreeTool = {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
// Create the worktree (captures originalHeadCommit internally)
|
|
86
|
-
const
|
|
86
|
+
const baseRef = context.aiManager?.getWorktreeBaseRef?.();
|
|
87
|
+
const worktreeInfo = createWorktree(name, mainRepoRoot, { baseRef });
|
|
87
88
|
// Build session state
|
|
88
89
|
const session = {
|
|
89
90
|
originalCwd: context.workdir,
|
|
@@ -50,6 +50,11 @@ export interface WaveConfiguration {
|
|
|
50
50
|
monitoring?: {
|
|
51
51
|
telemetry?: Partial<TelemetryConfig>;
|
|
52
52
|
};
|
|
53
|
+
/** Worktree configuration */
|
|
54
|
+
worktree?: {
|
|
55
|
+
/** Base ref for new worktrees: "fresh" (origin/<default-branch>, default) | "head" (local HEAD) */
|
|
56
|
+
baseRef?: "fresh" | "head";
|
|
57
|
+
};
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* Legacy alias for backward compatibility - will be deprecated
|
|
@@ -15,8 +15,6 @@ export interface PermissionDecision {
|
|
|
15
15
|
newPermissionMode?: PermissionMode;
|
|
16
16
|
/** Signal to persist a new allowed rule */
|
|
17
17
|
newPermissionRule?: string;
|
|
18
|
-
/** Signal to clear the conversation context and proceed with the plan */
|
|
19
|
-
clearContext?: boolean;
|
|
20
18
|
}
|
|
21
19
|
/** Callback function for custom permission logic */
|
|
22
20
|
export type PermissionCallback = (context: ToolPermissionContext) => Promise<PermissionDecision>;
|
|
@@ -44,6 +44,33 @@ export interface BackgroundWorkflow extends BackgroundTaskBase {
|
|
|
44
44
|
runId: string;
|
|
45
45
|
}
|
|
46
46
|
export type BackgroundTask = BackgroundShell | BackgroundSubagent | BackgroundWorkflow;
|
|
47
|
+
/**
|
|
48
|
+
* Serializable summary of a BackgroundTask, used for notifications where the
|
|
49
|
+
* full stdout/stderr and non-serializable process/onStop fields must be
|
|
50
|
+
* stripped to control payload size. Output is fetched on demand via
|
|
51
|
+
* getBackgroundTaskOutput.
|
|
52
|
+
*/
|
|
53
|
+
export interface BackgroundTaskSummary {
|
|
54
|
+
id: string;
|
|
55
|
+
type: BackgroundTaskType;
|
|
56
|
+
status: BackgroundTaskStatus;
|
|
57
|
+
startTime: number;
|
|
58
|
+
endTime?: number;
|
|
59
|
+
command?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
exitCode?: number;
|
|
62
|
+
runtime?: number;
|
|
63
|
+
outputPath?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Output snapshot returned by getBackgroundTaskOutput. */
|
|
66
|
+
export interface BackgroundTaskOutput {
|
|
67
|
+
stdout: string;
|
|
68
|
+
stderr: string;
|
|
69
|
+
status: BackgroundTaskStatus;
|
|
70
|
+
outputPath?: string;
|
|
71
|
+
type: BackgroundTaskType;
|
|
72
|
+
exitCode?: number;
|
|
73
|
+
}
|
|
47
74
|
export interface ForegroundTask {
|
|
48
75
|
id: string;
|
|
49
76
|
backgroundHandler: () => Promise<void>;
|
package/dist/types/workflow.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { WorkflowRun, WorkflowMeta, WorkflowPhaseState, } from "../workflow/types.js";
|
|
1
|
+
export type { WorkflowRun, WorkflowMeta, WorkflowPhaseState, SerializableWorkflowRun, } from "../workflow/types.js";
|
|
@@ -179,21 +179,12 @@ export function setupAgentContainer(setupOptions) {
|
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
181
|
const decision = await options.canUseTool(context);
|
|
182
|
-
const planFilePath = permissionManager.getPlanFilePath();
|
|
183
182
|
if (decision.newPermissionMode) {
|
|
184
183
|
setPermissionMode(decision.newPermissionMode);
|
|
185
184
|
}
|
|
186
185
|
if (decision.newPermissionRule) {
|
|
187
186
|
await addPermissionRule(decision.newPermissionRule);
|
|
188
187
|
}
|
|
189
|
-
if (decision.clearContext) {
|
|
190
|
-
messageManager.clearMessages();
|
|
191
|
-
if (planFilePath) {
|
|
192
|
-
messageManager.addUserMessage({
|
|
193
|
-
content: `Implement the plan at ${planFilePath}`,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
188
|
return decision;
|
|
198
189
|
}
|
|
199
190
|
: undefined;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* "WorktreeSession" container slot registered in containerSetup.ts and accessed via
|
|
6
6
|
* AIManager.getWorktreeSession()/setWorktreeSession()). This keeps worktree state
|
|
7
7
|
* isolated per session in stdio multi-agent mode — a process-level singleton would
|
|
8
|
-
* leak state across concurrent sessions (see specs/
|
|
8
|
+
* leak state across concurrent sessions (see docs/specs/multi-agent/worktree.md FR-042).
|
|
9
9
|
*/
|
|
10
10
|
export interface WorktreeSession {
|
|
11
11
|
/** The working directory the session was in before EnterWorktree */
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* "WorktreeSession" container slot registered in containerSetup.ts and accessed via
|
|
6
6
|
* AIManager.getWorktreeSession()/setWorktreeSession()). This keeps worktree state
|
|
7
7
|
* isolated per session in stdio multi-agent mode — a process-level singleton would
|
|
8
|
-
* leak state across concurrent sessions (see specs/
|
|
8
|
+
* leak state across concurrent sessions (see docs/specs/multi-agent/worktree.md FR-042).
|
|
9
9
|
*/
|
|
10
10
|
export {};
|
|
@@ -25,8 +25,14 @@ export declare function generateWorktreeName(): string;
|
|
|
25
25
|
export declare function getHeadCommit(cwd: string): string;
|
|
26
26
|
/**
|
|
27
27
|
* Create a git worktree for use during a session.
|
|
28
|
+
* @param name Worktree name
|
|
29
|
+
* @param cwd Current working directory (will be resolved to main repo root)
|
|
30
|
+
* @param options Optional creation options
|
|
31
|
+
* @param options.baseRef "fresh" (default, origin/<default-branch>) | "head" (local HEAD)
|
|
28
32
|
*/
|
|
29
|
-
export declare function createWorktree(name: string, cwd: string
|
|
33
|
+
export declare function createWorktree(name: string, cwd: string, options?: {
|
|
34
|
+
baseRef?: "fresh" | "head";
|
|
35
|
+
}): WorktreeInfo;
|
|
30
36
|
/**
|
|
31
37
|
* Remove a git worktree and its branch.
|
|
32
38
|
*/
|
|
@@ -68,8 +68,12 @@ export function getHeadCommit(cwd) {
|
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
70
|
* Create a git worktree for use during a session.
|
|
71
|
+
* @param name Worktree name
|
|
72
|
+
* @param cwd Current working directory (will be resolved to main repo root)
|
|
73
|
+
* @param options Optional creation options
|
|
74
|
+
* @param options.baseRef "fresh" (default, origin/<default-branch>) | "head" (local HEAD)
|
|
71
75
|
*/
|
|
72
|
-
export function createWorktree(name, cwd) {
|
|
76
|
+
export function createWorktree(name, cwd, options) {
|
|
73
77
|
const repoRoot = getGitMainRepoRoot(cwd);
|
|
74
78
|
if (!repoRoot) {
|
|
75
79
|
throw new Error("Cannot create a worktree: not in a git repository. Configure WorktreeCreate and WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.");
|
|
@@ -78,7 +82,8 @@ export function createWorktree(name, cwd) {
|
|
|
78
82
|
const originalHeadCommit = getHeadCommit(cwd);
|
|
79
83
|
const worktreePath = path.join(repoRoot, ".wave", "worktrees", name);
|
|
80
84
|
const branchName = `worktree-${name}`;
|
|
81
|
-
const
|
|
85
|
+
const useHead = options?.baseRef === "head";
|
|
86
|
+
const baseBranch = useHead ? "HEAD" : getDefaultRemoteBranch(cwd);
|
|
82
87
|
// Ensure Wave runtime files are git-excluded in this repo
|
|
83
88
|
ensureWaveRuntimeFilesExcluded(cwd);
|
|
84
89
|
// Ensure parent directory exists
|
|
@@ -144,8 +149,9 @@ export function createWorktree(name, cwd) {
|
|
|
144
149
|
throw new Error(`Failed to add worktree: ${innerError.message}`);
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
|
-
if (
|
|
148
|
-
stderr.includes("
|
|
152
|
+
if (!useHead &&
|
|
153
|
+
(stderr.includes("not a valid object name") ||
|
|
154
|
+
stderr.includes("unknown revision"))) {
|
|
149
155
|
// Base branch not fetched yet — try fetching then retrying
|
|
150
156
|
const branchNameOnly = baseBranch.split("/").pop();
|
|
151
157
|
try {
|
package/dist/workflow/types.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ export interface WorkflowRun {
|
|
|
38
38
|
/** Error message from the failed agent */
|
|
39
39
|
failedAgentError?: string;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Serializable workflow run for stdio transport, with the non-serializable
|
|
43
|
+
* `completionPromise` stripped. Returned by the `getWorkflowRuns` RPC.
|
|
44
|
+
*/
|
|
45
|
+
export type SerializableWorkflowRun = Omit<WorkflowRun, "completionPromise">;
|
|
41
46
|
export interface JournalEntry {
|
|
42
47
|
agentIndex: number;
|
|
43
48
|
prompt: string;
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -1266,4 +1266,14 @@ export class Agent {
|
|
|
1266
1266
|
this.subagentManager.getActiveInstances().length > 0;
|
|
1267
1267
|
return runningTasks || activeSubagents;
|
|
1268
1268
|
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Check if there are pending items (messages, bang commands, or background
|
|
1272
|
+
* task notifications) in the message queue. Background task completion
|
|
1273
|
+
* notifications are enqueued before the main agent's dispatch consumes them,
|
|
1274
|
+
* so callers waiting for the agent to fully settle must also wait on this.
|
|
1275
|
+
*/
|
|
1276
|
+
public get hasPendingMessages(): boolean {
|
|
1277
|
+
return this.messageQueue.hasPending();
|
|
1278
|
+
}
|
|
1269
1279
|
}
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./utils/tokenCalculation.js";
|
|
|
29
29
|
export * from "./utils/gitUtils.js";
|
|
30
30
|
export * from "./utils/nameGenerator.js";
|
|
31
31
|
export * from "./utils/worktreeSession.js";
|
|
32
|
+
export { loadMergedWaveConfig } from "./services/configurationService.js";
|
|
32
33
|
export * from "./types/index.js";
|
|
33
34
|
|
|
34
35
|
// Export tool building utilities
|
|
@@ -252,6 +252,10 @@ export class AIManager {
|
|
|
252
252
|
return this.configurationService.resolveAutoMemoryEnabled();
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
public getWorktreeBaseRef(): "fresh" | "head" {
|
|
256
|
+
return this.configurationService.resolveWorktreeBaseRef();
|
|
257
|
+
}
|
|
258
|
+
|
|
255
259
|
public getWorkdir(): string {
|
|
256
260
|
return this.container.get<string>("Workdir") ?? process.cwd();
|
|
257
261
|
}
|
|
@@ -290,6 +290,12 @@ export class SubagentManager {
|
|
|
290
290
|
// Create a child container for the subagent to isolate its managers
|
|
291
291
|
const subagentContainer = this.container.createChild();
|
|
292
292
|
|
|
293
|
+
// Register an independent MessageQueue so the subagent's AIManager drains its
|
|
294
|
+
// own (empty) queue instead of falling back to the parent container's queue.
|
|
295
|
+
// Without this, concurrent background subagents steal sibling completion
|
|
296
|
+
// notifications from the parent queue, causing the main agent to exit early.
|
|
297
|
+
subagentContainer.register("MessageQueue", new MessageQueue());
|
|
298
|
+
|
|
293
299
|
// Register a modified AgentOptions without onLoadingChange to prevent subagent loading
|
|
294
300
|
// from affecting the parent agent's loading state
|
|
295
301
|
const parentOptions =
|
|
@@ -320,6 +320,23 @@ export class ConfigurationService {
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// Validate worktree if present
|
|
324
|
+
if (config.worktree !== undefined) {
|
|
325
|
+
if (typeof config.worktree !== "object" || config.worktree === null) {
|
|
326
|
+
result.isValid = false;
|
|
327
|
+
result.errors.push("worktree configuration must be an object");
|
|
328
|
+
} else if (
|
|
329
|
+
config.worktree.baseRef !== undefined &&
|
|
330
|
+
config.worktree.baseRef !== "fresh" &&
|
|
331
|
+
config.worktree.baseRef !== "head"
|
|
332
|
+
) {
|
|
333
|
+
result.isValid = false;
|
|
334
|
+
result.errors.push(
|
|
335
|
+
`Invalid worktree.baseRef: "${config.worktree.baseRef}". Must be "fresh" or "head".`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
323
340
|
return result;
|
|
324
341
|
}
|
|
325
342
|
|
|
@@ -640,6 +657,19 @@ export class ConfigurationService {
|
|
|
640
657
|
return true;
|
|
641
658
|
}
|
|
642
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Resolves worktree base ref with fallbacks
|
|
662
|
+
* Resolution priority: settings.json > default ("fresh")
|
|
663
|
+
* @returns Resolved worktree base ref
|
|
664
|
+
*/
|
|
665
|
+
resolveWorktreeBaseRef(): "fresh" | "head" {
|
|
666
|
+
const baseRef = this.currentConfiguration?.worktree?.baseRef;
|
|
667
|
+
if (baseRef === "head") {
|
|
668
|
+
return "head";
|
|
669
|
+
}
|
|
670
|
+
return "fresh";
|
|
671
|
+
}
|
|
672
|
+
|
|
643
673
|
/**
|
|
644
674
|
* Resolves auto-memory extraction frequency with fallbacks
|
|
645
675
|
* Resolution priority: settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
|
|
@@ -1172,6 +1202,7 @@ export function loadWaveConfigFromFile(
|
|
|
1172
1202
|
: undefined,
|
|
1173
1203
|
models: config.models || undefined,
|
|
1174
1204
|
marketplaces: config.marketplaces || undefined,
|
|
1205
|
+
worktree: config.worktree || undefined,
|
|
1175
1206
|
};
|
|
1176
1207
|
} catch (error) {
|
|
1177
1208
|
if (error instanceof SyntaxError) {
|
|
@@ -1320,6 +1351,11 @@ export function loadMergedWaveConfig(
|
|
|
1320
1351
|
Object.assign(mergedConfig.marketplaces, config.marketplaces);
|
|
1321
1352
|
}
|
|
1322
1353
|
|
|
1354
|
+
// Merge worktree (last one wins)
|
|
1355
|
+
if (config.worktree !== undefined) {
|
|
1356
|
+
mergedConfig.worktree = config.worktree;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1323
1359
|
// Merge models
|
|
1324
1360
|
if (config.models) {
|
|
1325
1361
|
if (!mergedConfig.models) mergedConfig.models = {};
|
|
@@ -1363,5 +1399,6 @@ export function loadMergedWaveConfig(
|
|
|
1363
1399
|
mergedConfig.models && Object.keys(mergedConfig.models).length > 0
|
|
1364
1400
|
? mergedConfig.models
|
|
1365
1401
|
: undefined,
|
|
1402
|
+
worktree: mergedConfig.worktree,
|
|
1366
1403
|
};
|
|
1367
1404
|
}
|
|
@@ -326,6 +326,7 @@ export function mergeRemoteSettings(
|
|
|
326
326
|
result.autoMemoryEnabled = remote.autoMemoryEnabled;
|
|
327
327
|
if (remote.autoMemoryFrequency !== undefined)
|
|
328
328
|
result.autoMemoryFrequency = remote.autoMemoryFrequency;
|
|
329
|
+
if (remote.worktree !== undefined) result.worktree = remote.worktree;
|
|
329
330
|
if (remote.models !== undefined) result.models = remote.models;
|
|
330
331
|
if (remote.marketplaces !== undefined)
|
|
331
332
|
result.marketplaces = remote.marketplaces;
|
|
@@ -102,7 +102,8 @@ export const enterWorktreeTool: ToolPlugin = {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Create the worktree (captures originalHeadCommit internally)
|
|
105
|
-
const
|
|
105
|
+
const baseRef = context.aiManager?.getWorktreeBaseRef?.();
|
|
106
|
+
const worktreeInfo = createWorktree(name, mainRepoRoot, { baseRef });
|
|
106
107
|
|
|
107
108
|
// Build session state
|
|
108
109
|
const session: WorktreeSession = {
|
|
@@ -54,6 +54,11 @@ export interface WaveConfiguration {
|
|
|
54
54
|
monitoring?: {
|
|
55
55
|
telemetry?: Partial<TelemetryConfig>;
|
|
56
56
|
};
|
|
57
|
+
/** Worktree configuration */
|
|
58
|
+
worktree?: {
|
|
59
|
+
/** Base ref for new worktrees: "fresh" (origin/<default-branch>, default) | "head" (local HEAD) */
|
|
60
|
+
baseRef?: "fresh" | "head";
|
|
61
|
+
};
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
/**
|
package/src/types/permissions.ts
CHANGED
|
@@ -35,8 +35,6 @@ export interface PermissionDecision {
|
|
|
35
35
|
newPermissionMode?: PermissionMode;
|
|
36
36
|
/** Signal to persist a new allowed rule */
|
|
37
37
|
newPermissionRule?: string;
|
|
38
|
-
/** Signal to clear the conversation context and proceed with the plan */
|
|
39
|
-
clearContext?: boolean;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
/** Callback function for custom permission logic */
|
package/src/types/processes.ts
CHANGED
|
@@ -59,6 +59,35 @@ export type BackgroundTask =
|
|
|
59
59
|
| BackgroundSubagent
|
|
60
60
|
| BackgroundWorkflow;
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Serializable summary of a BackgroundTask, used for notifications where the
|
|
64
|
+
* full stdout/stderr and non-serializable process/onStop fields must be
|
|
65
|
+
* stripped to control payload size. Output is fetched on demand via
|
|
66
|
+
* getBackgroundTaskOutput.
|
|
67
|
+
*/
|
|
68
|
+
export interface BackgroundTaskSummary {
|
|
69
|
+
id: string;
|
|
70
|
+
type: BackgroundTaskType;
|
|
71
|
+
status: BackgroundTaskStatus;
|
|
72
|
+
startTime: number;
|
|
73
|
+
endTime?: number;
|
|
74
|
+
command?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
exitCode?: number;
|
|
77
|
+
runtime?: number;
|
|
78
|
+
outputPath?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Output snapshot returned by getBackgroundTaskOutput. */
|
|
82
|
+
export interface BackgroundTaskOutput {
|
|
83
|
+
stdout: string;
|
|
84
|
+
stderr: string;
|
|
85
|
+
status: BackgroundTaskStatus;
|
|
86
|
+
outputPath?: string;
|
|
87
|
+
type: BackgroundTaskType;
|
|
88
|
+
exitCode?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
export interface ForegroundTask {
|
|
63
92
|
id: string;
|
|
64
93
|
backgroundHandler: () => Promise<void>;
|
package/src/types/workflow.ts
CHANGED
|
@@ -256,8 +256,6 @@ export function setupAgentContainer(
|
|
|
256
256
|
|
|
257
257
|
const decision = await options.canUseTool!(context);
|
|
258
258
|
|
|
259
|
-
const planFilePath = permissionManager.getPlanFilePath();
|
|
260
|
-
|
|
261
259
|
if (decision.newPermissionMode) {
|
|
262
260
|
setPermissionMode(decision.newPermissionMode);
|
|
263
261
|
}
|
|
@@ -266,15 +264,6 @@ export function setupAgentContainer(
|
|
|
266
264
|
await addPermissionRule(decision.newPermissionRule);
|
|
267
265
|
}
|
|
268
266
|
|
|
269
|
-
if (decision.clearContext) {
|
|
270
|
-
messageManager.clearMessages();
|
|
271
|
-
if (planFilePath) {
|
|
272
|
-
messageManager.addUserMessage({
|
|
273
|
-
content: `Implement the plan at ${planFilePath}`,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
267
|
return decision;
|
|
279
268
|
}
|
|
280
269
|
: undefined;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* "WorktreeSession" container slot registered in containerSetup.ts and accessed via
|
|
6
6
|
* AIManager.getWorktreeSession()/setWorktreeSession()). This keeps worktree state
|
|
7
7
|
* isolated per session in stdio multi-agent mode — a process-level singleton would
|
|
8
|
-
* leak state across concurrent sessions (see specs/
|
|
8
|
+
* leak state across concurrent sessions (see docs/specs/multi-agent/worktree.md FR-042).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export interface WorktreeSession {
|
|
@@ -93,8 +93,16 @@ export function getHeadCommit(cwd: string): string {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Create a git worktree for use during a session.
|
|
96
|
+
* @param name Worktree name
|
|
97
|
+
* @param cwd Current working directory (will be resolved to main repo root)
|
|
98
|
+
* @param options Optional creation options
|
|
99
|
+
* @param options.baseRef "fresh" (default, origin/<default-branch>) | "head" (local HEAD)
|
|
96
100
|
*/
|
|
97
|
-
export function createWorktree(
|
|
101
|
+
export function createWorktree(
|
|
102
|
+
name: string,
|
|
103
|
+
cwd: string,
|
|
104
|
+
options?: { baseRef?: "fresh" | "head" },
|
|
105
|
+
): WorktreeInfo {
|
|
98
106
|
const repoRoot = getGitMainRepoRoot(cwd);
|
|
99
107
|
if (!repoRoot) {
|
|
100
108
|
throw new Error(
|
|
@@ -107,7 +115,8 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
107
115
|
|
|
108
116
|
const worktreePath = path.join(repoRoot, ".wave", "worktrees", name);
|
|
109
117
|
const branchName = `worktree-${name}`;
|
|
110
|
-
const
|
|
118
|
+
const useHead = options?.baseRef === "head";
|
|
119
|
+
const baseBranch = useHead ? "HEAD" : getDefaultRemoteBranch(cwd);
|
|
111
120
|
|
|
112
121
|
// Ensure Wave runtime files are git-excluded in this repo
|
|
113
122
|
ensureWaveRuntimeFilesExcluded(cwd);
|
|
@@ -183,8 +192,9 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
183
192
|
}
|
|
184
193
|
}
|
|
185
194
|
if (
|
|
186
|
-
|
|
187
|
-
stderr.includes("
|
|
195
|
+
!useHead &&
|
|
196
|
+
(stderr.includes("not a valid object name") ||
|
|
197
|
+
stderr.includes("unknown revision"))
|
|
188
198
|
) {
|
|
189
199
|
// Base branch not fetched yet — try fetching then retrying
|
|
190
200
|
const branchNameOnly = baseBranch.split("/").pop()!;
|
package/src/workflow/types.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface WorkflowRun {
|
|
|
42
42
|
failedAgentError?: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Serializable workflow run for stdio transport, with the non-serializable
|
|
47
|
+
* `completionPromise` stripped. Returned by the `getWorkflowRuns` RPC.
|
|
48
|
+
*/
|
|
49
|
+
export type SerializableWorkflowRun = Omit<WorkflowRun, "completionPromise">;
|
|
50
|
+
|
|
45
51
|
export interface JournalEntry {
|
|
46
52
|
agentIndex: number;
|
|
47
53
|
prompt: string;
|