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
@@ -244,18 +244,26 @@ Usage:
244
244
 
245
245
  const stats = await stat(actualFilePath);
246
246
 
247
- // Deduplication
247
+ // Deduplication: only dedup if the exact same range was read before
248
248
  if (context.readFileState) {
249
249
  const state = context.readFileState.get(actualFilePath);
250
- if (state && state.mtime === stats.mtime.getTime()) {
251
- return {
252
- success: true,
253
- content: `File ${filePath} has not changed since last read.`,
254
- shortResult: "File unchanged",
255
- metadata: {
256
- type: "file_unchanged",
257
- },
258
- };
250
+ // Only dedup entries from a prior Read with explicit offset (not Edit/Write entries)
251
+ if (
252
+ state &&
253
+ state.mtime === stats.mtime.getTime() &&
254
+ state.offset !== undefined
255
+ ) {
256
+ const rangeMatch = state.offset === offset && state.limit === limit;
257
+ if (rangeMatch) {
258
+ return {
259
+ success: true,
260
+ content: `File ${filePath} has not changed since last read. The content from the earlier Read tool_result in this conversation is still current — refer to that instead of re-reading.`,
261
+ shortResult: "File unchanged",
262
+ metadata: {
263
+ type: "file_unchanged",
264
+ },
265
+ };
266
+ }
259
267
  }
260
268
  }
261
269
 
@@ -287,6 +295,8 @@ Usage:
287
295
  context.readFileState.set(actualFilePath, {
288
296
  mtime: stats.mtime.getTime(),
289
297
  hash,
298
+ offset, // undefined for full reads
299
+ limit, // undefined for full reads
290
300
  });
291
301
  }
292
302
 
@@ -31,6 +31,12 @@ export interface ToolPlugin {
31
31
  workdir?: string;
32
32
  isSubagent?: boolean;
33
33
  }) => string;
34
+ /**
35
+ * Whether this tool is safe to run in parallel with other tools.
36
+ * Default (undefined) = true (parallel). Set to false for tools that
37
+ * perform read-modify-write on shared resources (e.g. Edit, Write).
38
+ */
39
+ isConcurrencySafe?: boolean;
34
40
  }
35
41
 
36
42
  export interface ToolResult {
@@ -104,15 +110,21 @@ export interface ToolContext {
104
110
  maxTokens: number;
105
111
  };
106
112
  /** State of files read in the current session for deduplication */
107
- readFileState?: Map<string, { mtime: number; hash: string }>;
113
+ readFileState?: Map<
114
+ string,
115
+ {
116
+ mtime: number;
117
+ hash: string;
118
+ offset?: number; // undefined = full read or Edit/Write entry (never dedup)
119
+ limit?: number;
120
+ }
121
+ >;
108
122
  /** Hook manager instance for executing hooks */
109
123
  hookManager?: import("../managers/hookManager.js").HookManager;
110
124
  /** Callback to notify when the current working directory changes */
111
125
  onCwdChange?: (newCwd: string) => void;
112
126
  /** Original working directory (before any cd changes) for CWD reset */
113
127
  originalWorkdir?: string;
114
- /** Merged environment variables (process.env + agent env) for child processes */
115
- env?: Record<string, string>;
116
128
  /** Workflow manager instance for workflow orchestration */
117
129
  workflowManager?: import("../managers/workflowManager.js").WorkflowManager;
118
130
  }
@@ -1,4 +1,5 @@
1
- import { readFile, writeFile, mkdir } from "fs/promises";
1
+ import { readFile, writeFile, mkdir, stat } from "fs/promises";
2
+ import { createHash } from "crypto";
2
3
  import { dirname } from "path";
3
4
  import { logger } from "../utils/globalLogger.js";
4
5
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
@@ -10,6 +11,7 @@ import { READ_TOOL_NAME } from "../constants/tools.js";
10
11
  */
