wave-agent-sdk 0.17.9 → 0.17.11

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.
Files changed (85) hide show
  1. package/builtin/skills/settings/ENV.md +1 -1
  2. package/dist/managers/aiManager.d.ts +11 -4
  3. package/dist/managers/aiManager.d.ts.map +1 -1
  4. package/dist/managers/aiManager.js +232 -200
  5. package/dist/managers/backgroundTaskManager.js +1 -1
  6. package/dist/managers/bangManager.js +1 -1
  7. package/dist/managers/hookManager.js +2 -4
  8. package/dist/managers/mcpManager.d.ts.map +1 -1
  9. package/dist/managers/mcpManager.js +1 -3
  10. package/dist/managers/messageManager.d.ts.map +1 -1
  11. package/dist/managers/messageManager.js +1 -8
  12. package/dist/managers/toolManager.d.ts +6 -0
  13. package/dist/managers/toolManager.d.ts.map +1 -1
  14. package/dist/managers/toolManager.js +17 -12
  15. package/dist/services/aiService.d.ts.map +1 -1
  16. package/dist/services/aiService.js +20 -12
  17. package/dist/services/authService.d.ts +0 -1
  18. package/dist/services/authService.d.ts.map +1 -1
  19. package/dist/services/authService.js +1 -29
  20. package/dist/services/configurationService.d.ts.map +1 -1
  21. package/dist/services/configurationService.js +13 -0
  22. package/dist/services/fileWatcher.d.ts +2 -0
  23. package/dist/services/fileWatcher.d.ts.map +1 -1
  24. package/dist/services/fileWatcher.js +12 -1
  25. package/dist/services/jsonlHandler.d.ts +1 -1
  26. package/dist/services/jsonlHandler.d.ts.map +1 -1
  27. package/dist/services/jsonlHandler.js +7 -22
  28. package/dist/services/session.d.ts +2 -9
  29. package/dist/services/session.d.ts.map +1 -1
  30. package/dist/services/session.js +16 -46
  31. package/dist/tools/bashTool.d.ts.map +1 -1
  32. package/dist/tools/bashTool.js +3 -1
  33. package/dist/tools/editTool.d.ts.map +1 -1
  34. package/dist/tools/editTool.js +28 -1
  35. package/dist/tools/enterPlanMode.d.ts.map +1 -1
  36. package/dist/tools/enterPlanMode.js +8 -0
  37. package/dist/tools/exitPlanMode.d.ts.map +1 -1
  38. package/dist/tools/exitPlanMode.js +8 -0
  39. package/dist/tools/readTool.d.ts.map +1 -1
  40. package/dist/tools/readTool.js +18 -10
  41. package/dist/tools/types.d.ts +8 -2
  42. package/dist/tools/types.d.ts.map +1 -1
  43. package/dist/tools/writeTool.d.ts.map +1 -1
  44. package/dist/tools/writeTool.js +14 -1
  45. package/dist/types/agent.d.ts +0 -2
  46. package/dist/types/agent.d.ts.map +1 -1
  47. package/dist/types/config.d.ts +1 -0
  48. package/dist/types/config.d.ts.map +1 -1
  49. package/dist/utils/cacheControlUtils.d.ts +20 -15
  50. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  51. package/dist/utils/cacheControlUtils.js +69 -66
  52. package/dist/utils/constants.d.ts +1 -1
  53. package/dist/utils/constants.js +1 -1
  54. package/dist/utils/containerSetup.d.ts.map +1 -1
  55. package/dist/utils/containerSetup.js +1 -6
  56. package/dist/utils/taskReminder.d.ts +17 -0
  57. package/dist/utils/taskReminder.d.ts.map +1 -0
  58. package/dist/utils/taskReminder.js +76 -0
  59. package/package.json +1 -1
  60. package/src/managers/aiManager.ts +326 -262
  61. package/src/managers/backgroundTaskManager.ts +1 -1
  62. package/src/managers/bangManager.ts +1 -1
  63. package/src/managers/hookManager.ts +6 -6
  64. package/src/managers/mcpManager.ts +2 -4
  65. package/src/managers/messageManager.ts +1 -9
  66. package/src/managers/toolManager.ts +18 -12
  67. package/src/services/aiService.ts +23 -12
  68. package/src/services/authService.ts +1 -31
  69. package/src/services/configurationService.ts +21 -0
  70. package/src/services/fileWatcher.ts +15 -1
  71. package/src/services/jsonlHandler.ts +7 -27
  72. package/src/services/session.ts +16 -57
  73. package/src/tools/bashTool.ts +3 -1
  74. package/src/tools/editTool.ts +31 -1
  75. package/src/tools/enterPlanMode.ts +9 -0
  76. package/src/tools/exitPlanMode.ts +10 -0
  77. package/src/tools/readTool.ts +20 -10
  78. package/src/tools/types.ts +15 -3
  79. package/src/tools/writeTool.ts +15 -1
  80. package/src/types/agent.ts +0 -2
  81. package/src/types/config.ts +1 -0
  82. package/src/utils/cacheControlUtils.ts +77 -95
  83. package/src/utils/constants.ts +1 -1
  84. package/src/utils/containerSetup.ts +3 -7
  85. package/src/utils/taskReminder.ts +108 -0
