wave-agent-sdk 0.18.7 → 0.19.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/skills/settings/ENV.md +0 -1
- package/builtin/skills/settings/HOOKS.md +10 -0
- package/builtin/skills/settings/MODELS.md +37 -0
- package/builtin/skills/settings/SKILLS.md +6 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +20 -4
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +7 -2
- package/dist/managers/messageManager.d.ts +3 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +17 -77
- package/dist/managers/skillManager.d.ts +1 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +11 -2
- package/dist/prompts/index.d.ts +3 -2
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +13 -9
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +22 -9
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +6 -0
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +1 -1
- package/dist/services/interactionService.js +1 -1
- package/dist/services/reversionService.d.ts +5 -1
- package/dist/services/reversionService.d.ts.map +1 -1
- package/dist/services/reversionService.js +50 -22
- package/dist/services/session.d.ts +4 -13
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +59 -215
- package/dist/telemetry/sessionTracing.d.ts +14 -4
- package/dist/telemetry/sessionTracing.d.ts.map +1 -1
- package/dist/telemetry/sessionTracing.js +134 -8
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +16 -1
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +1 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +0 -1
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +14 -1
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/utils/atomicWrite.d.ts +15 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +59 -0
- package/dist/utils/cacheControlUtils.d.ts +3 -15
- package/dist/utils/cacheControlUtils.d.ts.map +1 -1
- package/dist/utils/cacheControlUtils.js +4 -35
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +5 -1
- package/dist/utils/convertMessagesForAPI.d.ts +9 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +74 -55
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +2 -1
- package/dist/utils/modelCapabilities.d.ts +22 -0
- package/dist/utils/modelCapabilities.d.ts.map +1 -0
- package/dist/utils/modelCapabilities.js +24 -0
- package/dist/utils/skillParser.d.ts.map +1 -1
- package/dist/utils/skillParser.js +3 -1
- package/package.json +1 -1
- package/src/agent.ts +11 -5
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +22 -2
- package/src/managers/mcpManager.ts +8 -2
- package/src/managers/messageManager.ts +22 -104
- package/src/managers/skillManager.ts +16 -2
- package/src/prompts/index.ts +6 -1
- package/src/services/aiService.ts +12 -5
- package/src/services/configurationService.ts +24 -24
- package/src/services/hook.ts +7 -0
- package/src/services/initializationService.ts +1 -3
- package/src/services/interactionService.ts +1 -1
- package/src/services/reversionService.ts +50 -27
- package/src/services/session.ts +63 -262
- package/src/telemetry/sessionTracing.ts +167 -12
- package/src/tools/bashTool.ts +23 -1
- package/src/types/config.ts +8 -0
- package/src/types/hooks.ts +1 -0
- package/src/types/messaging.ts +0 -1
- package/src/types/skills.ts +1 -0
- package/src/types/telemetry.ts +17 -1
- package/src/utils/atomicWrite.ts +61 -0
- package/src/utils/cacheControlUtils.ts +5 -38
- package/src/utils/containerSetup.ts +5 -1
- package/src/utils/convertMessagesForAPI.ts +87 -57
- package/src/utils/mcpUtils.ts +2 -1
- package/src/utils/modelCapabilities.ts +30 -0
- package/src/utils/skillParser.ts +3 -1
package/src/agent.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { SkillManager } from "./managers/skillManager.js";
|
|
|
41
41
|
import { TaskManager } from "./services/taskManager.js";
|
|
42
42
|
import { btw } from "./services/aiService.js";
|
|
43
43
|
import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
|
|
44
|
+
import { supportsVision } from "./utils/modelCapabilities.js";
|
|
44
45
|
import { parseTaskNotificationXml } from "./utils/notificationXml.js";
|
|
45
46
|
import { InitializationService } from "./services/initializationService.js";
|
|
46
47
|
import { InteractionService } from "./services/interactionService.js";
|
|
@@ -598,17 +599,20 @@ export class Agent {
|
|
|
598
599
|
options,
|
|
599
600
|
);
|
|
600
601
|
|
|
601
|
-
// Initialize OpenTelemetry (
|
|
602
|
+
// Initialize OpenTelemetry (awaited to ensure ALS context is ready before
|
|
603
|
+
// startInteractionSpan is called, preventing trace context fragmentation)
|
|
602
604
|
const telemetryConfig = this.configurationService.resolveTelemetryConfig();
|
|
603
|
-
|
|
605
|
+
try {
|
|
606
|
+
await initializeTelemetry(telemetryConfig);
|
|
607
|
+
} catch (error) {
|
|
604
608
|
this.logger?.warn("Telemetry initialization failed:", error);
|
|
605
|
-
}
|
|
609
|
+
}
|
|
606
610
|
|
|
607
611
|
// Log session_start event
|
|
608
612
|
try {
|
|
609
613
|
const modelConfig = this.getModelConfig();
|
|
610
614
|
if (modelConfig.model) {
|
|
611
|
-
logOTelEvent("session_start", {
|
|
615
|
+
await logOTelEvent("session_start", {
|
|
612
616
|
sessionId: this.messageManager.getSessionId(),
|
|
613
617
|
model: modelConfig.model,
|
|
614
618
|
workdir: this.workdir,
|
|
@@ -992,7 +996,9 @@ export class Agent {
|
|
|
992
996
|
* @returns Promise that resolves to the AI's answer
|
|
993
997
|
*/
|
|
994
998
|
public async askBtw(question: string): Promise<string> {
|
|
995
|
-
const messages = convertMessagesForAPI(this.messageManager.getMessages()
|
|
999
|
+
const messages = convertMessagesForAPI(this.messageManager.getMessages(), {
|
|
1000
|
+
supportsVision: supportsVision(this.getModelConfig().capabilities),
|
|
1001
|
+
});
|
|
996
1002
|
const result = await btw({
|
|
997
1003
|
gatewayConfig: this.getGatewayConfig(),
|
|
998
1004
|
modelConfig: this.getModelConfig(),
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./managers/messageQueue.js";
|
|
|
14
14
|
// Export all utilities
|
|
15
15
|
export * from "./utils/bashParser.js";
|
|
16
16
|
export * from "./utils/convertMessagesForAPI.js";
|
|
17
|
+
export * from "./utils/modelCapabilities.js";
|
|
17
18
|
export * from "./utils/fileSearch.js";
|
|
18
19
|
export * from "./utils/globalLogger.js";
|
|
19
20
|
export * from "./utils/mcpUtils.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CallAgentOptions } from "../services/aiService.js";
|
|
2
2
|
import * as aiService from "../services/aiService.js";
|
|
3
3
|
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
4
|
+
import { supportsVision } from "../utils/modelCapabilities.js";
|
|
4
5
|
import { parseTaskNotificationXml } from "../utils/notificationXml.js";
|
|
5
6
|
import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
|
|
6
7
|
import { estimateTokens } from "../utils/tokenEstimate.js";
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
endInteractionSpan,
|
|
46
47
|
startLLMRequestSpan,
|
|
47
48
|
endLLMRequestSpan,
|
|
49
|
+
resetTracingState,
|
|
48
50
|
} from "../telemetry/sessionTracing.js";
|
|
49
51
|
import { logOTelEvent } from "../telemetry/events.js";
|
|
50
52
|
|
|
@@ -492,7 +494,7 @@ export class AIManager {
|
|
|
492
494
|
);
|
|
493
495
|
|
|
494
496
|
// 7. Execute message reconstruction
|
|
495
|
-
this.messageManager.compactMessagesAndUpdateSession(
|
|
497
|
+
await this.messageManager.compactMessagesAndUpdateSession(
|
|
496
498
|
enhancedSummary,
|
|
497
499
|
compactUsage,
|
|
498
500
|
);
|
|
@@ -523,6 +525,9 @@ export class AIManager {
|
|
|
523
525
|
|
|
524
526
|
this.consecutiveCompactionFailures = 0;
|
|
525
527
|
|
|
528
|
+
// Reset incremental tracing state after compaction
|
|
529
|
+
resetTracingState();
|
|
530
|
+
|
|
526
531
|
// 9. Log OTEL event
|
|
527
532
|
logOTelEvent("compaction", {
|
|
528
533
|
beforeTokens: String(messagesToCompact.length),
|
|
@@ -838,7 +843,10 @@ export class AIManager {
|
|
|
838
843
|
|
|
839
844
|
// Get recent message history
|
|
840
845
|
const rawMessages = this.messageManager.getMessages();
|
|
841
|
-
const
|
|
846
|
+
const currentModelConfig = this.getModelConfig();
|
|
847
|
+
const recentMessages = convertMessagesForAPI(rawMessages, {
|
|
848
|
+
supportsVision: supportsVision(currentModelConfig.capabilities),
|
|
849
|
+
});
|
|
842
850
|
|
|
843
851
|
// Track if assistant message has been created
|
|
844
852
|
let assistantMessageCreated = false;
|
|
@@ -953,6 +961,17 @@ export class AIManager {
|
|
|
953
961
|
|
|
954
962
|
llmSpan = startLLMRequestSpan(
|
|
955
963
|
model || this.getModelConfig().model || "",
|
|
964
|
+
{
|
|
965
|
+
context: "interaction",
|
|
966
|
+
systemPrompt:
|
|
967
|
+
typeof callAgentOptions.systemPrompt === "string"
|
|
968
|
+
? callAgentOptions.systemPrompt
|
|
969
|
+
: undefined,
|
|
970
|
+
inputMessages: callAgentOptions.messages,
|
|
971
|
+
toolsSchema: callAgentOptions.tools
|
|
972
|
+
? JSON.stringify(callAgentOptions.tools)
|
|
973
|
+
: undefined,
|
|
974
|
+
},
|
|
956
975
|
);
|
|
957
976
|
|
|
958
977
|
const result = await aiService.callAgent(callAgentOptions);
|
|
@@ -966,6 +985,7 @@ export class AIManager {
|
|
|
966
985
|
outputTokens: result.usage?.completion_tokens,
|
|
967
986
|
cacheReadTokens: result.usage?.cache_read_input_tokens,
|
|
968
987
|
cacheCreationTokens: result.usage?.cache_creation_input_tokens,
|
|
988
|
+
modelOutput: result.content,
|
|
969
989
|
});
|
|
970
990
|
|
|
971
991
|
const createdByStreaming = assistantMessageCreated;
|
|
@@ -725,11 +725,17 @@ export class McpManager {
|
|
|
725
725
|
}
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
-
// Get all tools from connected servers
|
|
728
|
+
// Get all tools from connected servers.
|
|
729
|
+
// "reconnecting" servers retain their last-known tool snapshot so that
|
|
730
|
+
// transient SSE disconnects don't churn the tool list (which would bust
|
|
731
|
+
// the prompt cache). Tools are only dropped once the server truly fails
|
|
732
|
+
// (status "error"/"disconnected").
|
|
729
733
|
getAllConnectedTools(): McpTool[] {
|
|
730
734
|
const allTools: McpTool[] = [];
|
|
731
735
|
for (const server of this.servers.values()) {
|
|
732
|
-
|
|
736
|
+
const usable =
|
|
737
|
+
server.status === "connected" || server.status === "reconnecting";
|
|
738
|
+
if (usable && server.tools) {
|
|
733
739
|
allTools.push(...server.tools);
|
|
734
740
|
}
|
|
735
741
|
}
|
|
@@ -94,8 +94,6 @@ export interface MessageManagerOptions {
|
|
|
94
94
|
export class MessageManager {
|
|
95
95
|
// Private state properties
|
|
96
96
|
private sessionId: string;
|
|
97
|
-
private rootSessionId: string;
|
|
98
|
-
private parentSessionId?: string;
|
|
99
97
|
private messages: Message[];
|
|
100
98
|
private latestTotalTokens: number;
|
|
101
99
|
private workdir: string;
|
|
@@ -118,7 +116,6 @@ export class MessageManager {
|
|
|
118
116
|
options: MessageManagerOptions,
|
|
119
117
|
) {
|
|
120
118
|
this.sessionId = generateSessionId();
|
|
121
|
-
this.rootSessionId = this.sessionId;
|
|
122
119
|
this.messages = [];
|
|
123
120
|
this.latestTotalTokens = 0;
|
|
124
121
|
this.workdir = options.workdir;
|
|
@@ -152,11 +149,7 @@ export class MessageManager {
|
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
public getRootSessionId(): string {
|
|
155
|
-
return this.
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
public getParentSessionId(): string | undefined {
|
|
159
|
-
return this.parentSessionId;
|
|
152
|
+
return this.sessionId;
|
|
160
153
|
}
|
|
161
154
|
|
|
162
155
|
public getMessages(): Message[] {
|
|
@@ -358,8 +351,6 @@ export class MessageManager {
|
|
|
358
351
|
unsavedMessages,
|
|
359
352
|
this.workdir,
|
|
360
353
|
this.sessionType,
|
|
361
|
-
this.rootSessionId,
|
|
362
|
-
this.parentSessionId,
|
|
363
354
|
);
|
|
364
355
|
|
|
365
356
|
// Update the saved message count
|
|
@@ -383,7 +374,6 @@ export class MessageManager {
|
|
|
383
374
|
public clearMessages(): void {
|
|
384
375
|
this.setMessages([]);
|
|
385
376
|
const newSessionId = generateSessionId();
|
|
386
|
-
this.rootSessionId = newSessionId;
|
|
387
377
|
this.setSessionId(newSessionId);
|
|
388
378
|
this.setlatestTotalTokens(0);
|
|
389
379
|
this.savedMessageCount = 0; // Reset saved message count
|
|
@@ -399,8 +389,6 @@ export class MessageManager {
|
|
|
399
389
|
// Initialize state from session data
|
|
400
390
|
public initializeFromSession(sessionData: SessionData): void {
|
|
401
391
|
this.setSessionId(sessionData.id);
|
|
402
|
-
this.rootSessionId = sessionData.rootSessionId || sessionData.id;
|
|
403
|
-
this.parentSessionId = sessionData.parentSessionId;
|
|
404
392
|
this.setMessages([...sessionData.messages]);
|
|
405
393
|
this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
|
|
406
394
|
|
|
@@ -563,12 +551,13 @@ export class MessageManager {
|
|
|
563
551
|
}
|
|
564
552
|
|
|
565
553
|
/**
|
|
566
|
-
* Compact messages and update session
|
|
554
|
+
* Compact messages and update session — append compact message to same file (append-only).
|
|
555
|
+
* After compaction, in-memory messages are [compactMessage, ...lastThreeMessages].
|
|
567
556
|
*/
|
|
568
|
-
public compactMessagesAndUpdateSession(
|
|
557
|
+
public async compactMessagesAndUpdateSession(
|
|
569
558
|
compactedContent: string,
|
|
570
559
|
usage?: Usage,
|
|
571
|
-
): void {
|
|
560
|
+
): Promise<void> {
|
|
572
561
|
// Get last 2 API rounds to preserve (structurally safe boundary)
|
|
573
562
|
const lastThreeMessages = getLastApiRounds(this.messages, 2);
|
|
574
563
|
|
|
@@ -580,23 +569,27 @@ export class MessageManager {
|
|
|
580
569
|
{
|
|
581
570
|
type: "compact",
|
|
582
571
|
content: compactedContent,
|
|
583
|
-
sessionId: this.sessionId,
|
|
584
572
|
},
|
|
585
573
|
],
|
|
586
574
|
timestamp: new Date().toISOString(),
|
|
587
575
|
...(usage && { usage }),
|
|
588
576
|
};
|
|
589
577
|
|
|
590
|
-
// Build new message array: keep the compacted message and last
|
|
578
|
+
// Build new message array: keep the compacted message and last messages
|
|
591
579
|
const newMessages: Message[] = [compactMessage, ...lastThreeMessages];
|
|
592
580
|
|
|
593
|
-
//
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
581
|
+
// APPEND to the same session file (append-only, like Claude Code)
|
|
582
|
+
const { appendMessages } = await import("../services/session.js");
|
|
583
|
+
await appendMessages(
|
|
584
|
+
this.sessionId,
|
|
585
|
+
newMessages,
|
|
586
|
+
this.workdir,
|
|
587
|
+
this.sessionType,
|
|
588
|
+
);
|
|
597
589
|
|
|
598
|
-
//
|
|
590
|
+
// Update in-memory state
|
|
599
591
|
this.setMessages(newMessages);
|
|
592
|
+
this.savedMessageCount = newMessages.length;
|
|
600
593
|
|
|
601
594
|
// Clear and rebuild loaded rule IDs from remaining meta messages
|
|
602
595
|
this.clearLoadedRuleIds();
|
|
@@ -911,101 +904,26 @@ export class MessageManager {
|
|
|
911
904
|
index: number,
|
|
912
905
|
reversionManager?: import("./reversionManager.js").ReversionManager,
|
|
913
906
|
): Promise<void> {
|
|
914
|
-
const { messages
|
|
907
|
+
const { messages } = await this.getFullMessageThread();
|
|
915
908
|
|
|
916
909
|
if (index < 0 || index >= messages.length) {
|
|
917
910
|
throw new Error(`Invalid message index: ${index}`);
|
|
918
911
|
}
|
|
919
912
|
|
|
920
|
-
|
|
921
|
-
let targetSessionId = this.sessionId;
|
|
922
|
-
let targetIndexInSession = index;
|
|
923
|
-
|
|
924
|
-
// We need to be careful here because loadFullMessageThread might have removed "compact" blocks
|
|
925
|
-
// Let's re-calculate based on the actual messages returned.
|
|
926
|
-
// Actually, it's easier to just load sessions one by one again or keep track of counts.
|
|
927
|
-
|
|
928
|
-
// For simplicity, let's assume we want to truncate the WHOLE thread.
|
|
929
|
-
// If the index is in a previous session, we need to:
|
|
930
|
-
// 1. Load that session.
|
|
931
|
-
// 2. Truncate it.
|
|
932
|
-
// 3. Make it the current session.
|
|
933
|
-
// 4. Delete/Invalidate subsequent sessions.
|
|
934
|
-
|
|
935
|
-
// To correctly map 'index' to a session, we need to know the message count of each session
|
|
936
|
-
// as they appear in the concatenated 'messages' array.
|
|
937
|
-
|
|
938
|
-
let remainingIndex = index;
|
|
939
|
-
const { loadSessionFromJsonl } = await import("../services/session.js");
|
|
940
|
-
|
|
941
|
-
for (const sid of sessionIds) {
|
|
942
|
-
const sessionData = await loadSessionFromJsonl(sid, this.workdir);
|
|
943
|
-
if (!sessionData) continue;
|
|
944
|
-
|
|
945
|
-
const sessionMessages = sessionData.messages;
|
|
946
|
-
// If this is not the first session in the thread, it might have a compact block at the start
|
|
947
|
-
// that was removed in getFullMessageThread.
|
|
948
|
-
const hasCompactBlock = sessionMessages[0]?.blocks.some(
|
|
949
|
-
(b) => b.type === "compact",
|
|
950
|
-
);
|
|
951
|
-
const effectiveMessages =
|
|
952
|
-
hasCompactBlock && sid !== sessionIds[0]
|
|
953
|
-
? sessionMessages.slice(1)
|
|
954
|
-
: sessionMessages;
|
|
955
|
-
|
|
956
|
-
if (remainingIndex < effectiveMessages.length) {
|
|
957
|
-
targetSessionId = sid;
|
|
958
|
-
targetIndexInSession = hasCompactBlock
|
|
959
|
-
? remainingIndex + 1
|
|
960
|
-
: remainingIndex;
|
|
961
|
-
break;
|
|
962
|
-
}
|
|
963
|
-
remainingIndex -= effectiveMessages.length;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// Load the target session to perform truncation
|
|
967
|
-
const targetSessionData = await loadSessionFromJsonl(
|
|
968
|
-
targetSessionId,
|
|
969
|
-
this.workdir,
|
|
970
|
-
);
|
|
971
|
-
if (!targetSessionData)
|
|
972
|
-
throw new Error(`Target session ${targetSessionId} not found`);
|
|
973
|
-
|
|
974
|
-
// Identify messages to be removed (from the whole thread)
|
|
913
|
+
const newMessages = messages.slice(0, index);
|
|
975
914
|
const messagesToRemove = messages.slice(index);
|
|
976
915
|
const messageIdsToRemove = messagesToRemove
|
|
977
916
|
.map((m) => m.id as string)
|
|
978
917
|
.filter((id) => !!id);
|
|
979
918
|
|
|
980
|
-
// Revert file changes if manager is provided
|
|
981
919
|
if (reversionManager && messageIdsToRemove.length > 0) {
|
|
982
920
|
await reversionManager.revertTo(messageIdsToRemove, messages);
|
|
983
921
|
}
|
|
984
922
|
|
|
985
|
-
//
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
);
|
|
990
|
-
|
|
991
|
-
// Update target session file
|
|
992
|
-
this.sessionId = targetSessionId;
|
|
993
|
-
this.rootSessionId = targetSessionData.rootSessionId || targetSessionId;
|
|
994
|
-
this.parentSessionId = targetSessionData.parentSessionId;
|
|
995
|
-
this.transcriptPath = this.computeTranscriptPath();
|
|
996
|
-
|
|
997
|
-
await this.rewriteSessionFile(newMessagesInSession);
|
|
998
|
-
|
|
999
|
-
// Update in-memory messages to the truncated session messages
|
|
1000
|
-
// We do NOT include ancestor messages here to avoid exceeding context limits.
|
|
1001
|
-
// The 'compact' block at the start of the session (if any) already summarizes them.
|
|
1002
|
-
this.setMessages(newMessagesInSession);
|
|
1003
|
-
|
|
1004
|
-
// Update saved message count
|
|
1005
|
-
this.savedMessageCount = newMessagesInSession.length;
|
|
1006
|
-
|
|
1007
|
-
// Notify session ID change if it changed
|
|
1008
|
-
this.callbacks.onSessionIdChange?.(this.sessionId);
|
|
923
|
+
// Rewrite file with truncated messages
|
|
924
|
+
await this.rewriteSessionFile(newMessages);
|
|
925
|
+
this.setMessages(newMessages);
|
|
926
|
+
this.savedMessageCount = newMessages.length;
|
|
1009
927
|
}
|
|
1010
928
|
|
|
1011
929
|
/**
|
|
@@ -30,6 +30,7 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
30
30
|
export class SkillManager extends EventEmitter {
|
|
31
31
|
private personalSkillsPath: string;
|
|
32
32
|
private personalClaudeSkillsPath: string;
|
|
33
|
+
private personalAgentsSkillsPath: string;
|
|
33
34
|
private scanTimeout: number;
|
|
34
35
|
private workdir: string;
|
|
35
36
|
|
|
@@ -50,6 +51,8 @@ export class SkillManager extends EventEmitter {
|
|
|
50
51
|
options.personalSkillsPath || join(homedir(), ".wave", "skills");
|
|
51
52
|
this.personalClaudeSkillsPath =
|
|
52
53
|
options.personalClaudeSkillsPath || join(homedir(), ".claude", "skills");
|
|
54
|
+
this.personalAgentsSkillsPath =
|
|
55
|
+
options.personalAgentsSkillsPath || join(homedir(), ".agents", "skills");
|
|
53
56
|
this.scanTimeout = options.scanTimeout || 5000;
|
|
54
57
|
this.workdir = options.workdir || process.cwd();
|
|
55
58
|
this.watchEnabled = options.watch ?? false;
|
|
@@ -131,8 +134,10 @@ export class SkillManager extends EventEmitter {
|
|
|
131
134
|
const pathsToWatch = [
|
|
132
135
|
this.personalSkillsPath,
|
|
133
136
|
this.personalClaudeSkillsPath,
|
|
137
|
+
this.personalAgentsSkillsPath,
|
|
134
138
|
join(this.workdir, ".wave", "skills"),
|
|
135
139
|
join(this.workdir, ".claude", "skills"),
|
|
140
|
+
join(this.workdir, ".agents", "skills"),
|
|
136
141
|
];
|
|
137
142
|
|
|
138
143
|
logger?.debug(`Setting up skill watcher for: ${pathsToWatch.join(", ")}`);
|
|
@@ -225,6 +230,11 @@ export class SkillManager extends EventEmitter {
|
|
|
225
230
|
"builtin",
|
|
226
231
|
);
|
|
227
232
|
|
|
233
|
+
const personalAgentsCollection = await this.discoverSkillCollection(
|
|
234
|
+
this.personalAgentsSkillsPath,
|
|
235
|
+
"personal",
|
|
236
|
+
);
|
|
237
|
+
|
|
228
238
|
const personalClaudeCollection = await this.discoverSkillCollection(
|
|
229
239
|
this.personalClaudeSkillsPath,
|
|
230
240
|
"personal",
|
|
@@ -243,12 +253,14 @@ export class SkillManager extends EventEmitter {
|
|
|
243
253
|
return {
|
|
244
254
|
builtinSkills: builtinCollection.skills,
|
|
245
255
|
personalSkills: new Map([
|
|
246
|
-
...
|
|
256
|
+
...personalAgentsCollection.skills, // lowest priority
|
|
257
|
+
...personalClaudeCollection.skills, // overrides .agents
|
|
247
258
|
...personalCollection.skills, // .wave overrides .claude
|
|
248
259
|
]),
|
|
249
260
|
projectSkills: projectCollection.skills,
|
|
250
261
|
errors: [
|
|
251
262
|
...builtinCollection.errors,
|
|
263
|
+
...personalAgentsCollection.errors,
|
|
252
264
|
...personalClaudeCollection.errors,
|
|
253
265
|
...personalCollection.errors,
|
|
254
266
|
...projectCollection.errors,
|
|
@@ -271,9 +283,11 @@ export class SkillManager extends EventEmitter {
|
|
|
271
283
|
};
|
|
272
284
|
|
|
273
285
|
if (type === "project") {
|
|
274
|
-
// Scan .
|
|
286
|
+
// Scan .agents/skills first, then .claude/skills, then .wave/skills (wave overrides)
|
|
287
|
+
const agentsPath = join(basePath, ".agents", "skills");
|
|
275
288
|
const claudePath = join(basePath, ".claude", "skills");
|
|
276
289
|
const wavePath = join(basePath, ".wave", "skills");
|
|
290
|
+
await this.scanSkillPath(agentsPath, collection);
|
|
277
291
|
await this.scanSkillPath(claudePath, collection);
|
|
278
292
|
await this.scanSkillPath(wavePath, collection);
|
|
279
293
|
} else {
|
package/src/prompts/index.ts
CHANGED
|
@@ -19,7 +19,11 @@ import {
|
|
|
19
19
|
GREP_TOOL_NAME,
|
|
20
20
|
} from "../constants/tools.js";
|
|
21
21
|
|
|
22
|
-
export const BASE_SYSTEM_PROMPT = `You are
|
|
22
|
+
export const BASE_SYSTEM_PROMPT = `You are a coding agent developed by NetEase that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
|
|
23
|
+
|
|
24
|
+
export const MODEL_IDENTITY_PROMPT = `# Model identity
|
|
25
|
+
|
|
26
|
+
If asked about your model, model version, underlying architecture, or product name, identify yourself simply as a coding agent developed by NetEase. Do not reveal the specific model ID, model family, model provider, or product name. Do not mention or hint at any of these details.`;
|
|
23
27
|
|
|
24
28
|
export const DOING_TASKS_PROMPT = `# Doing tasks
|
|
25
29
|
- The user will primarily request you to perform software engineering tasks. These may include solving bugs, adding new functionality, refactoring code, explaining code, and more. When given an unclear or generic instruction, consider it in the context of these software engineering tasks and the current working directory. For example, if the user asks you to change "methodName" to snake case, do not reply with just "method_name", instead find the method in the code and modify the code.
|
|
@@ -265,6 +269,7 @@ export function buildSystemPrompt(
|
|
|
265
269
|
|
|
266
270
|
staticText += `\n\n${OUTPUT_EFFICIENCY_PROMPT}`;
|
|
267
271
|
staticText += `\n\n${TONE_AND_STYLE_PROMPT}`;
|
|
272
|
+
staticText += `\n\n${MODEL_IDENTITY_PROMPT}`;
|
|
268
273
|
|
|
269
274
|
const blocks: SystemPromptBlock[] = [{ text: staticText, cacheable: true }];
|
|
270
275
|
|
|
@@ -13,11 +13,11 @@ import type { GatewayConfig, ModelConfig } from "../types/index.js";
|
|
|
13
13
|
import { ConfigurationError, CONFIG_ERRORS } from "../types/index.js";
|
|
14
14
|
import {
|
|
15
15
|
transformMessagesForExplicitCache,
|
|
16
|
-
supportsPromptCaching,
|
|
17
16
|
extendUsageWithCacheMetrics,
|
|
18
17
|
type ClaudeUsage,
|
|
19
18
|
type ClaudeChatCompletionContentPartText,
|
|
20
19
|
} from "../utils/cacheControlUtils.js";
|
|
20
|
+
import { supportsPromptCaching } from "../utils/modelCapabilities.js";
|
|
21
21
|
|
|
22
22
|
import * as os from "os";
|
|
23
23
|
import * as fs from "fs";
|
|
@@ -265,13 +265,12 @@ export async function callAgent(
|
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
// Determine model early (needed for system prompt construction)
|
|
268
|
-
const currentModel = model || modelConfig.model;
|
|
269
268
|
const resolvedMaxTokens = options.maxTokens ?? modelConfig.maxTokens;
|
|
270
269
|
|
|
271
270
|
// Build system message content
|
|
272
271
|
let systemMessage: ChatCompletionMessageParam;
|
|
273
272
|
if (Array.isArray(systemPrompt)) {
|
|
274
|
-
if (supportsPromptCaching(
|
|
273
|
+
if (supportsPromptCaching(modelConfig.capabilities)) {
|
|
275
274
|
// For Claude models, map blocks to content parts with cache_control on cacheable blocks
|
|
276
275
|
const contentParts: ClaudeChatCompletionContentPartText[] =
|
|
277
276
|
systemPrompt.map((block) => {
|
|
@@ -307,10 +306,10 @@ export async function callAgent(
|
|
|
307
306
|
|
|
308
307
|
processedTools = tools;
|
|
309
308
|
|
|
310
|
-
if (supportsPromptCaching(
|
|
309
|
+
if (supportsPromptCaching(modelConfig.capabilities)) {
|
|
311
310
|
openaiMessages = transformMessagesForExplicitCache(
|
|
312
311
|
openaiMessages,
|
|
313
|
-
|
|
312
|
+
modelConfig.capabilities,
|
|
314
313
|
);
|
|
315
314
|
}
|
|
316
315
|
|
|
@@ -320,12 +319,14 @@ export async function callAgent(
|
|
|
320
319
|
fastModel: _fastModel,
|
|
321
320
|
maxTokens: _maxTokens,
|
|
322
321
|
permissionMode: _permissionMode,
|
|
322
|
+
capabilities: _capabilities,
|
|
323
323
|
...extraParams
|
|
324
324
|
} = modelConfig;
|
|
325
325
|
void _model;
|
|
326
326
|
void _fastModel;
|
|
327
327
|
void _maxTokens;
|
|
328
328
|
void _permissionMode;
|
|
329
|
+
void _capabilities;
|
|
329
330
|
|
|
330
331
|
const openaiModelConfig = getModelConfig(model || modelConfig.model, {
|
|
331
332
|
max_tokens: resolvedMaxTokens,
|
|
@@ -866,6 +867,7 @@ export async function compactMessages(
|
|
|
866
867
|
fastModel: _fastModel,
|
|
867
868
|
maxTokens: _maxTokens,
|
|
868
869
|
permissionMode: _permissionMode,
|
|
870
|
+
capabilities: _capabilities,
|
|
869
871
|
fastModelConfig: _fastModelConfig,
|
|
870
872
|
...extraParams
|
|
871
873
|
} = modelConfig;
|
|
@@ -873,6 +875,7 @@ export async function compactMessages(
|
|
|
873
875
|
void _fastModel;
|
|
874
876
|
void _maxTokens;
|
|
875
877
|
void _permissionMode;
|
|
878
|
+
void _capabilities;
|
|
876
879
|
|
|
877
880
|
// When a fast model override is provided, use the fast model's hyperparams
|
|
878
881
|
// (if configured); otherwise fall back to the agent model's extraParams.
|
|
@@ -989,6 +992,7 @@ export async function processWebContent(
|
|
|
989
992
|
fastModel: _fastModel,
|
|
990
993
|
maxTokens: _maxTokens,
|
|
991
994
|
permissionMode: _permissionMode,
|
|
995
|
+
capabilities: _capabilities,
|
|
992
996
|
fastModelConfig: _fastModelConfig,
|
|
993
997
|
...extraParams
|
|
994
998
|
} = modelConfig;
|
|
@@ -996,6 +1000,7 @@ export async function processWebContent(
|
|
|
996
1000
|
void _fastModel;
|
|
997
1001
|
void _maxTokens;
|
|
998
1002
|
void _permissionMode;
|
|
1003
|
+
void _capabilities;
|
|
999
1004
|
|
|
1000
1005
|
// When a fast model override is provided, use the fast model's hyperparams
|
|
1001
1006
|
// (if configured); otherwise fall back to the agent model's extraParams.
|
|
@@ -1106,12 +1111,14 @@ export async function btw(options: BtwOptions): Promise<BtwResult> {
|
|
|
1106
1111
|
fastModel: _fastModel,
|
|
1107
1112
|
maxTokens: _maxTokens,
|
|
1108
1113
|
permissionMode: _permissionMode,
|
|
1114
|
+
capabilities: _capabilities,
|
|
1109
1115
|
...extraParams
|
|
1110
1116
|
} = modelConfig;
|
|
1111
1117
|
void _model;
|
|
1112
1118
|
void _fastModel;
|
|
1113
1119
|
void _maxTokens;
|
|
1114
1120
|
void _permissionMode;
|
|
1121
|
+
void _capabilities;
|
|
1115
1122
|
|
|
1116
1123
|
const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
|
|
1117
1124
|
temperature: 0.1,
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
} from "./remoteSettingsService.js";
|
|
49
49
|
import { createAuthAwareFetch } from "./authService.js";
|
|
50
50
|
import { ensureWaveRuntimeFilesExcluded } from "../utils/gitUtils.js";
|
|
51
|
+
import { atomicWriteFile } from "../utils/atomicWrite.js";
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Default ConfigurationService implementation
|
|
@@ -545,12 +546,14 @@ export class ConfigurationService {
|
|
|
545
546
|
fastModel: _ffm,
|
|
546
547
|
maxTokens: _fmt,
|
|
547
548
|
permissionMode: _fpm,
|
|
549
|
+
capabilities: _fcap,
|
|
548
550
|
...fastHyperparams
|
|
549
551
|
} = fastModelConfigSource;
|
|
550
552
|
void _fm;
|
|
551
553
|
void _ffm;
|
|
552
554
|
void _fmt;
|
|
553
555
|
void _fpm;
|
|
556
|
+
void _fcap;
|
|
554
557
|
baseConfig.fastModelConfig = fastHyperparams;
|
|
555
558
|
}
|
|
556
559
|
|
|
@@ -729,7 +732,7 @@ export class ConfigurationService {
|
|
|
729
732
|
}
|
|
730
733
|
|
|
731
734
|
config.model = model;
|
|
732
|
-
await
|
|
735
|
+
await atomicWriteFile(configPath, JSON.stringify(config, null, 2));
|
|
733
736
|
}
|
|
734
737
|
|
|
735
738
|
/**
|
|
@@ -816,11 +819,7 @@ export class ConfigurationService {
|
|
|
816
819
|
|
|
817
820
|
if (!config.permissions.allow.includes(rule)) {
|
|
818
821
|
config.permissions.allow.push(rule);
|
|
819
|
-
await
|
|
820
|
-
localConfigPath,
|
|
821
|
-
JSON.stringify(config, null, 2),
|
|
822
|
-
"utf-8",
|
|
823
|
-
);
|
|
822
|
+
await atomicWriteFile(localConfigPath, JSON.stringify(config, null, 2));
|
|
824
823
|
}
|
|
825
824
|
}
|
|
826
825
|
|
|
@@ -868,7 +867,7 @@ export class ConfigurationService {
|
|
|
868
867
|
|
|
869
868
|
config.enabledPlugins[pluginId] = enabled;
|
|
870
869
|
|
|
871
|
-
await
|
|
870
|
+
await atomicWriteFile(configPath, JSON.stringify(config, null, 2));
|
|
872
871
|
}
|
|
873
872
|
|
|
874
873
|
/**
|
|
@@ -940,11 +939,7 @@ export class ConfigurationService {
|
|
|
940
939
|
}
|
|
941
940
|
fileConfig.marketplaces[name] = config;
|
|
942
941
|
|
|
943
|
-
await
|
|
944
|
-
configPath,
|
|
945
|
-
JSON.stringify(fileConfig, null, 2),
|
|
946
|
-
"utf-8",
|
|
947
|
-
);
|
|
942
|
+
await atomicWriteFile(configPath, JSON.stringify(fileConfig, null, 2));
|
|
948
943
|
}
|
|
949
944
|
|
|
950
945
|
/**
|
|
@@ -978,11 +973,7 @@ export class ConfigurationService {
|
|
|
978
973
|
|
|
979
974
|
if (fileConfig.marketplaces && name in fileConfig.marketplaces) {
|
|
980
975
|
delete fileConfig.marketplaces[name];
|
|
981
|
-
await
|
|
982
|
-
configPath,
|
|
983
|
-
JSON.stringify(fileConfig, null, 2),
|
|
984
|
-
"utf-8",
|
|
985
|
-
);
|
|
976
|
+
await atomicWriteFile(configPath, JSON.stringify(fileConfig, null, 2));
|
|
986
977
|
}
|
|
987
978
|
} catch {
|
|
988
979
|
// Ignore errors for corrupted or non-existent files
|
|
@@ -1020,11 +1011,7 @@ export class ConfigurationService {
|
|
|
1020
1011
|
|
|
1021
1012
|
if (config.enabledPlugins && pluginId in config.enabledPlugins) {
|
|
1022
1013
|
delete config.enabledPlugins[pluginId];
|
|
1023
|
-
await
|
|
1024
|
-
configPath,
|
|
1025
|
-
JSON.stringify(config, null, 2),
|
|
1026
|
-
"utf-8",
|
|
1027
|
-
);
|
|
1014
|
+
await atomicWriteFile(configPath, JSON.stringify(config, null, 2));
|
|
1028
1015
|
}
|
|
1029
1016
|
} catch {
|
|
1030
1017
|
// Ignore errors for corrupted or non-existent files
|
|
@@ -1171,6 +1158,15 @@ export function loadWaveConfigFromFile(
|
|
|
1171
1158
|
|
|
1172
1159
|
try {
|
|
1173
1160
|
const content = readFileSync(filePath, "utf-8");
|
|
1161
|
+
|
|
1162
|
+
// Tolerate empty/whitespace-only files: a concurrent writer may have the
|
|
1163
|
+
// file truncated mid-write. Returning null lets callers skip this source
|
|
1164
|
+
// instead of crashing (consistent with the "file not found" path).
|
|
1165
|
+
if (content.trim() === "") {
|
|
1166
|
+
logger.warn(`Empty configuration file (likely mid-write): ${filePath}`);
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1174
1170
|
const config = JSON.parse(content) as WaveConfiguration;
|
|
1175
1171
|
|
|
1176
1172
|
return {
|
|
@@ -1189,10 +1185,14 @@ export function loadWaveConfigFromFile(
|
|
|
1189
1185
|
};
|
|
1190
1186
|
} catch (error) {
|
|
1191
1187
|
if (error instanceof SyntaxError) {
|
|
1192
|
-
|
|
1188
|
+
// Tolerate corrupt JSON: a concurrent writer may leave the file in a
|
|
1189
|
+
// partially-written state. Warn and return null so callers skip this
|
|
1190
|
+
// source instead of crashing.
|
|
1191
|
+
logger.warn(`Invalid JSON syntax in ${filePath}: ${error.message}`);
|
|
1192
|
+
return null;
|
|
1193
1193
|
}
|
|
1194
1194
|
|
|
1195
|
-
// Re-throw
|
|
1195
|
+
// Re-throw other errors as-is
|
|
1196
1196
|
throw error;
|
|
1197
1197
|
}
|
|
1198
1198
|
}
|