wave-agent-sdk 0.17.10 → 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.
@@ -75,6 +75,7 @@ export declare class AIManager {
75
75
  * Called before getMessages() so the stored message is included in the API call.
76
76
  */
77
77
  private maybeAddPlanModeMessage;
78
+ private maybeInjectTaskReminderIntoMessages;
78
79
  setIsLoading(isLoading: boolean): void;
79
80
  abortAIMessage(): void;
80
81
  private generateCompactParams;
@@ -1 +1 @@
1
- {"version":3,"file":"aiManager.d.ts","sourceRoot":"","sources":["../../src/managers/aiManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aiManager.d.ts","sourceRoot":"","sources":["../../src/managers/aiManager.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
3
3
  import { microcompactMessages } from "../utils/microcompact.js";
4
4
  import { parseTaskNotificationXml } from "../utils/notificationXml.js";
5
5
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
6
+ import { getTaskReminderTurnCounts, maybeInjectTaskReminder, TASK_REMINDER_CONFIG, } from "../utils/taskReminder.js";
6
7
  import { existsSync } from "node:fs";
7
8
  import { buildSystemPrompt } from "../prompts/index.js";
8
9
  import { buildPlanModeReminder, buildPlanModeReEntryReminder, buildExitedPlanModeReminder, } from "../prompts/planModeReminders.js";
@@ -165,6 +166,21 @@ export class AIManager {
165
166
  planMgr.consumePlanEntryReminder();
166
167
  }
167
168
  }
169
+ async maybeInjectTaskReminderIntoMessages(messages, toolNames) {
170
+ // Guard: no task tools available
171
+ if (!toolNames.has("TaskUpdate"))
172
+ return;
173
+ const internalMessages = this.messageManager.getMessages();
174
+ const turnCounts = getTaskReminderTurnCounts(internalMessages);
175
+ if (turnCounts.turnsSinceLastTaskManagement <
176
+ TASK_REMINDER_CONFIG.TURNS_SINCE_WRITE ||
177
+ turnCounts.turnsSinceLastReminder <
178
+ TASK_REMINDER_CONFIG.TURNS_BETWEEN_REMINDERS) {
179
+ return;
180
+ }
181
+ const tasks = await this.taskManager.listTasks();
182
+ maybeInjectTaskReminder(messages, turnCounts, tasks);
183
+ }
168
184
  setIsLoading(isLoading) {
169
185
  this.isLoading = isLoading;
170
186
  this.onLoadingChange?.(isLoading);
@@ -593,6 +609,8 @@ export class AIManager {
593
609
  maxTokens: maxTokens, // Pass max tokens override
594
610
  toolChoice: this.toolChoiceOverride, // Pass tool_choice override
595
611
  };
612
+ // Inject task reminder if needed (not persisted, regenerated each turn)
613
+ await this.maybeInjectTaskReminderIntoMessages(callAgentOptions.messages, toolNames);
596
614
  // Add streaming callbacks only if streaming is enabled
597
615
  if (this.stream) {
598
616
  callAgentOptions.onContentUpdate = (content) => {
@@ -44,7 +44,6 @@ export declare class AuthService {
44
44
  */
45
45
  private exchangeCode;
46
46
  private startLocalAuthServer;
47
- private openBrowser;
48
47
  isSSOAuthenticated(): boolean;
49
48
  /**
50
49
  * Check if the current token is expired or within the refresh buffer.
@@ -1 +1 @@
1
- {"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAiB,MAAM,kBAAkB,CAAC;AAQ5E,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAc;IACrC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,qBAAqB,CACxB;IACL,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;IAE1D,MAAM,CAAC,WAAW,IAAI,WAAW;IAOjC;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI/B;;;OAGG;IACH,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IASvE,OAAO,CAAC,gBAAgB;IAUxB,WAAW,IAAI,MAAM;IAKrB,QAAQ,IAAI,UAAU;IAmBtB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAgBlC,SAAS,IAAI,IAAI;IAgBjB,WAAW,IAAI,MAAM,GAAG,SAAS;IAKjC,YAAY,IAAI,MAAM;IAUhB,KAAK,CAAC,OAAO,CAAC,EAAE;QACpB,6DAA6D;QAC7D,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,mGAAmG;QACnG,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,oFAAoF;QACpF,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgCnB;;;OAGG;YACW,YAAY;IAyB1B,OAAO,CAAC,oBAAoB;YAoGd,WAAW;IAmBzB,kBAAkB,IAAI,OAAO;IAW7B;;;OAGG;IACH,cAAc,IAAI,OAAO;IAgBzB;;;OAGG;IACG,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBtD;;;OAGG;YACW,YAAY;IA4D1B;;;OAGG;IACH,yEAAyE;IACzE,6BAA6B,IAAI,OAAO;IAwBxC,WAAW,IAAI,QAAQ,GAAG,SAAS;CAIpC;AAED,eAAO,MAAM,WAAW,aAA4B,CAAC;AAErD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,KAAK,GAAG,OAAO,KAAK,CA+C3E;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAsC/C;AAED,4DAA4D;AAC5D,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD"}
1
+ {"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAiB,MAAM,kBAAkB,CAAC;AAM5E,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAc;IACrC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,qBAAqB,CACxB;IACL,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;IAE1D,MAAM,CAAC,WAAW,IAAI,WAAW;IAOjC;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI/B;;;OAGG;IACH,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IASvE,OAAO,CAAC,gBAAgB;IAUxB,WAAW,IAAI,MAAM;IAKrB,QAAQ,IAAI,UAAU;IAmBtB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAgBlC,SAAS,IAAI,IAAI;IAgBjB,WAAW,IAAI,MAAM,GAAG,SAAS;IAKjC,YAAY,IAAI,MAAM;IAUhB,KAAK,CAAC,OAAO,CAAC,EAAE;QACpB,6DAA6D;QAC7D,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,mGAAmG;QACnG,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,oFAAoF;QACpF,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgCnB;;;OAGG;YACW,YAAY;IAyB1B,OAAO,CAAC,oBAAoB;IA6F5B,kBAAkB,IAAI,OAAO;IAW7B;;;OAGG;IACH,cAAc,IAAI,OAAO;IAgBzB;;;OAGG;IACG,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBtD;;;OAGG;YACW,YAAY;IA4D1B;;;OAGG;IACH,yEAAyE;IACzE,6BAA6B,IAAI,OAAO;IAwBxC,WAAW,IAAI,QAAQ,GAAG,SAAS;CAIpC;AAED,eAAO,MAAM,WAAW,aAA4B,CAAC;AAErD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,KAAK,GAAG,OAAO,KAAK,CA+C3E;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAsC/C;AAED,4DAA4D;AAC5D,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD"}
@@ -10,12 +10,9 @@ import * as os from "os";
10
10
  import { randomBytes } from "crypto";
11
11
  import { createServer } from "http";
12
12
  import { URL } from "url";
13
- import { execFile } from "child_process";
14
- import { promisify } from "util";
15
13
  import { logger } from "../utils/globalLogger.js";
16
14
  /** Persistent anonymous ID for telemetry fallback when SSO is not authenticated. */
17
15
  let _anonymousId;
18
- const execFileAsync = promisify(execFile);
19
16
  export class AuthService {
20
17
  constructor() {
21
18
  this.onAuthChangeCallbacks = [];
@@ -209,15 +206,8 @@ export class AuthService {
209
206
  const port = address.port;
210
207
  const callbackUrl = `http://127.0.0.1:${port}`;
211
208
  const authUrl = `${serverUrl}/login?callback_url=${encodeURIComponent(callbackUrl)}`;
212
- // Notify caller of the auth URL
209
+ // Notify caller of the auth URL; caller is responsible for opening browser
213
210
  options?.onAuthUrl?.(authUrl);
214
- // Try to open browser; if it fails, keep server alive for manual visit
215
- try {
216
- await this.openBrowser(authUrl);
217
- }
218
- catch {
219
- // Browser not available — server stays alive
220
- }
221
211
  });
222
212
  // If manual code reading is provided, race between server callback and user input
223
213
  if (options?.readToken) {
@@ -241,24 +231,6 @@ export class AuthService {
241
231
  }, 5 * 60 * 1000);
242
232
  });
243
233
  }
244
- async openBrowser(url) {
245
- const platform = process.platform;
246
- let command;
247
- let args;
248
- if (platform === "darwin") {
249
- command = "open";
250
- args = [url];
251
- }
252
- else if (platform === "win32") {
253
- command = "cmd";
254
- args = ["/c", "start", "", url];
255
- }
256
- else {
257
- command = "xdg-open";
258
- args = [url];
259
- }
260
- await execFileAsync(command, args);
261
- }
262
234
  isSSOAuthenticated() {
263
235
  const config = this.loadAuth();
264
236
  if (!config.SSO_TOKEN)
@@ -18,6 +18,8 @@ export interface FileWatcherConfig {
18
18
  maxRetries: number;
19
19
  fallbackPolling: boolean;
20
20
  ignoreTempFiles: boolean;
21
+ depth: number;
22
+ ignored: string[];
21
23
  }
22
24
  export interface FileWatcherStatus {
23
25
  isActive: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"fileWatcher.d.ts","sourceRoot":"","sources":["../../src/services/fileWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAaD,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,OAAO,CAAC,QAAQ,CAA4C;IAC5D,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,MAAM,CAAC,CAAS;gBAEZ,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAahE;;;OAGG;IACG,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACxC,OAAO,CAAC,IAAI,CAAC;IA+BhB;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9C;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IA0BxD;;;OAGG;IACH,qBAAqB,IAAI,iBAAiB,EAAE;IAM5C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAahB,iBAAiB;IAqD/B,OAAO,CAAC,wBAAwB;IAoChC,OAAO,CAAC,eAAe;CAmCxB"}
1
+ {"version":3,"file":"fileWatcher.d.ts","sourceRoot":"","sources":["../../src/services/fileWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAaD,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,OAAO,CAAC,QAAQ,CAA4C;IAC5D,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,MAAM,CAAC,CAAS;gBAEZ,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAehE;;;OAGG;IACG,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACxC,OAAO,CAAC,IAAI,CAAC;IA+BhB;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9C;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IA0BxD;;;OAGG;IACH,qBAAqB,IAAI,iBAAiB,EAAE;IAM5C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAahB,iBAAiB;IAuD/B,OAAO,CAAC,wBAAwB;IA4ChC,OAAO,CAAC,eAAe;CAmCxB"}
@@ -18,6 +18,8 @@ export class FileWatcherService extends EventEmitter {
18
18
  maxRetries: 3,
19
19
  fallbackPolling: false,
20
20
  ignoreTempFiles: true,
21
+ depth: 5,
22
+ ignored: ["**/node_modules/**", "**/.git/**", "**/*.swp", "**/*~"],
21
23
  ...config,
22
24
  };
23
25
  }
@@ -127,6 +129,8 @@ export class FileWatcherService extends EventEmitter {
127
129
  this.globalWatcher = chokidar.watch([], {
128
130
  persistent: true,
129
131
  ignoreInitial: true,
132
+ depth: entry.config.depth,
133
+ ignored: entry.config.ignored,
130
134
  awaitWriteFinish: {
131
135
  stabilityThreshold: entry.config.stabilityThreshold,
132
136
  pollInterval: entry.config.pollInterval,
@@ -185,7 +189,14 @@ export class FileWatcherService extends EventEmitter {
185
189
  });
186
190
  this.globalWatcher.on("error", (err) => {
187
191
  const error = err instanceof Error ? err : new Error(String(err));
188
- this.logger?.error(`FileWatcher: File watcher error: ${error.message}`);
192
+ const isEMFILE = error.code === "EMFILE";
193
+ if (isEMFILE) {
194
+ this.logger?.warn("FileWatcher: Too many open files (EMFILE). " +
195
+ "Consider increasing the limit: `ulimit -n 10240` (macOS) or `ulimit -n 65536` (Linux).");
196
+ }
197
+ else {
198
+ this.logger?.error(`FileWatcher: File watcher error: ${error.message}`);
199
+ }
189
200
  this.emit("watcherError", error);
190
201
  });
191
202
  }
@@ -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.10",
3
+ "version": "0.17.11",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -4,6 +4,11 @@ import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
4
4
  import { microcompactMessages } from "../utils/microcompact.js";
5
5
  import { parseTaskNotificationXml } from "../utils/notificationXml.js";
6
6
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
7
+ import {
8
+ getTaskReminderTurnCounts,
9
+ maybeInjectTaskReminder,
10
+ TASK_REMINDER_CONFIG,
11
+ } from "../utils/taskReminder.js";
7
12
  import { existsSync } from "node:fs";
8
13
  import type {
9
14
  GatewayConfig,
@@ -17,6 +22,7 @@ import type { ToolContext, ToolResult } from "../tools/types.js";
17
22
  import type { MessageManager } from "./messageManager.js";
18
23
  import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
19
24
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
25
+ import type { ChatCompletionMessageParam } from "openai/resources.js";
20
26
  import type { HookManager } from "./hookManager.js";
21
27
  import type { ExtendedHookExecutionContext } from "../types/hooks.js";
22
28
  import type { PermissionManager } from "./permissionManager.js";
@@ -292,6 +298,29 @@ export class AIManager {
292
298
  }
293
299
  }
294
300
 
301
+ private async maybeInjectTaskReminderIntoMessages(
302
+ messages: ChatCompletionMessageParam[],
303
+ toolNames: Set<string>,
304
+ ): Promise<void> {
305
+ // Guard: no task tools available
306
+ if (!toolNames.has("TaskUpdate")) return;
307
+
308
+ const internalMessages = this.messageManager.getMessages();
309
+ const turnCounts = getTaskReminderTurnCounts(internalMessages);
310
+
311
+ if (
312
+ turnCounts.turnsSinceLastTaskManagement <
313
+ TASK_REMINDER_CONFIG.TURNS_SINCE_WRITE ||
314
+ turnCounts.turnsSinceLastReminder <
315
+ TASK_REMINDER_CONFIG.TURNS_BETWEEN_REMINDERS
316
+ ) {
317
+ return;
318
+ }
319
+
320
+ const tasks = await this.taskManager.listTasks();
321
+ maybeInjectTaskReminder(messages, turnCounts, tasks);
322
+ }
323
+
295
324
  public setIsLoading(isLoading: boolean): void {
296
325
  this.isLoading = isLoading;
297
326
  this.onLoadingChange?.(isLoading);
@@ -852,6 +881,12 @@ export class AIManager {
852
881
  toolChoice: this.toolChoiceOverride, // Pass tool_choice override
853
882
  };
854
883
 
884
+ // Inject task reminder if needed (not persisted, regenerated each turn)
885
+ await this.maybeInjectTaskReminderIntoMessages(
886
+ callAgentOptions.messages,
887
+ toolNames,
888
+ );
889
+
855
890
  // Add streaming callbacks only if streaming is enabled
856
891
  if (this.stream) {
857
892
  callAgentOptions.onContentUpdate = (content: string) => {
@@ -19,16 +19,12 @@ import * as os from "os";
19
19
  import { randomBytes } from "crypto";
20
20
  import { createServer, Server } from "http";
21
21
  import { URL } from "url";
22
- import { execFile } from "child_process";
23
- import { promisify } from "util";
24
22
  import type { AuthConfig, AuthUser, TokenResponse } from "../types/auth.js";
25
23
  import { logger } from "../utils/globalLogger.js";
26
24
 
27
25
  /** Persistent anonymous ID for telemetry fallback when SSO is not authenticated. */
28
26
  let _anonymousId: string | undefined;
29
27
 
30
- const execFileAsync = promisify(execFile);
31
-
32
28
  export class AuthService {
33
29
  private static instance: AuthService;
34
30
  private _serverUrl: string | undefined;
@@ -274,15 +270,8 @@ export class AuthService {
274
270
  const callbackUrl = `http://127.0.0.1:${port}`;
275
271
  const authUrl = `${serverUrl}/login?callback_url=${encodeURIComponent(callbackUrl)}`;
276
272
 
277
- // Notify caller of the auth URL
273
+ // Notify caller of the auth URL; caller is responsible for opening browser
278
274
  options?.onAuthUrl?.(authUrl);
279
-
280
- // Try to open browser; if it fails, keep server alive for manual visit
281
- try {
282
- await this.openBrowser(authUrl);
283
- } catch {
284
- // Browser not available — server stays alive
285
- }
286
275
  });
287
276
 
288
277
  // If manual code reading is provided, race between server callback and user input
@@ -315,25 +304,6 @@ export class AuthService {
315
304
  });
316
305
  }
317
306
 
318
- private async openBrowser(url: string): Promise<void> {
319
- const platform = process.platform;
320
- let command: string;
321
- let args: string[];
322
-
323
- if (platform === "darwin") {
324
- command = "open";
325
- args = [url];
326
- } else if (platform === "win32") {
327
- command = "cmd";
328
- args = ["/c", "start", "", url];
329
- } else {
330
- command = "xdg-open";
331
- args = [url];
332
- }
333
-
334
- await execFileAsync(command, args);
335
- }
336
-
337
307
  isSSOAuthenticated(): boolean {
338
308
  const config = this.loadAuth();
339
309
  if (!config.SSO_TOKEN) return false;
@@ -22,6 +22,8 @@ export interface FileWatcherConfig {
22
22
  maxRetries: number; // Default: 3
23
23
  fallbackPolling: boolean; // Default: false
24
24
  ignoreTempFiles: boolean; // Default: true
25
+ depth: number; // default: 5
26
+ ignored: string[]; // default: ['**/node_modules/**', '**/.git/**', '**/*.swp', '**/*~']
25
27
  }
26
28
 
27
29
  export interface FileWatcherStatus {
@@ -59,6 +61,8 @@ export class FileWatcherService extends EventEmitter {
59
61
  maxRetries: 3,
60
62
  fallbackPolling: false,
61
63
  ignoreTempFiles: true,
64
+ depth: 5,
65
+ ignored: ["**/node_modules/**", "**/.git/**", "**/*.swp", "**/*~"],
62
66
  ...config,
63
67
  };
64
68
  }
@@ -185,6 +189,8 @@ export class FileWatcherService extends EventEmitter {
185
189
  this.globalWatcher = chokidar.watch([], {
186
190
  persistent: true,
187
191
  ignoreInitial: true,
192
+ depth: entry.config.depth,
193
+ ignored: entry.config.ignored,
188
194
  awaitWriteFinish: {
189
195
  stabilityThreshold: entry.config.stabilityThreshold,
190
196
  pollInterval: entry.config.pollInterval,
@@ -262,7 +268,15 @@ export class FileWatcherService extends EventEmitter {
262
268
 
263
269
  this.globalWatcher.on("error", (err: unknown) => {
264
270
  const error = err instanceof Error ? err : new Error(String(err));
265
- this.logger?.error(`FileWatcher: File watcher error: ${error.message}`);
271
+ const isEMFILE = (error as NodeJS.ErrnoException).code === "EMFILE";
272
+ if (isEMFILE) {
273
+ this.logger?.warn(
274
+ "FileWatcher: Too many open files (EMFILE). " +
275
+ "Consider increasing the limit: `ulimit -n 10240` (macOS) or `ulimit -n 65536` (Linux).",
276
+ );
277
+ } else {
278
+ this.logger?.error(`FileWatcher: File watcher error: ${error.message}`);
279
+ }
266
280
  this.emit("watcherError", error);
267
281
  });
268
282
  }
@@ -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
+ }