wave-agent-sdk 1.1.4 → 1.2.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 +72 -24
- package/dist/agent.js +123 -31
- package/dist/builtin/plugins.js +17 -2
- package/dist/builtin/skills/settings.js +6 -8
- package/dist/core/plugin.d.ts +4 -0
- package/dist/core/plugin.js +7 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/managers/aiManager.d.ts +0 -24
- package/dist/managers/aiManager.js +23 -181
- package/dist/managers/bashModeManager.d.ts +33 -0
- package/dist/managers/bashModeManager.js +110 -0
- package/dist/managers/hookManager.d.ts +5 -0
- package/dist/managers/hookManager.js +7 -0
- package/dist/managers/liveConfigManager.js +3 -3
- package/dist/managers/mcpManager.d.ts +23 -0
- package/dist/managers/mcpManager.js +162 -14
- package/dist/managers/messageManager.d.ts +12 -13
- package/dist/managers/messageManager.js +63 -60
- package/dist/managers/permissionManager.d.ts +29 -0
- package/dist/managers/permissionManager.js +158 -70
- package/dist/managers/planManager.d.ts +9 -0
- package/dist/managers/planManager.js +19 -1
- package/dist/managers/skillManager.d.ts +31 -0
- package/dist/managers/skillManager.js +122 -12
- package/dist/managers/slashCommandManager.js +9 -28
- package/dist/managers/subagentManager.d.ts +7 -0
- package/dist/managers/subagentManager.js +61 -9
- package/dist/managers/workflowManager.js +6 -0
- package/dist/prompts/index.d.ts +0 -1
- package/dist/prompts/index.js +0 -4
- package/dist/services/MarketplaceService.js +36 -14
- package/dist/services/configurationService.d.ts +34 -2
- package/dist/services/configurationService.js +123 -16
- package/dist/services/initializationService.js +2 -2
- package/dist/services/jsonlHandler.d.ts +14 -0
- package/dist/services/jsonlHandler.js +44 -1
- package/dist/services/memory.d.ts +14 -0
- package/dist/services/memory.js +33 -0
- package/dist/services/officialMarketplaceMirror.d.ts +85 -0
- package/dist/services/officialMarketplaceMirror.js +289 -0
- package/dist/services/remoteSettingsService.js +4 -4
- package/dist/services/session.js +30 -13
- package/dist/services/worktreeHooks.js +6 -1
- package/dist/stdio/index.d.ts +10 -0
- package/dist/stdio/index.js +10 -0
- package/dist/stdio/notificationRouter.d.ts +38 -0
- package/dist/stdio/notificationRouter.js +96 -0
- package/dist/stdio/rpcClient.d.ts +18 -0
- package/dist/stdio/rpcClient.js +10 -0
- package/dist/stdio/stdioAgent.d.ts +222 -0
- package/dist/stdio/stdioAgent.js +341 -0
- package/dist/tools/bashTool.js +2 -0
- package/dist/tools/exitPlanMode.js +10 -2
- package/dist/types/agent.d.ts +8 -0
- package/dist/types/commands.d.ts +7 -0
- package/dist/types/configuration.d.ts +6 -1
- package/dist/types/hooks.d.ts +1 -0
- package/dist/types/hooks.js +19 -0
- package/dist/types/mcp.d.ts +3 -0
- package/dist/types/messaging.d.ts +1 -8
- package/dist/types/skills.d.ts +11 -0
- package/dist/utils/bashParser.d.ts +17 -0
- package/dist/utils/bashParser.js +72 -0
- package/dist/utils/bashStructure/bashLexer.d.ts +96 -0
- package/dist/utils/bashStructure/bashLexer.js +676 -0
- package/dist/utils/bashStructure/bashParser.d.ts +144 -0
- package/dist/utils/bashStructure/bashParser.js +606 -0
- package/dist/utils/bashStructure/bashSemantics.d.ts +70 -0
- package/dist/utils/bashStructure/bashSemantics.js +477 -0
- package/dist/utils/bashStructure/index.d.ts +26 -0
- package/dist/utils/bashStructure/index.js +27 -0
- package/dist/utils/bashStructure/types.d.ts +62 -0
- package/dist/utils/bashStructure/types.js +47 -0
- package/dist/utils/container.d.ts +6 -0
- package/dist/utils/container.js +9 -0
- package/dist/utils/containerSetup.d.ts +11 -1
- package/dist/utils/containerSetup.js +26 -11
- package/dist/utils/fileUtils.d.ts +11 -0
- package/dist/utils/fileUtils.js +37 -0
- package/dist/utils/messageOperations.d.ts +0 -18
- package/dist/utils/messageOperations.js +0 -62
- package/dist/utils/subagentParser.js +9 -2
- package/dist/utils/tokenCalculation.js +0 -8
- package/dist/utils/worktreeUtils.d.ts +2 -1
- package/dist/utils/worktreeUtils.js +64 -34
- package/package.json +6 -1
- package/dist/managers/bangManager.d.ts +0 -26
- package/dist/managers/bangManager.js +0 -78
|
@@ -2,18 +2,15 @@ import * as aiService from "../services/aiService.js";
|
|
|
2
2
|
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
3
3
|
import { supportsVision } from "../utils/modelCapabilities.js";
|
|
4
4
|
import { persistToolImages } from "../utils/toolImagePersistence.js";
|
|
5
|
-
import { parseTaskNotificationXml
|
|
5
|
+
import { parseTaskNotificationXml } from "../utils/notificationXml.js";
|
|
6
6
|
import { estimateContextTokens } from "../utils/tokenCalculation.js";
|
|
7
7
|
import { estimateTokens } from "../utils/tokenEstimate.js";
|
|
8
8
|
import { getTaskReminderTurnCounts, maybeInjectTaskReminder, TASK_REMINDER_CONFIG, } from "../utils/taskReminder.js";
|
|
9
|
-
import {
|
|
10
|
-
import * as os from "node:os";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
11
10
|
import * as path from "node:path";
|
|
12
11
|
import { buildSystemPrompt, formatCompactSummary, getCompactPrompt, } from "../prompts/index.js";
|
|
13
12
|
import { buildPlanModeReminder, buildPlanModeReEntryReminder, buildExitedPlanModeReminder, wrapInSystemReminder, } from "../prompts/planModeReminders.js";
|
|
14
13
|
import { recoverTruncatedJson } from "../utils/stringUtils.js";
|
|
15
|
-
import { AGENT_TOOL_NAME, TASK_CREATE_TOOL_NAME, TASK_GET_TOOL_NAME, TASK_LIST_TOOL_NAME, TASK_UPDATE_TOOL_NAME, } from "../constants/tools.js";
|
|
16
|
-
import { addConsolidatedAbortListener } from "../utils/abortUtils.js";
|
|
17
14
|
import { logger } from "../utils/globalLogger.js";
|
|
18
15
|
import { startInteractionSpan, endInteractionSpan, startLLMRequestSpan, endLLMRequestSpan, resetTracingState, } from "../telemetry/sessionTracing.js";
|
|
19
16
|
import { logOTelEvent } from "../telemetry/events.js";
|
|
@@ -729,182 +726,6 @@ export class AIManager {
|
|
|
729
726
|
}
|
|
730
727
|
return result;
|
|
731
728
|
}
|
|
732
|
-
/**
|
|
733
|
-
* Fork subagent ("/subtask"): spawn a background agent that inherits the
|
|
734
|
-
* parent's full conversation context — the same system prompt, tools, model,
|
|
735
|
-
* and message prefix as the main loop — so the forked request prefix matches
|
|
736
|
-
* exactly and the prompt cache is reused. The fork runs to completion in the
|
|
737
|
-
* background via the shared BackgroundTaskManager (visible in /tasks, log at
|
|
738
|
-
* `os.tmpdir()/wave-subagent-<taskId>.log`); its final response is enqueued
|
|
739
|
-
* as a task notification carrying the `<result>` tag, so the main agent
|
|
740
|
-
* picks it up on the next turn and can act on it.
|
|
741
|
-
*
|
|
742
|
-
* Recursion is prevented by denying the Agent tool and the Task management
|
|
743
|
-
* tools (the fork cannot spawn further subagents or mutate the shared task
|
|
744
|
-
* list), and by honoring the parent's denied rules for everything else.
|
|
745
|
-
* Gate-approved tools execute locally in the stripped fork context (no
|
|
746
|
-
* permission prompts), mirroring the other fork paths.
|
|
747
|
-
*
|
|
748
|
-
* Returns the background task ID (empty string when no BackgroundTaskManager
|
|
749
|
-
* is registered, in which case the fork still runs but without task
|
|
750
|
-
* bookkeeping or notification).
|
|
751
|
-
*/
|
|
752
|
-
async runForkSubagent(prompt, options, abortSignal) {
|
|
753
|
-
const modelConfig = this.getModelConfig();
|
|
754
|
-
// Snapshot the conversation synchronously: the /subtask handler records
|
|
755
|
-
// the command echo in the transcript after this call, so the fork never
|
|
756
|
-
// sees its own trigger message duplicated.
|
|
757
|
-
const historyMessages = convertMessagesForAPI(this.messageManager.getMessages(), { supportsVision: supportsVision(modelConfig.capabilities) });
|
|
758
|
-
const backgroundTaskManager = this.backgroundTaskManager;
|
|
759
|
-
const permissionManager = this.permissionManager;
|
|
760
|
-
const messageQueue = this.container.has("MessageQueue")
|
|
761
|
-
? this.container.get("MessageQueue")
|
|
762
|
-
: undefined;
|
|
763
|
-
// Combine the caller's signal with an internal one so a task kill
|
|
764
|
-
// (BackgroundTaskManager.stopTask / cleanup) aborts the in-flight fork.
|
|
765
|
-
const abortController = new AbortController();
|
|
766
|
-
let abortCleanup;
|
|
767
|
-
if (abortSignal) {
|
|
768
|
-
abortCleanup = addConsolidatedAbortListener(abortSignal, [
|
|
769
|
-
() => abortController.abort(),
|
|
770
|
-
]);
|
|
771
|
-
}
|
|
772
|
-
// Recursion prevention: deny the Agent tool and Task tools, then honor the
|
|
773
|
-
// parent's denied rules for everything else.
|
|
774
|
-
const deniedTools = new Set([
|
|
775
|
-
AGENT_TOOL_NAME,
|
|
776
|
-
TASK_CREATE_TOOL_NAME,
|
|
777
|
-
TASK_GET_TOOL_NAME,
|
|
778
|
-
TASK_UPDATE_TOOL_NAME,
|
|
779
|
-
TASK_LIST_TOOL_NAME,
|
|
780
|
-
]);
|
|
781
|
-
const canUseTool = (name) => {
|
|
782
|
-
if (deniedTools.has(name))
|
|
783
|
-
return false;
|
|
784
|
-
if (permissionManager?.isToolDenied(name))
|
|
785
|
-
return false;
|
|
786
|
-
return true;
|
|
787
|
-
};
|
|
788
|
-
const deniedToolMessage = "The Agent tool and Task tools are not available in a fork subagent.";
|
|
789
|
-
// Background task bookkeeping mirrors subagentManager.executeAgent.
|
|
790
|
-
let taskId = "";
|
|
791
|
-
let logStream;
|
|
792
|
-
if (backgroundTaskManager) {
|
|
793
|
-
taskId = backgroundTaskManager.generateId();
|
|
794
|
-
const startTime = Date.now();
|
|
795
|
-
const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
|
|
796
|
-
logStream = createWriteStream(logPath, { flags: "a" });
|
|
797
|
-
backgroundTaskManager.addTask({
|
|
798
|
-
id: taskId,
|
|
799
|
-
type: "subagent",
|
|
800
|
-
status: "running",
|
|
801
|
-
startTime,
|
|
802
|
-
description: truncateWithMarker(options.description, 1000),
|
|
803
|
-
stdout: "",
|
|
804
|
-
stderr: "",
|
|
805
|
-
outputPath: logPath,
|
|
806
|
-
onStop: () => {
|
|
807
|
-
logStream?.destroy();
|
|
808
|
-
logStream = undefined;
|
|
809
|
-
abortController.abort();
|
|
810
|
-
},
|
|
811
|
-
});
|
|
812
|
-
logStream.write(`[${new Date().toISOString()}] Fork subagent started: ${options.description}\n`);
|
|
813
|
-
}
|
|
814
|
-
const finish = async (status, content) => {
|
|
815
|
-
if (!backgroundTaskManager || !taskId)
|
|
816
|
-
return;
|
|
817
|
-
const task = backgroundTaskManager.getTask(taskId);
|
|
818
|
-
if (!task)
|
|
819
|
-
return;
|
|
820
|
-
const wasAlreadyKilled = task.status === "killed";
|
|
821
|
-
const endTime = Date.now();
|
|
822
|
-
if (status === "completed") {
|
|
823
|
-
task.status = "completed";
|
|
824
|
-
task.stdout = content;
|
|
825
|
-
logStream?.write(`[${new Date().toISOString()}] Final response:\n${content}\n`);
|
|
826
|
-
}
|
|
827
|
-
else {
|
|
828
|
-
task.status = "failed";
|
|
829
|
-
task.stderr = content;
|
|
830
|
-
logStream?.write(`[${new Date().toISOString()}] Fork subagent failed: ${content}\n`);
|
|
831
|
-
}
|
|
832
|
-
task.endTime = endTime;
|
|
833
|
-
if (task.startTime)
|
|
834
|
-
task.runtime = endTime - task.startTime;
|
|
835
|
-
logStream?.end();
|
|
836
|
-
logStream = undefined;
|
|
837
|
-
// Skip the notification when the task was already stopped (e.g. by main
|
|
838
|
-
// agent shutdown) — mirrors subagentManager's wasAlreadyKilled guard.
|
|
839
|
-
if (!wasAlreadyKilled && messageQueue) {
|
|
840
|
-
const description = truncateWithMarker(options.description, 1000);
|
|
841
|
-
const summary = status === "completed"
|
|
842
|
-
? `Subtask "${description}" completed`
|
|
843
|
-
: `Subtask "${description}" failed: ${content}`;
|
|
844
|
-
messageQueue.enqueueNotification(taskNotificationToXml({
|
|
845
|
-
type: "task_notification",
|
|
846
|
-
taskId,
|
|
847
|
-
taskType: "agent",
|
|
848
|
-
status,
|
|
849
|
-
summary,
|
|
850
|
-
...(status === "completed" && { result: content }),
|
|
851
|
-
}));
|
|
852
|
-
}
|
|
853
|
-
};
|
|
854
|
-
// Fire-and-forget: the fork runs to completion in the background; the
|
|
855
|
-
// caller gets the task ID immediately. Tracked in the async work registry
|
|
856
|
-
// so destroy() drains it before returning (abort via the task's onStop
|
|
857
|
-
// makes the loop settle).
|
|
858
|
-
const forkPromise = (async () => {
|
|
859
|
-
try {
|
|
860
|
-
const result = await this.runForkLoop(historyMessages, prompt, {
|
|
861
|
-
// /subtask aligns with Claude Code's fork subagent
|
|
862
|
-
// (forkSubagent.ts:65): a loose upper bound that practically
|
|
863
|
-
// never binds.
|
|
864
|
-
maxTurns: options.maxTurns ?? 200,
|
|
865
|
-
canUseTool,
|
|
866
|
-
deniedToolMessage,
|
|
867
|
-
}, abortController.signal);
|
|
868
|
-
if (result.usage && this.callbacks?.onUsageAdded) {
|
|
869
|
-
this.callbacks.onUsageAdded({
|
|
870
|
-
...result.usage,
|
|
871
|
-
model: modelConfig.model,
|
|
872
|
-
operation_type: "agent",
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
if (result.content?.trim()) {
|
|
876
|
-
await finish("completed", result.content);
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
await finish("failed", "Fork subagent produced no text response");
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
catch (error) {
|
|
883
|
-
// Abort is the expected exit for a killed task; stopTask has already
|
|
884
|
-
// marked it "killed", so no notification is sent.
|
|
885
|
-
if (abortController.signal.aborted) {
|
|
886
|
-
const task = backgroundTaskManager?.getTask(taskId);
|
|
887
|
-
if (task && task.status === "running") {
|
|
888
|
-
task.status = "killed";
|
|
889
|
-
task.endTime = Date.now();
|
|
890
|
-
if (task.startTime)
|
|
891
|
-
task.runtime = task.endTime - task.startTime;
|
|
892
|
-
}
|
|
893
|
-
logStream?.end();
|
|
894
|
-
logStream = undefined;
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
logger?.error("Fork subagent execution failed:", error);
|
|
898
|
-
await finish("failed", error instanceof Error ? error.message : String(error));
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
finally {
|
|
902
|
-
abortCleanup?.();
|
|
903
|
-
}
|
|
904
|
-
})();
|
|
905
|
-
this.asyncWorkRegistry?.track(forkPromise);
|
|
906
|
-
return taskId;
|
|
907
|
-
}
|
|
908
729
|
/**
|
|
909
730
|
* Parse a fork tool call's JSON arguments, recovering truncated JSON the
|
|
910
731
|
* same way the main loop does. Unparseable arguments fall back to `{}` and
|
|
@@ -1271,6 +1092,18 @@ ${question}`;
|
|
|
1271
1092
|
this.abortController = abortController;
|
|
1272
1093
|
toolAbortController = new AbortController();
|
|
1273
1094
|
this.toolAbortController = toolAbortController;
|
|
1095
|
+
// Every tool call in a turn wires its own abort listener onto the
|
|
1096
|
+
// shared tool signal (see toolAbortController.signal in executeTool),
|
|
1097
|
+
// so a fan-out of >=11 parallel calls trips Node's default 10-listener
|
|
1098
|
+
// cap and spams MaxListenersExceededWarning. The signal dies with the
|
|
1099
|
+
// turn (toolAbortController is cleared in the tool loop), so there is
|
|
1100
|
+
// no real leak — raise the cap to silence the noise. Optional chaining
|
|
1101
|
+
// keeps this a no-op on Node >=26, which removed the EventTarget
|
|
1102
|
+
// warning mechanism (and the setMaxListeners method) entirely.
|
|
1103
|
+
const toolSignal = toolAbortController.signal;
|
|
1104
|
+
const turnSignal = abortController.signal;
|
|
1105
|
+
toolSignal.setMaxListeners?.(0);
|
|
1106
|
+
turnSignal.setMaxListeners?.(0);
|
|
1274
1107
|
}
|
|
1275
1108
|
else {
|
|
1276
1109
|
// Reuse existing controllers
|
|
@@ -1353,6 +1186,9 @@ ${question}`;
|
|
|
1353
1186
|
// Add streaming callbacks only if streaming is enabled
|
|
1354
1187
|
if (this.stream) {
|
|
1355
1188
|
callAgentOptions.onContentUpdate = (content) => {
|
|
1189
|
+
// Agent may have been destroyed mid-stream; ignore in-flight updates.
|
|
1190
|
+
if (!this.messageManager)
|
|
1191
|
+
return;
|
|
1356
1192
|
// Create assistant message on first chunk if not already created
|
|
1357
1193
|
if (!assistantMessageCreated) {
|
|
1358
1194
|
this.messageManager.addAssistantMessage();
|
|
@@ -1361,6 +1197,9 @@ ${question}`;
|
|
|
1361
1197
|
this.messageManager.updateCurrentMessageContent(content);
|
|
1362
1198
|
};
|
|
1363
1199
|
callAgentOptions.onToolUpdate = (toolCall) => {
|
|
1200
|
+
// Agent may have been destroyed mid-stream; ignore in-flight updates.
|
|
1201
|
+
if (!this.messageManager)
|
|
1202
|
+
return;
|
|
1364
1203
|
// Create assistant message on first tool update if not already created
|
|
1365
1204
|
if (!assistantMessageCreated) {
|
|
1366
1205
|
this.messageManager.addAssistantMessage();
|
|
@@ -1383,6 +1222,9 @@ ${question}`;
|
|
|
1383
1222
|
});
|
|
1384
1223
|
};
|
|
1385
1224
|
callAgentOptions.onReasoningUpdate = (reasoning) => {
|
|
1225
|
+
// Agent may have been destroyed mid-stream; ignore in-flight updates.
|
|
1226
|
+
if (!this.messageManager)
|
|
1227
|
+
return;
|
|
1386
1228
|
// Create assistant message on first reasoning update if not already created
|
|
1387
1229
|
if (!assistantMessageCreated) {
|
|
1388
1230
|
this.messageManager.addAssistantMessage();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Container } from "../utils/container.js";
|
|
2
|
+
export interface BashModeManagerOptions {
|
|
3
|
+
workdir: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CommandExecutionResult {
|
|
6
|
+
exitCode: number;
|
|
7
|
+
output: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Bash mode (`!command`) execution. The command is surfaced as a user-message
|
|
11
|
+
* tool block (name: BASH_TOOL_NAME) — the same rendering as fork-skill / `/plan`
|
|
12
|
+
* outputs (⎿ prefix + markdown, result enters the model context via the
|
|
13
|
+
* generic `<local-command-stdout>` rule) — and executed through the shared
|
|
14
|
+
* bash tool engine (shell resolution, CWD tracking, timeout, output handling).
|
|
15
|
+
*/
|
|
16
|
+
export declare class BashModeManager {
|
|
17
|
+
private container;
|
|
18
|
+
private workdir;
|
|
19
|
+
isCommandRunning: boolean;
|
|
20
|
+
private abortController;
|
|
21
|
+
onCommandRunningChange?: (running: boolean) => void;
|
|
22
|
+
constructor(container: Container, options: BashModeManagerOptions);
|
|
23
|
+
private get messageManager();
|
|
24
|
+
/**
|
|
25
|
+
* Merged env (OS env overlaid with this session's settings snapshot) for
|
|
26
|
+
* bash-mode subprocesses, so settings `env` vars reach them without
|
|
27
|
+
* polluting other sessions in one `wave --stdio` process.
|
|
28
|
+
*/
|
|
29
|
+
private get sessionEnv();
|
|
30
|
+
private setCommandRunning;
|
|
31
|
+
executeCommand(command: string): Promise<number>;
|
|
32
|
+
abortCommand(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { bashTool } from "../tools/bashTool.js";
|
|
2
|
+
import { BASH_TOOL_NAME } from "../constants/tools.js";
|
|
3
|
+
/**
|
|
4
|
+
* Bash mode (`!command`) execution. The command is surfaced as a user-message
|
|
5
|
+
* tool block (name: BASH_TOOL_NAME) — the same rendering as fork-skill / `/plan`
|
|
6
|
+
* outputs (⎿ prefix + markdown, result enters the model context via the
|
|
7
|
+
* generic `<local-command-stdout>` rule) — and executed through the shared
|
|
8
|
+
* bash tool engine (shell resolution, CWD tracking, timeout, output handling).
|
|
9
|
+
*/
|
|
10
|
+
export class BashModeManager {
|
|
11
|
+
constructor(container, options) {
|
|
12
|
+
this.container = container;
|
|
13
|
+
this.isCommandRunning = false;
|
|
14
|
+
this.abortController = null;
|
|
15
|
+
this.workdir = options.workdir;
|
|
16
|
+
}
|
|
17
|
+
get messageManager() {
|
|
18
|
+
return this.container.get("MessageManager");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Merged env (OS env overlaid with this session's settings snapshot) for
|
|
22
|
+
* bash-mode subprocesses, so settings `env` vars reach them without
|
|
23
|
+
* polluting other sessions in one `wave --stdio` process.
|
|
24
|
+
*/
|
|
25
|
+
get sessionEnv() {
|
|
26
|
+
return (this.container
|
|
27
|
+
.get("ConfigurationService")
|
|
28
|
+
?.getMergedEnv?.() ?? process.env);
|
|
29
|
+
}
|
|
30
|
+
setCommandRunning(isRunning) {
|
|
31
|
+
this.isCommandRunning = isRunning;
|
|
32
|
+
this.onCommandRunningChange?.(isRunning);
|
|
33
|
+
}
|
|
34
|
+
async executeCommand(command) {
|
|
35
|
+
if (this.isCommandRunning) {
|
|
36
|
+
throw new Error("Command already running");
|
|
37
|
+
}
|
|
38
|
+
this.setCommandRunning(true);
|
|
39
|
+
try {
|
|
40
|
+
// User message + running tool block. The user text mirrors the command
|
|
41
|
+
// itself (`!ls` renders as `!ls`), and incremental toolBlockUpdated
|
|
42
|
+
// notifications drive the live output on all three hosts.
|
|
43
|
+
const messageId = this.messageManager.addUserMessage({
|
|
44
|
+
content: command,
|
|
45
|
+
});
|
|
46
|
+
const blockId = this.messageManager.addToolBlockToMessage(messageId, {
|
|
47
|
+
name: BASH_TOOL_NAME,
|
|
48
|
+
parameters: command,
|
|
49
|
+
stage: "running",
|
|
50
|
+
});
|
|
51
|
+
this.abortController = new AbortController();
|
|
52
|
+
const taskManager = this.container.get("TaskManager");
|
|
53
|
+
// No permissionManager in the context: bash mode is an explicit user
|
|
54
|
+
// command, it must not be blocked by the permission gate.
|
|
55
|
+
const result = await bashTool.execute({ command }, {
|
|
56
|
+
workdir: this.workdir,
|
|
57
|
+
taskManager,
|
|
58
|
+
sessionEnv: this.sessionEnv,
|
|
59
|
+
abortSignal: this.abortController.signal,
|
|
60
|
+
// Stream realtime output into the running tool block — wired like
|
|
61
|
+
// aiManager tool execution (aiManager.ts updateToolBlock callbacks).
|
|
62
|
+
onShortResultUpdate: (shortResult) => {
|
|
63
|
+
this.messageManager.updateToolBlock({
|
|
64
|
+
id: blockId,
|
|
65
|
+
messageId,
|
|
66
|
+
shortResult,
|
|
67
|
+
stage: "running",
|
|
68
|
+
name: BASH_TOOL_NAME,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
onResultUpdate: (resultText) => {
|
|
72
|
+
this.messageManager.updateToolBlock({
|
|
73
|
+
id: blockId,
|
|
74
|
+
messageId,
|
|
75
|
+
result: resultText,
|
|
76
|
+
stage: "running",
|
|
77
|
+
name: BASH_TOOL_NAME,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
const exitCode = typeof result.metadata?.exitCode === "number"
|
|
82
|
+
? result.metadata.exitCode
|
|
83
|
+
: result.success
|
|
84
|
+
? 0
|
|
85
|
+
: 1;
|
|
86
|
+
const resultText = result.success
|
|
87
|
+
? result.content
|
|
88
|
+
: `[exit code: ${exitCode}]` +
|
|
89
|
+
(result.content ? `\n\n${result.content}` : "");
|
|
90
|
+
this.messageManager.updateToolBlock({
|
|
91
|
+
id: blockId,
|
|
92
|
+
messageId,
|
|
93
|
+
result: resultText,
|
|
94
|
+
shortResult: result.shortResult,
|
|
95
|
+
stage: "end",
|
|
96
|
+
success: result.success,
|
|
97
|
+
});
|
|
98
|
+
return exitCode;
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
this.abortController = null;
|
|
102
|
+
this.setCommandRunning(false);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
abortCommand() {
|
|
106
|
+
this.abortController?.abort();
|
|
107
|
+
this.abortController = null;
|
|
108
|
+
this.isCommandRunning = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -120,6 +120,11 @@ export declare class HookManager {
|
|
|
120
120
|
* Register hooks provided by a plugin
|
|
121
121
|
*/
|
|
122
122
|
registerPluginHooks(pluginRoot: string, hooks: PartialHookConfiguration): void;
|
|
123
|
+
/**
|
|
124
|
+
* Get hooks registered by plugins (read-only view for the settings UI).
|
|
125
|
+
* Plugin hooks carry pluginRoot on each command and are read-only in the UI.
|
|
126
|
+
*/
|
|
127
|
+
getPluginHooks(): PartialHookConfiguration;
|
|
123
128
|
/**
|
|
124
129
|
* Execute SessionStart hooks during initialization.
|
|
125
130
|
* Collects additionalContext and initialUserMessage from hook stdout.
|
|
@@ -670,6 +670,13 @@ export class HookManager {
|
|
|
670
670
|
this.mergeHooksConfiguration(this.pluginHooks, stampedHooks);
|
|
671
671
|
this.rebuildConfiguration();
|
|
672
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Get hooks registered by plugins (read-only view for the settings UI).
|
|
675
|
+
* Plugin hooks carry pluginRoot on each command and are read-only in the UI.
|
|
676
|
+
*/
|
|
677
|
+
getPluginHooks() {
|
|
678
|
+
return this.pluginHooks;
|
|
679
|
+
}
|
|
673
680
|
/**
|
|
674
681
|
* Execute SessionStart hooks during initialization.
|
|
675
682
|
* Collects additionalContext and initialUserMessage from hook stdout.
|
|
@@ -172,15 +172,15 @@ export class LiveConfigManager {
|
|
|
172
172
|
}
|
|
173
173
|
// Update permission manager if available
|
|
174
174
|
if (this.permissionManager) {
|
|
175
|
-
this.permissionManager.updateConfiguredPermissionMode(this.currentConfiguration.permissions?.
|
|
175
|
+
this.permissionManager.updateConfiguredPermissionMode(this.currentConfiguration.permissions?.defaultMode);
|
|
176
176
|
this.permissionManager.updateAllowedRules(this.currentConfiguration.permissions?.allow || []);
|
|
177
177
|
this.permissionManager.updateDeniedRules(this.currentConfiguration.permissions?.deny || []);
|
|
178
178
|
this.permissionManager.updateAdditionalDirectories(this.currentConfiguration.permissions?.additionalDirectories || []);
|
|
179
179
|
}
|
|
180
180
|
// Trigger reload callback. Awaited so fire-and-forget work spawned by the
|
|
181
181
|
// callback (e.g. skill rediscovery) completes before the reload resolves —
|
|
182
|
-
// otherwise Agent.create() can return
|
|
183
|
-
//
|
|
182
|
+
// otherwise Agent.create() can return while a skill rescan is still
|
|
183
|
+
// running (refreshSkills swaps caches atomically only after the scan).
|
|
184
184
|
await this.options.onReload?.(this.currentConfiguration);
|
|
185
185
|
return this.currentConfiguration;
|
|
186
186
|
}
|
|
@@ -32,11 +32,14 @@ export declare class McpManager {
|
|
|
32
32
|
private servers;
|
|
33
33
|
private connections;
|
|
34
34
|
private configPath;
|
|
35
|
+
private userConfigPath;
|
|
35
36
|
private workdir;
|
|
36
37
|
private callbacks;
|
|
37
38
|
private mcpServers;
|
|
38
39
|
private reconnectTimers;
|
|
39
40
|
private reconnectAttempts;
|
|
41
|
+
/** Explicit disconnectServer teardowns currently in flight */
|
|
42
|
+
private disconnecting;
|
|
40
43
|
constructor(container: Container, options?: McpManagerOptions);
|
|
41
44
|
/**
|
|
42
45
|
* Merged env for this session: OS env overlaid with the per-session settings
|
|
@@ -50,6 +53,18 @@ export declare class McpManager {
|
|
|
50
53
|
initialize(workdir: string, autoConnect?: boolean): Promise<void>;
|
|
51
54
|
ensureConfigLoaded(): Promise<McpConfig | null>;
|
|
52
55
|
loadConfig(): Promise<McpConfig | null>;
|
|
56
|
+
/**
|
|
57
|
+
* Read a raw MCP config file, tolerating ENOENT and corrupt JSON.
|
|
58
|
+
*/
|
|
59
|
+
private readConfigFile;
|
|
60
|
+
/**
|
|
61
|
+
* The user-level MCP config path (~/.wave/mcp.json).
|
|
62
|
+
*/
|
|
63
|
+
getUserConfigPath(): string;
|
|
64
|
+
/**
|
|
65
|
+
* The project-level MCP config path (<workdir>/.mcp.json).
|
|
66
|
+
*/
|
|
67
|
+
getProjectConfigPath(): string;
|
|
53
68
|
saveConfig(config: McpConfig): Promise<boolean>;
|
|
54
69
|
getConfig(): McpConfig | null;
|
|
55
70
|
getAllServers(): McpServerStatus[];
|
|
@@ -57,6 +72,14 @@ export declare class McpManager {
|
|
|
57
72
|
updateServerStatus(name: string, updates: Partial<McpServerStatus>): void;
|
|
58
73
|
addServer(name: string, config: McpServerConfig): boolean;
|
|
59
74
|
removeServer(name: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Remove a server from a persisted config file (user or project scope),
|
|
77
|
+
* then disconnect and drop it from the in-memory registry.
|
|
78
|
+
* @param scope - "user" removes from ~/.wave/mcp.json; "project" removes from <workdir>/.mcp.json
|
|
79
|
+
* @param name - The server name
|
|
80
|
+
* @returns true if the server was found and removed
|
|
81
|
+
*/
|
|
82
|
+
removeServerFromConfig(scope: "user" | "project", name: string): Promise<boolean>;
|
|
60
83
|
connectServer(name: string): Promise<boolean>;
|
|
61
84
|
/**
|
|
62
85
|
* Schedule auto-reconnect with exponential backoff.
|