11
12
  export const writeTool: ToolPlugin = {
12
13
  name: "Write",
14
+ isConcurrencySafe: false,
13
15
  config: {
14
16
  type: "function",
15
17
  function: {
@@ -164,6 +166,18 @@ Usage:
164
166
  };
165
167
  }
166
168
 
169
+ // Update readFileState so subsequent serialized edits pass staleness check
170
+ if (context.readFileState) {
171
+ const newStats = await stat(resolvedPath);
172
+ const hash = createHash("sha256").update(content).digest("hex");
173
+ context.readFileState.set(resolvedPath, {
174
+ mtime: newStats.mtime.getTime(),
175
+ hash,
176
+ offset: undefined,
177
+ limit: undefined,
178
+ });
179
+ }
180
+
167
181
  const shortResult = isExistingFile ? "File overwritten" : "File created";
168
182
 
169
183
  const lines = content.split("\n").length;
@@ -97,8 +97,6 @@ export interface AgentOptions {
97
97
  * File-based hooks (from config.json/.waverc.json) merge on top of these.
98
98
  */
99
99
  hooks?: PartialHookConfiguration;
100
- /** Per-agent environment variables, merged on top of process.env for bash, MCP, and hooks */
101
- env?: Record<string, string>;
102
100
  [key: string]: unknown;
103
101
  }
104
102
 
@@ -19,5 +19,6 @@ export interface ModelConfig {
19
19
  fastModel?: string;
20
20
  maxTokens?: number;
21
21
  permissionMode?: PermissionMode;
22
+ fastModelConfig?: Record<string, unknown>;
22
23
  [key: string]: unknown;
23
24
  }
@@ -10,7 +10,6 @@ import type {
10
10
  ChatCompletionMessageParam,
11
11
  ChatCompletionContentPart,
12
12
  ChatCompletionContentPartText,
13
- ChatCompletionFunctionTool,
14
13
  CompletionUsage,
15
14
  } from "openai/resources";
16
15
  import { logger } from "./globalLogger.js";
@@ -36,16 +35,6 @@ export interface ClaudeChatCompletionContentPartText
36
35
  cache_control?: CacheControl;
37
36
  }
38
37
 
39
- /**
40
- * Extended tool definition with cache control support
41
- */
42
- export interface ClaudeChatCompletionFunctionTool
43
- extends ChatCompletionFunctionTool {
44
- type: "function";
45
- function: ChatCompletionFunctionTool["function"];
46
- cache_control?: CacheControl;
47
- }
48
-
49
38
  /**
50
39
  * Extended prompt_tokens_details with cache_creation_input_tokens
51
40
  * Some models (e.g. Gemini, DeepSeek) return this field inside prompt_tokens_details
@@ -229,68 +218,53 @@ export function addCacheControlToContent(
229
218
  }
230
219
 
231
220
  /**
232
- * Adds cache control to the last tool in tools array
233
- * @param tools - Array of tool definitions
234
- * @returns Tools array with cache control on last tool
221
+ * Counts the total number of content blocks across all messages.
222
+ * Each element in a message's content array counts as one block.
223
+ * String content counts as one block. Null/undefined content counts as zero
224
+ * (e.g. assistant messages with only tool_calls).
225
+ * @param messages - Array of chat messages
226
+ * @returns Total content block count
235
227
  */