@@ -130,53 +130,48 @@ export function addCacheControlToContent(content, shouldCache) {
130
130
  });
131
131
  }
132
132
  /**
133
- * Adds cache control to the last tool in tools array
134
- * @param tools - Array of tool definitions
135
- * @returns Tools array with cache control on last tool
133
+ * Counts the total number of content blocks across all messages.
134
+ * Each element in a message's content array counts as one block.
135
+ * String content counts as one block. Null/undefined content counts as zero
136
+ * (e.g. assistant messages with only tool_calls).
137
+ * @param messages - Array of chat messages
138
+ * @returns Total content block count
136
139
  */
137
- export function addCacheControlToLastTool(tools) {
138
- // Handle null, undefined, or empty arrays
139
- if (!tools || !Array.isArray(tools) || tools.length === 0) {
140
- return [];
141
- }
142
- // Validate tools structure
143
- const validTools = tools.filter((tool) => {
144
- if (!tool || typeof tool !== "object") {
145
- logger.warn("Invalid tool structure detected, skipping:", tool);
146
- return false;
140
+ export function countContentBlocks(messages) {
141
+ let count = 0;
142
+ for (const message of messages) {
143
+ if (!message || typeof message !== "object")
144
+ continue;
145
+ const content = message.content;
146
+ if (typeof content === "string") {
147
+ count += 1;
147
148
  }
148
- if (tool.type !== "function" || !tool.function) {
149
- logger.warn("Tool is not a function type or missing function property:", tool);
150
- return false;
149
+ else if (Array.isArray(content)) {
150
+ count += content.length;
151
151
  }
152
- return true;
153
- });
154
- if (validTools.length === 0) {
155
- logger.warn("No valid tools found for cache control");
156
- return [];
157
152
  }
158
- // Create a copy of the valid tools array
159
- const result = validTools.map((tool) => ({
160
- ...tool,
161
- }));
162
- // Add cache control to the last tool only
163
- const lastIndex = result.length - 1;
164
- result[lastIndex] = {
165
- ...result[lastIndex],
166
- cache_control: { type: "ephemeral" },
167
- };
168
- return result;
153
+ return count;
169
154
  }
170
155
  /**
171
- * Transforms messages for Claude cache control with hardcoded strategy
156
+ * Transforms messages for explicit cache control.
157
+ *
158
+ * Cache breakpoints (following Claude Code's "last message marker" strategy):
159
+ * 1. System message — always marked (stable prefix)
160
+ * 2. Last message — the last user/assistant message with content is marked.
161
+ * The API scans backward from this marker within 20 blocks. Since each
162
+ * turn adds ~2 blocks (< 20), the previous request's marker position
163
+ * always falls within the scan window, ensuring cache hits.
164
+ *
165
+ * Tools are marked separately via addCacheControlToLastTool (called by aiService).
166
+ *
172
167
  * @param messages - Original OpenAI message array
173
168
  * @param modelName - Model name for cache detection
174
169
  * @returns Messages with cache control markers applied
175
170
  */
176
- export function transformMessagesForClaudeCache(messages, modelName) {
171
+ export function transformMessagesForExplicitCache(messages, modelName) {
177
172
  // Validate inputs
178
173
  if (!messages || !Array.isArray(messages)) {
179
- logger.warn("Invalid messages array provided to transformMessagesForClaudeCache");
174
+ logger.warn("Invalid messages array provided to transformMessagesForExplicitCache");
180
175
  return [];
181
176
  }
182
177
  if (messages.length === 0) {
@@ -188,48 +183,56 @@ export function transformMessagesForClaudeCache(messages, modelName) {
188
183
  }
189
184
  // Find first system message index
190
185
  const firstSystemIndex = messages.findIndex((m) => m.role === "system");
191
- // Find last user message index
192
- let lastUserIndex = -1;
186
+ // Determine which message indices should receive cache_control markers
187
+ const cacheIndices = new Set();
188
+ // Marker 1: First system message (always — stable prefix)
189
+ if (firstSystemIndex !== -1) {
190
+ cacheIndices.add(firstSystemIndex);
191
+ }
192
+ // Marker 2: Last message with content (user or assistant).
193
+ // This marker moves each turn (~2 blocks), but since the move is < 20 blocks,
194
+ // the previous marker position is always within the API's 20-block scan window.
195
+ // This ensures the entire conversation prefix is cached.
193
196
  for (let i = messages.length - 1; i >= 0; i--) {
194
- if (messages[i].role === "user") {
195
- lastUserIndex = i;
197
+ const msg = messages[i];
198
+ if (!msg || typeof msg !== "object")
199
+ continue;
200
+ if (i === firstSystemIndex)
201
+ continue;
202
+ if (msg.role !== "user" && msg.role !== "assistant")
203
+ continue;
204
+ const content = msg.content;
205
+ const hasContent = (typeof content === "string" && content.length > 0) ||
206
+ (Array.isArray(content) && content.length > 0);
207
+ if (hasContent) {
208
+ cacheIndices.add(i);
196
209
  break;
197
210
  }
198
211
  }
212
+ // Apply cache_control markers to selected messages
199
213
  const result = messages.map((message, index) => {
200
214
  // Validate message structure
201
215
  if (!message || typeof message !== "object" || !message.role) {
202
216
  logger.warn("Invalid message structure at index", index, ":", message);
203
- return message; // Return as-is to avoid breaking the flow
217
+ return message;
204
218
  }
205
- // First system message: always cached (hardcoded)
206
- if (message.role === "system" && index === firstSystemIndex) {
207
- const content = message.content || "";
208
- // If content is already an array, check if it already has cache control
209
- if (Array.isArray(content)) {
210
- const hasCacheControl = content.some((part) => part.type === "text" &&
211
- part.cache_control);
212
- if (hasCacheControl) {
213
- return message;
214
- }
215
- }
216
- const transformedContent = addCacheControlToContent(content, true);
217
- return {
218
- ...message,
219
- content: transformedContent,
220
- };
219
+ if (!cacheIndices.has(index)) {
220
+ return message;
221
221
  }
222
- // Last user message: cache recent conversation history
223
- if (message.role === "user" && index === lastUserIndex) {
224
- const content = message.content || "";
225
- const transformedContent = addCacheControlToContent(content, true);
226
- return {
227
- ...message,
228
- content: transformedContent,
229
- };
222
+ const content = message.content || "";
223
+ // Idempotency: skip if content already has cache_control (system message)
224
+ if (message.role === "system" && Array.isArray(content)) {
225
+ const hasCacheControl = content.some((part) => part.type === "text" &&
226
+ part.cache_control);
227
+ if (hasCacheControl) {
228
+ return message;
229
+ }
230
230
  }
231
- // Return message unchanged
232
- return message;
231
+ const transformedContent = addCacheControlToContent(content, true);
232
+ return {
233
+ ...message,
234
+ content: transformedContent,
235
+ };
233
236
  });
234
237
  return result;
235
238
  }
@@ -21,6 +21,6 @@ export declare const USER_MEMORY_FILE: string;
21
21
  /**
22
22
  * AI related constants
23
23
  */
24
- export declare const DEFAULT_WAVE_MAX_INPUT_TOKENS = 128000;
24
+ export declare const DEFAULT_WAVE_MAX_INPUT_TOKENS = 200000;
25
25
  export declare const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 16384;
26
26
  //# sourceMappingURL=constants.d.ts.map
@@ -23,5 +23,5 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
23
23
  /**
24
24
  * AI related constants
25
25
  */
26
- export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 128000; // Default token limit
26
+ export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 200000; // Default token limit
27
27
  export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 16384; // Default output token limit
@@ -1 +1 @@
1
- {"version":3,"file":"containerSetup.d.ts","sourceRoot":"","sources":["../../src/utils/containerSetup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA0B3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAM3E,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EACV,cAAc,EACd,KAAK,EACL,IAAI,EACJ,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAM3B,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAGhB,uBAAuB,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC3D,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACvC,sBAAsB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACvD,wBAAwB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzD,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,0BAA0B,GACvC,SAAS,CA2SX"}
1
+ {"version":3,"file":"containerSetup.d.ts","sourceRoot":"","sources":["../../src/utils/containerSetup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA0B3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAM3E,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EACV,cAAc,EACd,KAAK,EACL,IAAI,EACJ,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAM3B,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAGhB,uBAAuB,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC3D,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACvC,sBAAsB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACvD,wBAAwB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzD,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,0BAA0B,GACvC,SAAS,CAuSX"}
@@ -38,11 +38,6 @@ export function setupAgentContainer(setupOptions) {
38
38
  const container = new Container();
39
39
  container.register("AgentOptions", options);
40
40
  container.register("Workdir", workdir);
41
- const mergedEnv = {
42
- ...process.env,
43
- ...(options.env || {}),
44
- };
45
- container.register("MergedEnv", mergedEnv);
46
41
  if (options.worktreeName) {
47
42
  container.register("WorktreeName", options.worktreeName);
48
43
  container.register("MainRepoRoot", getGitMainRepoRoot(workdir));
@@ -159,7 +154,7 @@ export function setupAgentContainer(setupOptions) {
159
154
  toolName: context.toolName,
160
155
  toolInput: context.toolInput,
161
156
  planFilePath: permissionManager.getPlanFilePath(),
162
- env: mergedEnv,
157
+ env: Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined)),
163
158
  });
164
159
  if (results.length > 0) {
165
160
  const processResult = hookManager.processHookResults("PermissionRequest", results, messageManager);
@@ -0,0 +1,17 @@
1
+ import type { Message } from "../types/messaging.js";
2
+ import type { Task } from "../types/tasks.js";
3
+ import type { ChatCompletionMessageParam } from "openai/resources.js";
4
+ export declare const TASK_REMINDER_CONFIG: {
5
+ TURNS_SINCE_WRITE: number;
6
+ TURNS_BETWEEN_REMINDERS: number;
7
+ };
8
+ export declare function getTaskReminderTurnCounts(messages: Message[]): {
9
+ turnsSinceLastTaskManagement: number;
10
+ turnsSinceLastReminder: number;
11
+ };
12
+ export declare function buildTaskReminderText(tasks: Task[]): string;
13
+ export declare function maybeInjectTaskReminder(messages: ChatCompletionMessageParam[], turnCounts: {
14
+ turnsSinceLastTaskManagement: number;
15
+ turnsSinceLastReminder: number;
16
+ }, tasks: Task[]): void;
17
+ //# sourceMappingURL=taskReminder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taskReminder.d.ts","sourceRoot":"","sources":["../../src/utils/taskReminder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;CAGhC,CAAC;AAgBF,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG;IAC9D,4BAA4B,EAAE,MAAM,CAAC;IACrC,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAqCA;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAe3D;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,0BAA0B,EAAE,EACtC,UAAU,EAAE;IACV,4BAA4B,EAAE,MAAM,CAAC;IACrC,sBAAsB,EAAE,MAAM,CAAC;CAChC,EACD,KAAK,EAAE,IAAI,EAAE,GACZ,IAAI,CAkBN"}
@@ -0,0 +1,76 @@
1
+ export const TASK_REMINDER_CONFIG = {
2
+ TURNS_SINCE_WRITE: 10,
3
+ TURNS_BETWEEN_REMINDERS: 10,
4
+ };
5
+ const TASK_MANAGEMENT_TOOLS = new Set([
6
+ "TaskCreate",
7
+ "TaskUpdate",
8
+ "TaskList",
9
+ "TaskGet",
10
+ ]);
11
+ const TASK_REMINDER_MARKER = "<!-- task-reminder -->";
12
+ function isQualifyingAssistantMessage(message) {
13
+ if (message.role !== "assistant")
14
+ return false;
15
+ if (message.isMeta)
16
+ return false;
17
+ return message.blocks.some((block) => block.type !== "reasoning");
18
+ }
19
+ export function getTaskReminderTurnCounts(messages) {
20
+ let assistantTurnCount = 0;
21
+ let turnsSinceLastTaskManagement;
22
+ let turnsSinceLastReminder;
23
+ for (let i = messages.length - 1; i >= 0; i--) {
24
+ const message = messages[i];
25
+ // Check blocks for task management tools and reminder markers
26
+ for (const block of message.blocks) {
27
+ if (turnsSinceLastTaskManagement === undefined &&
28
+ block.type === "tool" &&
29
+ block.name &&
30
+ TASK_MANAGEMENT_TOOLS.has(block.name)) {
31
+ turnsSinceLastTaskManagement = assistantTurnCount;
32
+ }
33
+ if (turnsSinceLastReminder === undefined &&
34
+ block.type === "text" &&
35
+ block.content.includes(TASK_REMINDER_MARKER)) {
36
+ turnsSinceLastReminder = assistantTurnCount;
37
+ }
38
+ }
39
+ if (isQualifyingAssistantMessage(message)) {
40
+ assistantTurnCount++;
41
+ }
42
+ }
43
+ return {
44
+ turnsSinceLastTaskManagement: turnsSinceLastTaskManagement ?? assistantTurnCount,
45
+ turnsSinceLastReminder: turnsSinceLastReminder ?? assistantTurnCount,
46
+ };
47
+ }
48
+ export function buildTaskReminderText(tasks) {
49
+ let text = `${TASK_REMINDER_MARKER}\n`;
50
+ text +=
51
+ "Here is a gentle reminder about the task list. The user may not have explicitly asked for the following to be done, but you should check the task list to see if any tasks need attention.\n";
52
+ if (tasks.length > 0) {
53
+ text += "\n";
54
+ for (const task of tasks) {
55
+ text += `#${task.id} [${task.status}] ${task.subject}\n`;
56
+ }
57
+ }
58
+ else {
59
+ text += "The task list is currently empty.\n";
60
+ }
61
+ return text;
62
+ }
63
+ export function maybeInjectTaskReminder(messages, turnCounts, tasks) {
64
+ if (turnCounts.turnsSinceLastTaskManagement <
65
+ TASK_REMINDER_CONFIG.TURNS_SINCE_WRITE) {
66
+ return;
67
+ }
68
+ if (turnCounts.turnsSinceLastReminder <
69
+ TASK_REMINDER_CONFIG.TURNS_BETWEEN_REMINDERS) {
70
+ return;
71
+ }
72
+ messages.push({
73
+ role: "user",
74
+ content: buildTaskReminderText(tasks),
75
+ });
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.17.9",
3
+ "version": "0.17.11",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",