wave-agent-sdk 0.17.10 → 0.17.12
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/dist/managers/aiManager.d.ts +1 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +18 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +2 -1
- package/dist/services/authService.d.ts +0 -1
- package/dist/services/authService.d.ts.map +1 -1
- package/dist/services/authService.js +1 -29
- package/dist/services/fileWatcher.d.ts +2 -0
- package/dist/services/fileWatcher.d.ts.map +1 -1
- package/dist/services/fileWatcher.js +12 -1
- package/dist/utils/taskReminder.d.ts +17 -0
- package/dist/utils/taskReminder.d.ts.map +1 -0
- package/dist/utils/taskReminder.js +76 -0
- package/package.json +1 -1
- package/src/managers/aiManager.ts +35 -0
- package/src/managers/skillManager.ts +3 -1
- package/src/services/authService.ts +1 -31
- package/src/services/fileWatcher.ts +15 -1
- package/src/utils/taskReminder.ts +108 -0
|
@@ -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":"
|
|
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) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skillManager.d.ts","sourceRoot":"","sources":["../../src/managers/skillManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,KAAK,EAGL,aAAa,EACb,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD;;GAEG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAe1C,OAAO,CAAC,SAAS;IAdnB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,wBAAwB,CAAS;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,YAAY,CAAU;gBAGpB,SAAS,EAAE,SAAS,EAC5B,OAAO,GAAE,mBAAwB;IAYnC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBjC;;OAEG;YACW,aAAa;IAqC3B;;OAEG;YACW,YAAY;IAoC1B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,kBAAkB,IAAI,aAAa,EAAE;IAQrC;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAQzD;;;OAGG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAgBzD;;OAEG;YACW,cAAc;IAqC5B;;OAEG;YACW,uBAAuB;YAwBvB,aAAa;YAiBb,gBAAgB;IAsD9B;;OAEG;YACW,oBAAoB;IA4BlC;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;QAC/C,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,sBAAsB,CAAC;QACjC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IA8BF;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAC5C;QACE,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,KAAK,CAAC;KACd,GACD;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CACzC;IA6BD;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkC3B;;OAEG;YACW,yBAAyB;
|
|
1
|
+
{"version":3,"file":"skillManager.d.ts","sourceRoot":"","sources":["../../src/managers/skillManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,KAAK,EAGL,aAAa,EACb,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD;;GAEG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAe1C,OAAO,CAAC,SAAS;IAdnB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,wBAAwB,CAAS;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,YAAY,CAAU;gBAGpB,SAAS,EAAE,SAAS,EAC5B,OAAO,GAAE,mBAAwB;IAYnC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBjC;;OAEG;YACW,aAAa;IAqC3B;;OAEG;YACW,YAAY;IAoC1B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,kBAAkB,IAAI,aAAa,EAAE;IAQrC;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAQzD;;;OAGG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAgBzD;;OAEG;YACW,cAAc;IAqC5B;;OAEG;YACW,uBAAuB;YAwBvB,aAAa;YAiBb,gBAAgB;IAsD9B;;OAEG;YACW,oBAAoB;IA4BlC;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;QAC/C,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,sBAAsB,CAAC;QACjC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IA8BF;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAC5C;QACE,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,KAAK,CAAC;KACd,GACD;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CACzC;IA6BD;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkC3B;;OAEG;YACW,yBAAyB;IAWvC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI;CA8BhE"}
|
|
@@ -376,7 +376,8 @@ export class SkillManager extends EventEmitter {
|
|
|
376
376
|
async executeBashInSkillContent(content) {
|
|
377
377
|
const { commands } = parseBashCommands(content);
|
|
378
378
|
if (commands.length > 0) {
|
|
379
|
-
const
|
|
379
|
+
const currentWorkdir = this.container.get("Workdir") ?? this.workdir;
|
|
380
|
+
const results = await executeBashCommands(commands, currentWorkdir);
|
|
380
381
|
return replaceBashCommandsWithOutput(content, results);
|
|
381
382
|
}
|
|
382
383
|
return content;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
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)
|
|
@@ -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;
|
|
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
|
-
|
|
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
|
@@ -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) => {
|
|
@@ -503,7 +503,9 @@ export class SkillManager extends EventEmitter {
|
|
|
503
503
|
private async executeBashInSkillContent(content: string): Promise<string> {
|
|
504
504
|
const { commands } = parseBashCommands(content);
|
|
505
505
|
if (commands.length > 0) {
|
|
506
|
-
const
|
|
506
|
+
const currentWorkdir =
|
|
507
|
+
this.container.get<string>("Workdir") ?? this.workdir;
|
|
508
|
+
const results = await executeBashCommands(commands, currentWorkdir);
|
|
507
509
|
return replaceBashCommandsWithOutput(content, results);
|
|
508
510
|
}
|
|
509
511
|
return content;
|
|
@@ -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
|
-
|
|
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
|
+
}
|