236
- export function addCacheControlToLastTool(
237
- tools: ChatCompletionFunctionTool[],
238
- ): ClaudeChatCompletionFunctionTool[] {
239
- // Handle null, undefined, or empty arrays
240
- if (!tools || !Array.isArray(tools) || tools.length === 0) {
241
- return [];
242
- }
243
-
244
- // Validate tools structure
245
- const validTools = tools.filter((tool) => {
246
- if (!tool || typeof tool !== "object") {
247
- logger.warn("Invalid tool structure detected, skipping:", tool);
248
- return false;
249
- }
250
- if (tool.type !== "function" || !tool.function) {
251
- logger.warn(
252
- "Tool is not a function type or missing function property:",
253
- tool,
254
- );
255
- return false;
228
+ export function countContentBlocks(
229
+ messages: ChatCompletionMessageParam[],
230
+ ): number {
231
+ let count = 0;
232
+ for (const message of messages) {
233
+ if (!message || typeof message !== "object") continue;
234
+ const content = message.content;
235
+ if (typeof content === "string") {
236
+ count += 1;
237
+ } else if (Array.isArray(content)) {
238
+ count += content.length;
256
239
  }
257
- return true;
258
- });
259
-
260
- if (validTools.length === 0) {
261
- logger.warn("No valid tools found for cache control");
262
- return [];
263
240
  }
264
-
265
- // Create a copy of the valid tools array
266
- const result = validTools.map((tool) => ({
267
- ...tool,
268
- })) as ClaudeChatCompletionFunctionTool[];
269
-
270
- // Add cache control to the last tool only
271
- const lastIndex = result.length - 1;
272
- result[lastIndex] = {
273
- ...result[lastIndex],
274
- cache_control: { type: "ephemeral" },
275
- };
276
-
277
- return result;
241
+ return count;
278
242
  }
279
243
 
280
244
  /**
281
- * Transforms messages for Claude cache control with hardcoded strategy
245
+ * Transforms messages for explicit cache control.
246
+ *
247
+ * Cache breakpoints (following Claude Code's "last message marker" strategy):
248
+ * 1. System message — always marked (stable prefix)
249
+ * 2. Last message — the last user/assistant message with content is marked.
250
+ * The API scans backward from this marker within 20 blocks. Since each
251
+ * turn adds ~2 blocks (< 20), the previous request's marker position
252
+ * always falls within the scan window, ensuring cache hits.
253
+ *
254
+ * Tools are marked separately via addCacheControlToLastTool (called by aiService).
255
+ *
282
256
  * @param messages - Original OpenAI message array
283
257
  * @param modelName - Model name for cache detection
284
258
  * @returns Messages with cache control markers applied
285
259
  */
286
- export function transformMessagesForClaudeCache(
260
+ export function transformMessagesForExplicitCache(
287
261
  messages: ChatCompletionMessageParam[],
288
262
  modelName: string,
289
263
  ): ChatCompletionMessageParam[] {
290
264
  // Validate inputs
291
265
  if (!messages || !Array.isArray(messages)) {
292
266
  logger.warn(
293
- "Invalid messages array provided to transformMessagesForClaudeCache",
267
+ "Invalid messages array provided to transformMessagesForExplicitCache",
294
268
  );
295
269
  return [];
296
270
  }
@@ -307,60 +281,68 @@ export function transformMessagesForClaudeCache(
307
281
  // Find first system message index
308
282
  const firstSystemIndex = messages.findIndex((m) => m.role === "system");
309
283
 
310
- // Find last user message index
311
- let lastUserIndex = -1;
284
+ // Determine which message indices should receive cache_control markers
285
+ const cacheIndices = new Set<number>();
286
+
287
+ // Marker 1: First system message (always — stable prefix)
288
+ if (firstSystemIndex !== -1) {
289
+ cacheIndices.add(firstSystemIndex);
290
+ }
291
+
292
+ // Marker 2: Last message with content (user or assistant).
293
+ // This marker moves each turn (~2 blocks), but since the move is < 20 blocks,
294
+ // the previous marker position is always within the API's 20-block scan window.
295
+ // This ensures the entire conversation prefix is cached.
312
296
  for (let i = messages.length - 1; i >= 0; i--) {
313
- if (messages[i].role === "user") {
314
- lastUserIndex = i;
297
+ const msg = messages[i];
298
+ if (!msg || typeof msg !== "object") continue;
299
+ if (i === firstSystemIndex) continue;
300
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
301
+
302
+ const content = msg.content;
303
+ const hasContent =
304
+ (typeof content === "string" && content.length > 0) ||
305
+ (Array.isArray(content) && content.length > 0);
306
+
307
+ if (hasContent) {
308
+ cacheIndices.add(i);
315
309
  break;
316
310
  }
317
311
  }
318
312
 
313
+ // Apply cache_control markers to selected messages
319
314
  const result = messages.map((message, index) => {
320
315
  // Validate message structure
321
316
  if (!message || typeof message !== "object" || !message.role) {
322
317
  logger.warn("Invalid message structure at index", index, ":", message);
323
- return message; // Return as-is to avoid breaking the flow
318
+ return message;
324
319
  }
325
320
 
326
- // First system message: always cached (hardcoded)
327
- if (message.role === "system" && index === firstSystemIndex) {
328
- const content =
329
- (message.content as string | ChatCompletionContentPart[]) || "";
330
- // If content is already an array, check if it already has cache control
331
- if (Array.isArray(content)) {
332
- const hasCacheControl = content.some(
333
- (part) =>
334
- part.type === "text" &&
335
- (part as ClaudeChatCompletionContentPartText).cache_control,
336
- );
337
- if (hasCacheControl) {
338
- return message;
339
- }
340
- }
341
-
342
- const transformedContent = addCacheControlToContent(content, true);
343
-
344
- return {
345
- ...message,
346
- content: transformedContent,
347
- } as ChatCompletionMessageParam;
321
+ if (!cacheIndices.has(index)) {
322
+ return message;
348
323
  }
349
324
 
350
- // Last user message: cache recent conversation history
351
- if (message.role === "user" && index === lastUserIndex) {
352
- const content =
353
- (message.content as string | ChatCompletionContentPart[]) || "";
354
- const transformedContent = addCacheControlToContent(content, true);
325
+ const content =
326
+ (message.content as string | ChatCompletionContentPart[]) || "";
355
327
 
356
- return {
357
- ...message,
358
- content: transformedContent,
359
- } as ChatCompletionMessageParam;
328
+ // Idempotency: skip if content already has cache_control (system message)
329
+ if (message.role === "system" && Array.isArray(content)) {
330
+ const hasCacheControl = content.some(
331
+ (part) =>
332
+ part.type === "text" &&
333
+ (part as ClaudeChatCompletionContentPartText).cache_control,
334
+ );
335
+ if (hasCacheControl) {
336
+ return message;
337
+ }
360
338
  }
361
339
 
362
- // Return message unchanged
363
- return message;
340
+ const transformedContent = addCacheControlToContent(content, true);
341
+
342
+ return {
343
+ ...message,
344
+ content: transformedContent,
345
+ } as ChatCompletionMessageParam;
364
346
  });
365
347
 
366
348
  return result;
@@ -29,5 +29,5 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
29
29
  /**
30
30
  * AI related constants
31
31
  */
32
- export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 128000; // Default token limit
32
+ export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 200000; // Default token limit
33
33
  export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 16384; // Default output token limit
@@ -83,12 +83,6 @@ export function setupAgentContainer(
83
83
  container.register("AgentOptions", options);
84
84
  container.register("Workdir", workdir);
85
85
 
86
- const mergedEnv: Record<string, string> = {
87
- ...(process.env as Record<string, string>),
88
- ...(options.env || {}),
89
- };
90
- container.register("MergedEnv", mergedEnv);
91
-
92
86
  if (options.worktreeName) {
93
87
  container.register("WorktreeName", options.worktreeName);
94
88
  container.register("MainRepoRoot", getGitMainRepoRoot(workdir));
@@ -227,7 +221,9 @@ export function setupAgentContainer(
227
221
  toolName: context.toolName,
228
222
  toolInput: context.toolInput,
229
223
  planFilePath: permissionManager.getPlanFilePath(),
230
- env: mergedEnv,
224
+ env: Object.fromEntries(
225
+ Object.entries(process.env).filter((e) => e[1] !== undefined),
226
+ ) as Record<string, string>,
231
227
  });
232
228
 
233
229
  if (results.length > 0) {
@@ -0,0 +1,108 @@
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
+
5
+ export const TASK_REMINDER_CONFIG = {
6
+ TURNS_SINCE_WRITE: 10,
7
+ TURNS_BETWEEN_REMINDERS: 10,
8
+ };
9
+
10
+ const TASK_MANAGEMENT_TOOLS = new Set([
11
+ "TaskCreate",
12
+ "TaskUpdate",
13
+ "TaskList",
14
+ "TaskGet",
15
+ ]);
16
+ const TASK_REMINDER_MARKER = "<!-- task-reminder -->";
17
+
18
+ function isQualifyingAssistantMessage(message: Message): boolean {
19
+ if (message.role !== "assistant") return false;
20
+ if (message.isMeta) return false;
21
+ return message.blocks.some((block) => block.type !== "reasoning");
22
+ }
23
+
24
+ export function getTaskReminderTurnCounts(messages: Message[]): {
25
+ turnsSinceLastTaskManagement: number;
26
+ turnsSinceLastReminder: number;
27
+ } {
28
+ let assistantTurnCount = 0;
29
+ let turnsSinceLastTaskManagement: number | undefined;
30
+ let turnsSinceLastReminder: number | undefined;
31
+
32
+ for (let i = messages.length - 1; i >= 0; i--) {
33
+ const message = messages[i];
34
+
35
+ // Check blocks for task management tools and reminder markers
36
+ for (const block of message.blocks) {
37
+ if (
38
+ turnsSinceLastTaskManagement === undefined &&
39
+ block.type === "tool" &&
40
+ block.name &&
41
+ TASK_MANAGEMENT_TOOLS.has(block.name)
42
+ ) {
43
+ turnsSinceLastTaskManagement = assistantTurnCount;
44
+ }
45
+ if (
46
+ turnsSinceLastReminder === undefined &&
47
+ block.type === "text" &&
48
+ block.content.includes(TASK_REMINDER_MARKER)
49
+ ) {
50
+ turnsSinceLastReminder = assistantTurnCount;
51
+ }
52
+ }
53
+
54
+ if (isQualifyingAssistantMessage(message)) {
55
+ assistantTurnCount++;
56
+ }
57
+ }
58
+
59
+ return {
60
+ turnsSinceLastTaskManagement:
61
+ turnsSinceLastTaskManagement ?? assistantTurnCount,
62
+ turnsSinceLastReminder: turnsSinceLastReminder ?? assistantTurnCount,
63
+ };
64
+ }
65
+
66
+ export function buildTaskReminderText(tasks: Task[]): string {
67
+ let text = `${TASK_REMINDER_MARKER}\n`;
68
+ text +=
69
+ "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";
70
+
71
+ if (tasks.length > 0) {
72
+ text += "\n";
73
+ for (const task of tasks) {
74
+ text += `#${task.id} [${task.status}] ${task.subject}\n`;
75
+ }
76
+ } else {
77
+ text += "The task list is currently empty.\n";
78
+ }
79
+
80
+ return text;
81
+ }
82
+
83
+ export function maybeInjectTaskReminder(
84
+ messages: ChatCompletionMessageParam[],
85
+ turnCounts: {
86
+ turnsSinceLastTaskManagement: number;
87
+ turnsSinceLastReminder: number;
88
+ },
89
+ tasks: Task[],
90
+ ): void {
91
+ if (
92
+ turnCounts.turnsSinceLastTaskManagement <
93
+ TASK_REMINDER_CONFIG.TURNS_SINCE_WRITE
94
+ ) {
95
+ return;
96
+ }
97
+ if (
98
+ turnCounts.turnsSinceLastReminder <
99
+ TASK_REMINDER_CONFIG.TURNS_BETWEEN_REMINDERS
100
+ ) {
101
+ return;
102
+ }
103
+
104
+ messages.push({
105
+ role: "user",
106
+ content: buildTaskReminderText(tasks),
107
+ } as ChatCompletionMessageParam);
108
+ }