wave-agent-sdk 0.19.8 → 1.0.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/builtin/plugins/sdd/.wave-plugin/plugin.json +8 -0
- package/builtin/plugins/sdd/hooks/hooks.json +14 -0
- package/builtin/plugins/sdd/scripts/session-start.js +24 -0
- package/builtin/plugins/sdd/scripts/spec-count.js +77 -0
- package/builtin/plugins/sdd/skills/specify/SKILL.md +47 -0
- package/builtin/plugins/sdd/skills/specify/templates/spec-template.md +47 -0
- package/builtin/skills/settings/ENV.md +15 -9
- package/builtin/skills/settings/HOOKS.md +27 -2
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +26 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +25 -0
- package/dist/managers/aiManager.js +172 -51
- package/dist/managers/backgroundTaskManager.d.ts +6 -0
- package/dist/managers/backgroundTaskManager.js +11 -0
- package/dist/managers/bangManager.d.ts +6 -0
- package/dist/managers/bangManager.js +11 -0
- package/dist/managers/hookManager.d.ts +8 -2
- package/dist/managers/hookManager.js +14 -4
- package/dist/managers/mcpManager.d.ts +18 -4
- package/dist/managers/mcpManager.js +40 -18
- package/dist/managers/permissionManager.d.ts +7 -0
- package/dist/managers/permissionManager.js +102 -142
- package/dist/managers/pluginManager.d.ts +7 -0
- package/dist/managers/pluginManager.js +31 -0
- package/dist/managers/toolManager.js +5 -0
- package/dist/prompts/index.d.ts +12 -1
- package/dist/prompts/index.js +133 -45
- package/dist/services/aiService.d.ts +1 -17
- package/dist/services/aiService.js +3 -85
- package/dist/services/configurationService.d.ts +21 -2
- package/dist/services/configurationService.js +72 -23
- package/dist/services/initializationService.js +14 -4
- package/dist/services/interactionService.js +35 -7
- package/dist/services/remoteSettingsService.d.ts +12 -0
- package/dist/services/remoteSettingsService.js +15 -1
- package/dist/services/session.d.ts +3 -1
- package/dist/services/session.js +12 -4
- package/dist/services/taskManager.d.ts +1 -0
- package/dist/services/taskManager.js +41 -6
- package/dist/tools/bashTool.js +1 -0
- package/dist/tools/editTool.js +24 -10
- package/dist/tools/enterWorktreeTool.js +14 -3
- package/dist/tools/exitWorktreeTool.js +11 -10
- package/dist/tools/grepTool.js +8 -2
- package/dist/tools/types.d.ts +7 -0
- package/dist/tools/writeTool.js +36 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/hooks.d.ts +2 -2
- package/dist/utils/bashParser.d.ts +25 -0
- package/dist/utils/bashParser.js +103 -0
- package/dist/utils/configPaths.d.ts +4 -0
- package/dist/utils/configPaths.js +6 -0
- package/dist/utils/containerSetup.js +1 -1
- package/dist/utils/fileSearch.js +4 -2
- package/dist/utils/openaiClient.js +2 -1
- package/dist/utils/pathEncoder.js +7 -2
- package/dist/utils/worktreeUtils.d.ts +17 -0
- package/dist/utils/worktreeUtils.js +339 -1
- package/package.json +1 -1
- package/src/agent.ts +26 -12
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +238 -66
- package/src/managers/backgroundTaskManager.ts +15 -0
- package/src/managers/bangManager.ts +15 -0
- package/src/managers/hookManager.ts +20 -5
- package/src/managers/mcpManager.ts +60 -18
- package/src/managers/permissionManager.ts +116 -168
- package/src/managers/pluginManager.ts +29 -0
- package/src/managers/toolManager.ts +7 -0
- package/src/prompts/index.ts +144 -37
- package/src/services/aiService.ts +9 -128
- package/src/services/configurationService.ts +84 -23
- package/src/services/initializationService.ts +17 -4
- package/src/services/interactionService.ts +49 -6
- package/src/services/remoteSettingsService.ts +16 -1
- package/src/services/session.ts +18 -4
- package/src/services/taskManager.ts +56 -8
- package/src/tools/bashTool.ts +1 -0
- package/src/tools/editTool.ts +29 -11
- package/src/tools/enterWorktreeTool.ts +19 -2
- package/src/tools/exitWorktreeTool.ts +15 -12
- package/src/tools/grepTool.ts +11 -2
- package/src/tools/types.ts +7 -0
- package/src/tools/writeTool.ts +43 -0
- package/src/types/config.ts +2 -0
- package/src/types/hooks.ts +2 -2
- package/src/utils/bashParser.ts +106 -0
- package/src/utils/configPaths.ts +7 -0
- package/src/utils/containerSetup.ts +3 -1
- package/src/utils/fileSearch.ts +6 -2
- package/src/utils/openaiClient.ts +2 -0
- package/src/utils/pathEncoder.ts +7 -2
- package/src/utils/worktreeUtils.ts +401 -1
|
@@ -22,7 +22,10 @@ import type { ToolManager } from "./toolManager.js";
|
|
|
22
22
|
import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
23
23
|
import type { MessageManager } from "./messageManager.js";
|
|
24
24
|
import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
ChatCompletionMessageFunctionToolCall,
|
|
27
|
+
type ChatCompletionMessageParam,
|
|
28
|
+
} from "openai/resources.js";
|
|
26
29
|
|
|
27
30
|
import type { HookManager } from "./hookManager.js";
|
|
28
31
|
import type { ExtendedHookExecutionContext } from "../types/hooks.js";
|
|
@@ -31,11 +34,16 @@ import type { PermissionManager } from "./permissionManager.js";
|
|
|
31
34
|
import type { SubagentManager } from "./subagentManager.js";
|
|
32
35
|
import type { CronManager } from "./cronManager.js";
|
|
33
36
|
import type { SkillManager } from "./skillManager.js";
|
|
34
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
buildSystemPrompt,
|
|
39
|
+
formatCompactSummary,
|
|
40
|
+
getCompactPrompt,
|
|
41
|
+
} from "../prompts/index.js";
|
|
35
42
|
import {
|
|
36
43
|
buildPlanModeReminder,
|
|
37
44
|
buildPlanModeReEntryReminder,
|
|
38
45
|
buildExitedPlanModeReminder,
|
|
46
|
+
wrapInSystemReminder,
|
|
39
47
|
} from "../prompts/planModeReminders.js";
|
|
40
48
|
import { Container } from "../utils/container.js";
|
|
41
49
|
import type { WorktreeSession } from "../utils/worktreeSession.js";
|
|
@@ -206,9 +214,27 @@ export class AIManager {
|
|
|
206
214
|
return this.container.get<ConfigurationService>("ConfigurationService")!;
|
|
207
215
|
}
|
|
208
216
|
|
|
217
|
+
/**
|
|
218
|
+
* OS env merged with the per-session env snapshot. Falls back to process.env
|
|
219
|
+
* when ConfigurationService is absent or its getMergedEnv is missing (e.g. in
|
|
220
|
+
* unit tests with partial mocks), so hook-context env construction never
|
|
221
|
+
* throws. Use this (not the non-null `configurationService` getter) when
|
|
222
|
+
* building hook context env.
|
|
223
|
+
*/
|
|
224
|
+
private get mergedEnv(): Record<string, string> {
|
|
225
|
+
return (
|
|
226
|
+
this.container
|
|
227
|
+
.get<ConfigurationService>("ConfigurationService")
|
|
228
|
+
?.getMergedEnv?.() ?? (process.env as Record<string, string>)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
209
232
|
// Getter methods for accessing dynamic configuration
|
|
210
233
|
public getGatewayConfig(): GatewayConfig {
|
|
211
|
-
return
|
|
234
|
+
return {
|
|
235
|
+
...this.configurationService.resolveGatewayConfig(),
|
|
236
|
+
sessionId: this.messageManager.getSessionId(),
|
|
237
|
+
};
|
|
212
238
|
}
|
|
213
239
|
|
|
214
240
|
public getModelConfig(): ModelConfig {
|
|
@@ -537,36 +563,47 @@ export class AIManager {
|
|
|
537
563
|
|
|
538
564
|
this.setIsCompacting(true);
|
|
539
565
|
try {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const compactResult = await aiService.compactMessages({
|
|
544
|
-
gatewayConfig: this.getGatewayConfig(),
|
|
545
|
-
modelConfig: this.getModelConfig(),
|
|
546
|
-
messages: recentChatMessages,
|
|
547
|
-
abortSignal: options.abortSignal,
|
|
548
|
-
model: this.getModelConfig().fastModel,
|
|
549
|
-
customInstructions: mergedInstructions,
|
|
566
|
+
const modelConfig = this.getModelConfig();
|
|
567
|
+
const recentChatMessages = convertMessagesForAPI(messagesToCompact, {
|
|
568
|
+
supportsVision: supportsVision(modelConfig.capabilities),
|
|
550
569
|
});
|
|
570
|
+
const compactPrompt = getCompactPrompt(mergedInstructions);
|
|
571
|
+
|
|
572
|
+
// 4. Fork path: fork the conversation with the same system prompt,
|
|
573
|
+
// tools, model, and generation params as the main loop so the forked
|
|
574
|
+
// request prefix matches exactly and the prompt cache is reused.
|
|
575
|
+
const forkResult = await this.runCompactFork(
|
|
576
|
+
recentChatMessages,
|
|
577
|
+
compactPrompt,
|
|
578
|
+
options.abortSignal,
|
|
579
|
+
);
|
|
580
|
+
const summaryContent = forkResult.content;
|
|
581
|
+
const compactTokens = forkResult.usage;
|
|
582
|
+
if (!summaryContent) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
"Compaction failed: the model produced no summary output",
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
const compactModel = modelConfig.model;
|
|
551
588
|
|
|
552
589
|
// 5. Handle usage tracking
|
|
553
590
|
let compactUsage: Usage | undefined;
|
|
554
|
-
if (
|
|
591
|
+
if (compactTokens) {
|
|
555
592
|
compactUsage = {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
total_tokens: compactResult.usage.total_tokens,
|
|
559
|
-
model: this.getModelConfig().fastModel,
|
|
593
|
+
...compactTokens,
|
|
594
|
+
model: compactModel,
|
|
560
595
|
operation_type: "compact",
|
|
561
596
|
};
|
|
562
597
|
}
|
|
563
598
|
|
|
564
|
-
// 6.
|
|
565
|
-
const
|
|
566
|
-
compactResult.content,
|
|
567
|
-
);
|
|
599
|
+
// 6. Strip the <analysis> scratchpad and extract the <summary> body
|
|
600
|
+
const formattedSummary = formatCompactSummary(summaryContent);
|
|
568
601
|
|
|
569
|
-
// 7.
|
|
602
|
+
// 7. Build post-compact context restoration
|
|
603
|
+
const enhancedSummary =
|
|
604
|
+
await this.buildPostCompactContext(formattedSummary);
|
|
605
|
+
|
|
606
|
+
// 8. Execute message reconstruction
|
|
570
607
|
await this.messageManager.compactMessagesAndUpdateSession(
|
|
571
608
|
enhancedSummary,
|
|
572
609
|
compactUsage,
|
|
@@ -591,7 +628,7 @@ export class AIManager {
|
|
|
591
628
|
}
|
|
592
629
|
}
|
|
593
630
|
|
|
594
|
-
//
|
|
631
|
+
// 9. Track usage
|
|
595
632
|
if (compactUsage && this.callbacks?.onUsageAdded) {
|
|
596
633
|
this.callbacks.onUsageAdded(compactUsage);
|
|
597
634
|
}
|
|
@@ -601,14 +638,14 @@ export class AIManager {
|
|
|
601
638
|
// Reset incremental tracing state after compaction
|
|
602
639
|
resetTracingState();
|
|
603
640
|
|
|
604
|
-
//
|
|
641
|
+
// 10. Log OTEL event
|
|
605
642
|
logOTelEvent("compaction", {
|
|
606
643
|
beforeTokens: String(messagesToCompact.length),
|
|
607
644
|
afterTokens: "1",
|
|
608
|
-
model:
|
|
645
|
+
model: compactModel,
|
|
609
646
|
}).catch(() => {});
|
|
610
647
|
|
|
611
|
-
//
|
|
648
|
+
// 11. Run SessionStart hooks (existing behavior)
|
|
612
649
|
if (this.hookManager) {
|
|
613
650
|
try {
|
|
614
651
|
const newSessionId = this.messageManager.getSessionId();
|
|
@@ -638,13 +675,13 @@ export class AIManager {
|
|
|
638
675
|
}
|
|
639
676
|
}
|
|
640
677
|
|
|
641
|
-
//
|
|
678
|
+
// 12. Run PostCompact hooks
|
|
642
679
|
if (this.hookManager) {
|
|
643
680
|
try {
|
|
644
681
|
await this.hookManager.executePostCompactHooks(
|
|
645
682
|
this.messageManager.getSessionId(),
|
|
646
683
|
this.messageManager.getTranscriptPath(),
|
|
647
|
-
|
|
684
|
+
formattedSummary,
|
|
648
685
|
);
|
|
649
686
|
} catch (error) {
|
|
650
687
|
logger?.warn(`PostCompact hooks failed: ${(error as Error).message}`);
|
|
@@ -668,6 +705,158 @@ export class AIManager {
|
|
|
668
705
|
}
|
|
669
706
|
}
|
|
670
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Build the system prompt used by the main agent loop. Extracted so the
|
|
710
|
+
* compaction fork can mirror it exactly — the forked request prefix must
|
|
711
|
+
* match the main conversation's for the prompt cache to be reused.
|
|
712
|
+
*/
|
|
713
|
+
private async buildMainSystemPrompt(
|
|
714
|
+
filteredToolPlugins: ReturnType<ToolManager["getTools"]>,
|
|
715
|
+
) {
|
|
716
|
+
let autoMemoryOptions: { directory: string; content: string } | undefined;
|
|
717
|
+
|
|
718
|
+
if (this.getAutoMemoryEnabled()) {
|
|
719
|
+
const directory = this.memoryService.getAutoMemoryDirectory(
|
|
720
|
+
this.getWorkdir(),
|
|
721
|
+
);
|
|
722
|
+
const content = await this.memoryService.getAutoMemoryContent(
|
|
723
|
+
this.getWorkdir(),
|
|
724
|
+
);
|
|
725
|
+
autoMemoryOptions = { directory, content };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return buildSystemPrompt(this.systemPrompt, filteredToolPlugins, {
|
|
729
|
+
workdir: this.getWorkdir(),
|
|
730
|
+
originalWorkdir: this.getOriginalWorkdir(),
|
|
731
|
+
language: this.getLanguage(),
|
|
732
|
+
isSubagent: !!this.subagentType,
|
|
733
|
+
worktreeSession: this.getWorktreeSession(),
|
|
734
|
+
autoMemory: autoMemoryOptions,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
private resolveFilteredTools() {
|
|
739
|
+
const toolsConfig = this.getFilteredToolsConfig();
|
|
740
|
+
const toolNames = new Set(toolsConfig.map((t) => t.function.name));
|
|
741
|
+
const filteredToolPlugins = this.toolManager
|
|
742
|
+
.getTools()
|
|
743
|
+
.filter((t) => toolNames.has(t.name));
|
|
744
|
+
return { toolsConfig, toolNames, filteredToolPlugins };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Fork-path compaction: run a bounded agent loop over a copy of the
|
|
749
|
+
* conversation using the same system prompt, tools, model, and generation
|
|
750
|
+
* params as the main loop, so the forked request prefix matches exactly
|
|
751
|
+
* and the prompt cache is reused. Tool calls are denied locally (the model
|
|
752
|
+
* is told to summarize, not act) and their rejections are fed back for
|
|
753
|
+
* another turn. Returns undefined content when the model never produces
|
|
754
|
+
* text; the caller treats that as a compaction failure.
|
|
755
|
+
*/
|
|
756
|
+
private async runCompactFork(
|
|
757
|
+
historyMessages: ChatCompletionMessageParam[],
|
|
758
|
+
compactPrompt: string,
|
|
759
|
+
abortSignal?: AbortSignal,
|
|
760
|
+
): Promise<{
|
|
761
|
+
content?: string;
|
|
762
|
+
usage?: {
|
|
763
|
+
prompt_tokens: number;
|
|
764
|
+
completion_tokens: number;
|
|
765
|
+
total_tokens: number;
|
|
766
|
+
};
|
|
767
|
+
}> {
|
|
768
|
+
const MAX_FORK_TURNS = 3;
|
|
769
|
+
const modelConfig = this.getModelConfig();
|
|
770
|
+
const gatewayConfig = this.getGatewayConfig();
|
|
771
|
+
const sessionId = this.messageManager.getSessionId();
|
|
772
|
+
const workdir = this.getWorkdir();
|
|
773
|
+
|
|
774
|
+
const forkMessages: ChatCompletionMessageParam[] = [...historyMessages];
|
|
775
|
+
|
|
776
|
+
// Mirror the main loop's memory injection so the request prefix matches.
|
|
777
|
+
const { prependContent } =
|
|
778
|
+
await this.messageManager.getMemoryForInjection();
|
|
779
|
+
if (prependContent.trim()) {
|
|
780
|
+
forkMessages.unshift({
|
|
781
|
+
role: "user",
|
|
782
|
+
content: wrapInSystemReminder(prependContent),
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
forkMessages.push({ role: "user", content: compactPrompt });
|
|
787
|
+
|
|
788
|
+
const { toolsConfig, filteredToolPlugins } = this.resolveFilteredTools();
|
|
789
|
+
const systemPrompt = await this.buildMainSystemPrompt(filteredToolPlugins);
|
|
790
|
+
|
|
791
|
+
let totalUsage:
|
|
792
|
+
| {
|
|
793
|
+
prompt_tokens: number;
|
|
794
|
+
completion_tokens: number;
|
|
795
|
+
total_tokens: number;
|
|
796
|
+
}
|
|
797
|
+
| undefined;
|
|
798
|
+
let content: string | undefined;
|
|
799
|
+
|
|
800
|
+
for (let turn = 0; turn < MAX_FORK_TURNS; turn++) {
|
|
801
|
+
const result = await aiService.callAgent({
|
|
802
|
+
gatewayConfig,
|
|
803
|
+
modelConfig,
|
|
804
|
+
messages: forkMessages,
|
|
805
|
+
sessionId,
|
|
806
|
+
abortSignal,
|
|
807
|
+
workdir,
|
|
808
|
+
tools: toolsConfig,
|
|
809
|
+
systemPrompt,
|
|
810
|
+
toolChoice: this.toolChoiceOverride,
|
|
811
|
+
// Stream so a slow reasoning model emits first bytes before the
|
|
812
|
+
// gateway's idle timeout fires (non-streaming waits for the full
|
|
813
|
+
// summary, which exceeds the timeout on large contexts).
|
|
814
|
+
stream: true,
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
if (result.usage) {
|
|
818
|
+
totalUsage = {
|
|
819
|
+
prompt_tokens:
|
|
820
|
+
(totalUsage?.prompt_tokens ?? 0) + result.usage.prompt_tokens,
|
|
821
|
+
completion_tokens:
|
|
822
|
+
(totalUsage?.completion_tokens ?? 0) +
|
|
823
|
+
result.usage.completion_tokens,
|
|
824
|
+
total_tokens:
|
|
825
|
+
(totalUsage?.total_tokens ?? 0) + result.usage.total_tokens,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (result.content?.trim()) {
|
|
830
|
+
content = result.content;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (result.tool_calls && result.tool_calls.length > 0) {
|
|
835
|
+
// Deny all tool calls locally and feed the rejections back so the
|
|
836
|
+
// model gets another turn to produce the summary text.
|
|
837
|
+
forkMessages.push({
|
|
838
|
+
role: "assistant",
|
|
839
|
+
content: result.content ?? null,
|
|
840
|
+
tool_calls: result.tool_calls,
|
|
841
|
+
});
|
|
842
|
+
for (const toolCall of result.tool_calls) {
|
|
843
|
+
forkMessages.push({
|
|
844
|
+
role: "tool",
|
|
845
|
+
tool_call_id: toolCall.id,
|
|
846
|
+
content: "Tool use is not allowed during compaction",
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Neither text nor tool calls: retrying the identical request is
|
|
853
|
+
// pointless, bail out and let the caller fail the compaction.
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return { content, usage: totalUsage };
|
|
858
|
+
}
|
|
859
|
+
|
|
671
860
|
/**
|
|
672
861
|
* Build post-compact context restoration content.
|
|
673
862
|
* Restores file reads, working directory, plan mode, skills, and background tasks.
|
|
@@ -833,7 +1022,7 @@ export class AIManager {
|
|
|
833
1022
|
// has superseded us. setIsLoading(true) here also closes the "idle but
|
|
834
1023
|
// previous turn not finished" race window by marking us busy immediately.
|
|
835
1024
|
this.turnGeneration++;
|
|
836
|
-
|
|
1025
|
+
let myGeneration = this.turnGeneration;
|
|
837
1026
|
this.setIsLoading(true);
|
|
838
1027
|
|
|
839
1028
|
outer: while (true) {
|
|
@@ -938,30 +1127,16 @@ export class AIManager {
|
|
|
938
1127
|
|
|
939
1128
|
logger?.debug("modelConfig in sendAIMessage", this.getModelConfig());
|
|
940
1129
|
|
|
941
|
-
const toolsConfig =
|
|
942
|
-
|
|
943
|
-
const filteredToolPlugins = this.toolManager
|
|
944
|
-
.getTools()
|
|
945
|
-
.filter((t) => toolNames.has(t.name));
|
|
946
|
-
|
|
947
|
-
let autoMemoryOptions:
|
|
948
|
-
| { directory: string; content: string }
|
|
949
|
-
| undefined;
|
|
950
|
-
|
|
951
|
-
if (this.getAutoMemoryEnabled()) {
|
|
952
|
-
const directory = this.memoryService.getAutoMemoryDirectory(
|
|
953
|
-
this.getWorkdir(),
|
|
954
|
-
);
|
|
955
|
-
const content = await this.memoryService.getAutoMemoryContent(
|
|
956
|
-
this.getWorkdir(),
|
|
957
|
-
);
|
|
958
|
-
autoMemoryOptions = { directory, content };
|
|
959
|
-
}
|
|
1130
|
+
const { toolsConfig, toolNames, filteredToolPlugins } =
|
|
1131
|
+
this.resolveFilteredTools();
|
|
960
1132
|
|
|
961
1133
|
// Get memory for message-array injection (not system prompt)
|
|
962
1134
|
const { prependContent } =
|
|
963
1135
|
await this.messageManager.getMemoryForInjection();
|
|
964
1136
|
|
|
1137
|
+
const mainSystemPrompt =
|
|
1138
|
+
await this.buildMainSystemPrompt(filteredToolPlugins);
|
|
1139
|
+
|
|
965
1140
|
// Call AI service with streaming callbacks if enabled
|
|
966
1141
|
const callAgentOptions: CallAgentOptions = {
|
|
967
1142
|
gatewayConfig: this.getGatewayConfig(),
|
|
@@ -972,18 +1147,7 @@ export class AIManager {
|
|
|
972
1147
|
workdir: this.getWorkdir(), // Pass working directory
|
|
973
1148
|
tools: toolsConfig, // Pass filtered tool configuration
|
|
974
1149
|
model: model, // Use passed model
|
|
975
|
-
systemPrompt:
|
|
976
|
-
this.systemPrompt,
|
|
977
|
-
filteredToolPlugins,
|
|
978
|
-
{
|
|
979
|
-
workdir: this.getWorkdir(),
|
|
980
|
-
originalWorkdir: this.getOriginalWorkdir(),
|
|
981
|
-
language: this.getLanguage(),
|
|
982
|
-
isSubagent: !!this.subagentType,
|
|
983
|
-
worktreeSession: this.getWorktreeSession(),
|
|
984
|
-
autoMemory: autoMemoryOptions,
|
|
985
|
-
},
|
|
986
|
-
), // Pass custom system prompt
|
|
1150
|
+
systemPrompt: mainSystemPrompt, // Pass custom system prompt
|
|
987
1151
|
maxTokens: maxTokens, // Pass max tokens override
|
|
988
1152
|
toolChoice: this.toolChoiceOverride, // Pass tool_choice override
|
|
989
1153
|
};
|
|
@@ -992,7 +1156,7 @@ export class AIManager {
|
|
|
992
1156
|
if (prependContent.trim()) {
|
|
993
1157
|
callAgentOptions.messages.unshift({
|
|
994
1158
|
role: "user",
|
|
995
|
-
content:
|
|
1159
|
+
content: wrapInSystemReminder(prependContent),
|
|
996
1160
|
});
|
|
997
1161
|
}
|
|
998
1162
|
|
|
@@ -1507,6 +1671,14 @@ export class AIManager {
|
|
|
1507
1671
|
});
|
|
1508
1672
|
}
|
|
1509
1673
|
}
|
|
1674
|
+
// Re-assert loading state before restarting: if this turn was
|
|
1675
|
+
// aborted, abortAIMessage already reset loading to false, and the
|
|
1676
|
+
// restart below continues the conversation — the UI must show
|
|
1677
|
+
// streaming again (cursor, stop button, ESC handling).
|
|
1678
|
+
this.setIsLoading(true);
|
|
1679
|
+
// Adopt the current (possibly abort-bumped) generation so this
|
|
1680
|
+
// continued turn's end-of-turn cleanup is not skipped as superseded.
|
|
1681
|
+
myGeneration = this.turnGeneration;
|
|
1510
1682
|
// Restart outer loop to process the notifications
|
|
1511
1683
|
shouldRestart = true;
|
|
1512
1684
|
turnOffset = 0;
|
|
@@ -1585,7 +1757,7 @@ export class AIManager {
|
|
|
1585
1757
|
lastAssistantMessage: lastAssistantText, // Stop/SubagentStop: last assistant message text
|
|
1586
1758
|
// Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
|
|
1587
1759
|
env: Object.fromEntries(
|
|
1588
|
-
Object.entries(
|
|
1760
|
+
Object.entries(this.mergedEnv).filter((e) => e[1] !== undefined),
|
|
1589
1761
|
) as Record<string, string>, // Include environment variables
|
|
1590
1762
|
};
|
|
1591
1763
|
|
|
@@ -1779,7 +1951,7 @@ export class AIManager {
|
|
|
1779
1951
|
const sessionId = this.messageManager.getSessionId();
|
|
1780
1952
|
const transcriptPath = this.messageManager.getTranscriptPath();
|
|
1781
1953
|
const env = Object.fromEntries(
|
|
1782
|
-
Object.entries(
|
|
1954
|
+
Object.entries(this.mergedEnv).filter((e) => e[1] !== undefined),
|
|
1783
1955
|
) as Record<string, string>;
|
|
1784
1956
|
await this.hookManager.executeCwdChangedHooks(
|
|
1785
1957
|
oldCwd,
|
|
@@ -1874,7 +2046,7 @@ export class AIManager {
|
|
|
1874
2046
|
toolInput,
|
|
1875
2047
|
subagentType: this.subagentType, // Include subagent type in hook context
|
|
1876
2048
|
env: Object.fromEntries(
|
|
1877
|
-
Object.entries(
|
|
2049
|
+
Object.entries(this.mergedEnv).filter((e) => e[1] !== undefined),
|
|
1878
2050
|
) as Record<string, string>, // Include environment variables
|
|
1879
2051
|
};
|
|
1880
2052
|
|
|
@@ -1950,7 +2122,7 @@ export class AIManager {
|
|
|
1950
2122
|
subagentType: this.subagentType, // Include subagent type in hook context
|
|
1951
2123
|
planFilePath: this.permissionManager?.getPlanFilePath(),
|
|
1952
2124
|
env: Object.fromEntries(
|
|
1953
|
-
Object.entries(
|
|
2125
|
+
Object.entries(this.mergedEnv).filter((e) => e[1] !== undefined),
|
|
1954
2126
|
) as Record<string, string>, // Include environment variables
|
|
1955
2127
|
};
|
|
1956
2128
|
|
|
@@ -8,6 +8,7 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
8
8
|
import { Container } from "../utils/container.js";
|
|
9
9
|
import { MessageQueue } from "./messageQueue.js";
|
|
10
10
|
import { resolveShellPath } from "../utils/shellResolver.js";
|
|
11
|
+
import type { ConfigurationService } from "../services/configurationService.js";
|
|
11
12
|
|
|
12
13
|
export interface BackgroundTaskManagerCallbacks {
|
|
13
14
|
onBackgroundTasksChange?: (tasks: BackgroundTask[]) => void;
|
|
@@ -32,6 +33,19 @@ export class BackgroundTaskManager {
|
|
|
32
33
|
this.workdir = options.workdir;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Merged env (OS env overlaid with this session's settings snapshot) for
|
|
38
|
+
* background-task subprocesses, so settings `env` vars reach them without
|
|
39
|
+
* polluting other sessions in one `wave --stdio` process.
|
|
40
|
+
*/
|
|
41
|
+
private get sessionEnv(): Record<string, string> {
|
|
42
|
+
return (
|
|
43
|
+
this.container
|
|
44
|
+
.get<ConfigurationService>("ConfigurationService")
|
|
45
|
+
?.getMergedEnv?.() ?? (process.env as Record<string, string>)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
/**
|
|
36
50
|
* Fire the onBackgroundTasksChange callback so UI consumers refresh.
|
|
37
51
|
* Public so other managers (e.g. WorkflowManager) can trigger a refresh
|
|
@@ -73,6 +87,7 @@ export class BackgroundTaskManager {
|
|
|
73
87
|
cwd: cwd ?? this.workdir,
|
|
74
88
|
env: {
|
|
75
89
|
...process.env,
|
|
90
|
+
...this.sessionEnv,
|
|
76
91
|
},
|
|
77
92
|
});
|
|
78
93
|
|
|
@@ -2,6 +2,7 @@ import { spawn, type ChildProcess } from "child_process";
|
|
|
2
2
|
import type { MessageManager } from "./messageManager.js";
|
|
3
3
|
import { Container } from "../utils/container.js";
|
|
4
4
|
import { resolveShellPath } from "../utils/shellResolver.js";
|
|
5
|
+
import type { ConfigurationService } from "../services/configurationService.js";
|
|
5
6
|
|
|
6
7
|
export interface BangManagerOptions {
|
|
7
8
|
workdir: string;
|
|
@@ -29,6 +30,19 @@ export class BangManager {
|
|
|
29
30
|
return this.container.get<MessageManager>("MessageManager")!;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Merged env (OS env overlaid with this session's settings snapshot) for
|
|
35
|
+
* bang-command subprocesses, so settings `env` vars reach them without
|
|
36
|
+
* polluting other sessions in one `wave --stdio` process.
|
|
37
|
+
*/
|
|
38
|
+
private get sessionEnv(): Record<string, string> {
|
|
39
|
+
return (
|
|
40
|
+
this.container
|
|
41
|
+
.get<ConfigurationService>("ConfigurationService")
|
|
42
|
+
?.getMergedEnv?.() ?? (process.env as Record<string, string>)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
private setCommandRunning(isRunning: boolean): void {
|
|
33
47
|
this.isCommandRunning = isRunning;
|
|
34
48
|
this.onCommandRunningChange?.(isRunning);
|
|
@@ -51,6 +65,7 @@ export class BangManager {
|
|
|
51
65
|
cwd: this.workdir,
|
|
52
66
|
env: {
|
|
53
67
|
...process.env,
|
|
68
|
+
...this.sessionEnv,
|
|
54
69
|
},
|
|
55
70
|
});
|
|
56
71
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type HookExecutionResult,
|
|
14
14
|
type HookValidationResult,
|
|
15
15
|
type SessionEndSource,
|
|
16
|
+
type SessionStartSource,
|
|
16
17
|
HookConfigurationError,
|
|
17
18
|
isValidHookEvent,
|
|
18
19
|
isValidHookEventConfig,
|
|
@@ -26,6 +27,7 @@ import { executeCommand, isCommandSafe } from "../services/hook.js";
|
|
|
26
27
|
import { MessageSource } from "../types/index.js";
|
|
27
28
|
import type { MessageManager } from "./messageManager.js";
|
|
28
29
|
import { Container } from "../utils/container.js";
|
|
30
|
+
import type { ConfigurationService } from "../services/configurationService.js";
|
|
29
31
|
|
|
30
32
|
import { logger } from "../utils/globalLogger.js";
|
|
31
33
|
|
|
@@ -46,6 +48,19 @@ export class HookManager {
|
|
|
46
48
|
this.matcher = matcher;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Merged env for this session (OS env overlaid with the per-session settings
|
|
53
|
+
* snapshot). Hook subprocesses spawn with this env so settings.json `env`
|
|
54
|
+
* vars reach hooks without polluting other sessions in one stdio process.
|
|
55
|
+
*/
|
|
56
|
+
private get sessionEnv(): Record<string, string> {
|
|
57
|
+
return (
|
|
58
|
+
this.container
|
|
59
|
+
.get<ConfigurationService>("ConfigurationService")
|
|
60
|
+
?.getMergedEnv?.() ?? (process.env as Record<string, string>)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
49
64
|
/**
|
|
50
65
|
* Load hook configuration from programmatic source (AgentOptions.hooks)
|
|
51
66
|
*/
|
|
@@ -916,7 +931,7 @@ export class HookManager {
|
|
|
916
931
|
* Collects additionalContext and initialUserMessage from hook stdout.
|
|
917
932
|
*/
|
|
918
933
|
async executeSessionStartHooks(
|
|
919
|
-
source:
|
|
934
|
+
source: SessionStartSource,
|
|
920
935
|
sessionId: string,
|
|
921
936
|
transcriptPath: string,
|
|
922
937
|
agentType?: string,
|
|
@@ -935,7 +950,7 @@ export class HookManager {
|
|
|
935
950
|
source,
|
|
936
951
|
agentType,
|
|
937
952
|
env: Object.fromEntries(
|
|
938
|
-
Object.entries(
|
|
953
|
+
Object.entries(this.sessionEnv).filter((e) => e[1] !== undefined),
|
|
939
954
|
) as Record<string, string>,
|
|
940
955
|
};
|
|
941
956
|
|
|
@@ -989,7 +1004,7 @@ export class HookManager {
|
|
|
989
1004
|
cwd: this.workdir,
|
|
990
1005
|
endSource: source,
|
|
991
1006
|
env: Object.fromEntries(
|
|
992
|
-
Object.entries(
|
|
1007
|
+
Object.entries(this.sessionEnv).filter((e) => e[1] !== undefined),
|
|
993
1008
|
) as Record<string, string>,
|
|
994
1009
|
};
|
|
995
1010
|
|
|
@@ -1024,7 +1039,7 @@ export class HookManager {
|
|
|
1024
1039
|
cwd: this.workdir,
|
|
1025
1040
|
compactInstructions: customInstructions,
|
|
1026
1041
|
env: Object.fromEntries(
|
|
1027
|
-
Object.entries(
|
|
1042
|
+
Object.entries(this.sessionEnv).filter((e) => e[1] !== undefined),
|
|
1028
1043
|
) as Record<string, string>,
|
|
1029
1044
|
};
|
|
1030
1045
|
|
|
@@ -1061,7 +1076,7 @@ export class HookManager {
|
|
|
1061
1076
|
cwd: this.workdir,
|
|
1062
1077
|
compactSummary,
|
|
1063
1078
|
env: Object.fromEntries(
|
|
1064
|
-
Object.entries(
|
|
1079
|
+
Object.entries(this.sessionEnv).filter((e) => e[1] !== undefined),
|
|
1065
1080
|
) as Record<string, string>,
|
|
1066
1081
|
};
|
|
1067
1082
|
|