@madh-io/alfred-ai 0.7.0 → 0.7.2
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/bundle/index.js +417 -31
- package/package.json +1 -1
package/bundle/index.js
CHANGED
|
@@ -2035,6 +2035,29 @@ For complex tasks, work through multiple steps:
|
|
|
2035
2035
|
- Documents: ${homeDir}/Documents
|
|
2036
2036
|
- Desktop: ${homeDir}/Desktop
|
|
2037
2037
|
- Downloads: ${homeDir}/Downloads`;
|
|
2038
|
+
const serverTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
2039
|
+
const effectiveTimezone = userProfile?.timezone || serverTimezone;
|
|
2040
|
+
const now = /* @__PURE__ */ new Date();
|
|
2041
|
+
const timeStr = now.toLocaleTimeString("en-GB", {
|
|
2042
|
+
timeZone: effectiveTimezone,
|
|
2043
|
+
hour: "2-digit",
|
|
2044
|
+
minute: "2-digit"
|
|
2045
|
+
});
|
|
2046
|
+
const dateStr = now.toLocaleDateString("en-CA", { timeZone: effectiveTimezone });
|
|
2047
|
+
const dayStr = now.toLocaleDateString("en-US", { timeZone: effectiveTimezone, weekday: "long" });
|
|
2048
|
+
prompt += `
|
|
2049
|
+
|
|
2050
|
+
## Current date & time`;
|
|
2051
|
+
prompt += `
|
|
2052
|
+
- Timezone: ${effectiveTimezone}`;
|
|
2053
|
+
prompt += `
|
|
2054
|
+
- Date: ${dateStr} (${dayStr})`;
|
|
2055
|
+
prompt += `
|
|
2056
|
+
- Time: ${timeStr}`;
|
|
2057
|
+
if (userProfile?.timezone && userProfile.timezone !== serverTimezone) {
|
|
2058
|
+
prompt += `
|
|
2059
|
+
- Server timezone: ${serverTimezone}`;
|
|
2060
|
+
}
|
|
2038
2061
|
if (skills && skills.length > 0) {
|
|
2039
2062
|
prompt += "\n\n## Available tools\n";
|
|
2040
2063
|
for (const s of skills) {
|
|
@@ -2049,13 +2072,8 @@ For complex tasks, work through multiple steps:
|
|
|
2049
2072
|
- Name: ${userProfile.displayName}`;
|
|
2050
2073
|
}
|
|
2051
2074
|
if (userProfile.timezone) {
|
|
2052
|
-
const now = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", {
|
|
2053
|
-
timeZone: userProfile.timezone,
|
|
2054
|
-
hour: "2-digit",
|
|
2055
|
-
minute: "2-digit"
|
|
2056
|
-
});
|
|
2057
2075
|
prompt += `
|
|
2058
|
-
- Timezone: ${userProfile.timezone}
|
|
2076
|
+
- Timezone: ${userProfile.timezone}`;
|
|
2059
2077
|
}
|
|
2060
2078
|
if (userProfile.language) {
|
|
2061
2079
|
prompt += `
|
|
@@ -2526,20 +2544,120 @@ var init_skill_registry = __esm({
|
|
|
2526
2544
|
});
|
|
2527
2545
|
|
|
2528
2546
|
// ../skills/dist/skill-sandbox.js
|
|
2529
|
-
var DEFAULT_TIMEOUT_MS, SkillSandbox;
|
|
2547
|
+
var DEFAULT_TIMEOUT_MS, INACTIVITY_THRESHOLD_MS, POLL_INTERVAL_MS, MAX_TOTAL_TIME_MS, SkillSandbox;
|
|
2530
2548
|
var init_skill_sandbox = __esm({
|
|
2531
2549
|
"../skills/dist/skill-sandbox.js"() {
|
|
2532
2550
|
"use strict";
|
|
2533
2551
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
2552
|
+
INACTIVITY_THRESHOLD_MS = 12e4;
|
|
2553
|
+
POLL_INTERVAL_MS = 1e4;
|
|
2554
|
+
MAX_TOTAL_TIME_MS = 20 * 6e4;
|
|
2534
2555
|
SkillSandbox = class {
|
|
2535
2556
|
logger;
|
|
2536
2557
|
constructor(logger) {
|
|
2537
2558
|
this.logger = logger;
|
|
2538
2559
|
}
|
|
2539
|
-
|
|
2560
|
+
/**
|
|
2561
|
+
* Execute a skill with timeout protection.
|
|
2562
|
+
*
|
|
2563
|
+
* If an ActivityTracker is provided, uses an inactivity-based timeout:
|
|
2564
|
+
* the skill keeps running as long as the tracker receives pings.
|
|
2565
|
+
* Only kills the skill when it goes silent for INACTIVITY_THRESHOLD_MS.
|
|
2566
|
+
*
|
|
2567
|
+
* Without a tracker, falls back to a simple hard timeout.
|
|
2568
|
+
*/
|
|
2569
|
+
async execute(skill, input2, context, timeoutMs, tracker) {
|
|
2540
2570
|
timeoutMs = timeoutMs ?? skill.metadata.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2541
2571
|
const { name } = skill.metadata;
|
|
2542
2572
|
this.logger.info({ skill: name, input: input2 }, "Skill execution started");
|
|
2573
|
+
if (tracker) {
|
|
2574
|
+
return this.executeWithTracker(skill, input2, context, name, timeoutMs, tracker);
|
|
2575
|
+
}
|
|
2576
|
+
return this.executeWithHardTimeout(skill, input2, context, name, timeoutMs);
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Activity-aware timeout: polls the tracker and only kills
|
|
2580
|
+
* the skill when it has been inactive for too long.
|
|
2581
|
+
*/
|
|
2582
|
+
async executeWithTracker(skill, input2, context, name, initialTimeoutMs, tracker) {
|
|
2583
|
+
return new Promise((resolve) => {
|
|
2584
|
+
let settled = false;
|
|
2585
|
+
let pollTimer;
|
|
2586
|
+
let safetyTimer;
|
|
2587
|
+
let initialTimer;
|
|
2588
|
+
const cleanup = () => {
|
|
2589
|
+
if (pollTimer)
|
|
2590
|
+
clearInterval(pollTimer);
|
|
2591
|
+
if (safetyTimer)
|
|
2592
|
+
clearTimeout(safetyTimer);
|
|
2593
|
+
if (initialTimer)
|
|
2594
|
+
clearTimeout(initialTimer);
|
|
2595
|
+
};
|
|
2596
|
+
const finish = (result) => {
|
|
2597
|
+
if (settled)
|
|
2598
|
+
return;
|
|
2599
|
+
settled = true;
|
|
2600
|
+
cleanup();
|
|
2601
|
+
resolve(result);
|
|
2602
|
+
};
|
|
2603
|
+
skill.execute(input2, context).then((result) => {
|
|
2604
|
+
this.logger.info({ skill: name, success: result.success }, "Skill execution completed");
|
|
2605
|
+
finish(result);
|
|
2606
|
+
}, (error) => {
|
|
2607
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2608
|
+
this.logger.error({ skill: name, error: message }, "Skill execution failed");
|
|
2609
|
+
finish({ success: false, error: message });
|
|
2610
|
+
});
|
|
2611
|
+
initialTimer = setTimeout(() => {
|
|
2612
|
+
if (settled)
|
|
2613
|
+
return;
|
|
2614
|
+
const idleMs = tracker.getIdleMs();
|
|
2615
|
+
if (idleMs >= INACTIVITY_THRESHOLD_MS) {
|
|
2616
|
+
const snapshot2 = tracker.getSnapshot();
|
|
2617
|
+
this.logger.warn({ skill: name, idleMs, state: snapshot2.state, iteration: snapshot2.iteration }, "Agent inactive after initial timeout \u2014 aborting");
|
|
2618
|
+
finish({
|
|
2619
|
+
success: false,
|
|
2620
|
+
error: `Skill "${name}" timed out \u2014 inactive for ${Math.round(idleMs / 1e3)}s (last state: ${snapshot2.state})`
|
|
2621
|
+
});
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
const snapshot = tracker.getSnapshot();
|
|
2625
|
+
this.logger.info({ skill: name, idleMs, state: snapshot.state, iteration: snapshot.iteration, totalMs: snapshot.totalElapsedMs }, "Initial timeout reached but agent is active \u2014 extending");
|
|
2626
|
+
pollTimer = setInterval(() => {
|
|
2627
|
+
if (settled) {
|
|
2628
|
+
cleanup();
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
const currentIdleMs = tracker.getIdleMs();
|
|
2632
|
+
const snap = tracker.getSnapshot();
|
|
2633
|
+
if (currentIdleMs >= INACTIVITY_THRESHOLD_MS) {
|
|
2634
|
+
this.logger.warn({ skill: name, idleMs: currentIdleMs, state: snap.state, iteration: snap.iteration, totalMs: snap.totalElapsedMs }, "Agent went inactive \u2014 aborting");
|
|
2635
|
+
finish({
|
|
2636
|
+
success: false,
|
|
2637
|
+
error: `Skill "${name}" killed \u2014 inactive for ${Math.round(currentIdleMs / 1e3)}s (last state: ${snap.state})`
|
|
2638
|
+
});
|
|
2639
|
+
} else {
|
|
2640
|
+
this.logger.debug({ skill: name, idleMs: currentIdleMs, state: snap.state, iteration: snap.iteration }, "Agent still active, continuing...");
|
|
2641
|
+
}
|
|
2642
|
+
}, POLL_INTERVAL_MS);
|
|
2643
|
+
}, initialTimeoutMs);
|
|
2644
|
+
safetyTimer = setTimeout(() => {
|
|
2645
|
+
if (settled)
|
|
2646
|
+
return;
|
|
2647
|
+
const snap = tracker.getSnapshot();
|
|
2648
|
+
this.logger.error({ skill: name, totalMs: snap.totalElapsedMs, state: snap.state, iteration: snap.iteration }, "Absolute time limit reached \u2014 force killing agent");
|
|
2649
|
+
finish({
|
|
2650
|
+
success: false,
|
|
2651
|
+
error: `Skill "${name}" force-killed after ${Math.round(MAX_TOTAL_TIME_MS / 6e4)} minutes (safety limit)`
|
|
2652
|
+
});
|
|
2653
|
+
}, MAX_TOTAL_TIME_MS);
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Simple hard timeout for skills that don't use a tracker.
|
|
2658
|
+
* This is the legacy behavior.
|
|
2659
|
+
*/
|
|
2660
|
+
async executeWithHardTimeout(skill, input2, context, name, timeoutMs) {
|
|
2543
2661
|
try {
|
|
2544
2662
|
const result = await Promise.race([
|
|
2545
2663
|
skill.execute(input2, context),
|
|
@@ -2562,6 +2680,90 @@ var init_skill_sandbox = __esm({
|
|
|
2562
2680
|
}
|
|
2563
2681
|
});
|
|
2564
2682
|
|
|
2683
|
+
// ../skills/dist/activity-tracker.js
|
|
2684
|
+
var ActivityTracker;
|
|
2685
|
+
var init_activity_tracker = __esm({
|
|
2686
|
+
"../skills/dist/activity-tracker.js"() {
|
|
2687
|
+
"use strict";
|
|
2688
|
+
ActivityTracker = class {
|
|
2689
|
+
state = "starting";
|
|
2690
|
+
iteration = 0;
|
|
2691
|
+
maxIterations = 0;
|
|
2692
|
+
currentTool;
|
|
2693
|
+
lastPingAt;
|
|
2694
|
+
startedAt;
|
|
2695
|
+
history = [];
|
|
2696
|
+
onProgress;
|
|
2697
|
+
constructor(onProgress) {
|
|
2698
|
+
this.startedAt = Date.now();
|
|
2699
|
+
this.lastPingAt = Date.now();
|
|
2700
|
+
this.onProgress = onProgress;
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Called by the agent at every meaningful step.
|
|
2704
|
+
* Resets the inactivity timer and reports status upward.
|
|
2705
|
+
*/
|
|
2706
|
+
ping(state, meta) {
|
|
2707
|
+
this.state = state;
|
|
2708
|
+
this.lastPingAt = Date.now();
|
|
2709
|
+
if (meta?.iteration !== void 0)
|
|
2710
|
+
this.iteration = meta.iteration;
|
|
2711
|
+
if (meta?.maxIterations !== void 0)
|
|
2712
|
+
this.maxIterations = meta.maxIterations;
|
|
2713
|
+
this.currentTool = meta?.tool;
|
|
2714
|
+
this.history.push({
|
|
2715
|
+
state,
|
|
2716
|
+
tool: meta?.tool,
|
|
2717
|
+
iteration: this.iteration,
|
|
2718
|
+
timestamp: this.lastPingAt
|
|
2719
|
+
});
|
|
2720
|
+
if (this.onProgress) {
|
|
2721
|
+
this.onProgress(this.formatStatus());
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
/** How long since last activity, in ms. */
|
|
2725
|
+
getIdleMs() {
|
|
2726
|
+
return Date.now() - this.lastPingAt;
|
|
2727
|
+
}
|
|
2728
|
+
/** Total elapsed time since agent started. */
|
|
2729
|
+
getTotalElapsedMs() {
|
|
2730
|
+
return Date.now() - this.startedAt;
|
|
2731
|
+
}
|
|
2732
|
+
/** Current human-readable status string. */
|
|
2733
|
+
formatStatus() {
|
|
2734
|
+
const iter = this.maxIterations > 0 ? ` (${this.iteration}/${this.maxIterations})` : "";
|
|
2735
|
+
switch (this.state) {
|
|
2736
|
+
case "starting":
|
|
2737
|
+
return `Sub-agent starting...`;
|
|
2738
|
+
case "llm_call":
|
|
2739
|
+
return `Sub-agent thinking...${iter}`;
|
|
2740
|
+
case "tool_call":
|
|
2741
|
+
return this.currentTool ? `Sub-agent using ${this.currentTool}${iter}` : `Sub-agent using tool...${iter}`;
|
|
2742
|
+
case "processing":
|
|
2743
|
+
return `Sub-agent processing...${iter}`;
|
|
2744
|
+
case "done":
|
|
2745
|
+
return `Sub-agent done${iter}`;
|
|
2746
|
+
default:
|
|
2747
|
+
return `Sub-agent working...${iter}`;
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
/** Full snapshot for logging / debugging. */
|
|
2751
|
+
getSnapshot() {
|
|
2752
|
+
return {
|
|
2753
|
+
state: this.state,
|
|
2754
|
+
iteration: this.iteration,
|
|
2755
|
+
maxIterations: this.maxIterations,
|
|
2756
|
+
lastPingAt: this.lastPingAt,
|
|
2757
|
+
idleMs: this.getIdleMs(),
|
|
2758
|
+
currentTool: this.currentTool,
|
|
2759
|
+
totalElapsedMs: this.getTotalElapsedMs(),
|
|
2760
|
+
history: [...this.history]
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
|
|
2565
2767
|
// ../skills/dist/plugin-loader.js
|
|
2566
2768
|
import fs3 from "node:fs";
|
|
2567
2769
|
import path3 from "node:path";
|
|
@@ -2974,9 +3176,9 @@ var init_reminder = __esm({
|
|
|
2974
3176
|
reminderRepo;
|
|
2975
3177
|
metadata = {
|
|
2976
3178
|
name: "reminder",
|
|
2977
|
-
description: 'Set timed reminders that notify the user later. Use when the user says "remind me", "erinnere mich", or asks to be notified about something at a specific time.',
|
|
3179
|
+
description: 'Set timed reminders that notify the user later. Use when the user says "remind me", "erinnere mich", or asks to be notified about something at a specific time. Prefer triggerAt (absolute time like "14:30" or "2026-02-28 09:00") over delayMinutes \u2014 it is more precise and avoids calculation errors.',
|
|
2978
3180
|
riskLevel: "write",
|
|
2979
|
-
version: "
|
|
3181
|
+
version: "3.0.0",
|
|
2980
3182
|
inputSchema: {
|
|
2981
3183
|
type: "object",
|
|
2982
3184
|
properties: {
|
|
@@ -2989,9 +3191,13 @@ var init_reminder = __esm({
|
|
|
2989
3191
|
type: "string",
|
|
2990
3192
|
description: "The reminder message (required for set)"
|
|
2991
3193
|
},
|
|
3194
|
+
triggerAt: {
|
|
3195
|
+
type: "string",
|
|
3196
|
+
description: 'Absolute time for the reminder. Accepts "HH:MM" for today or "YYYY-MM-DD HH:MM" for a specific date. Preferred over delayMinutes for time-specific reminders.'
|
|
3197
|
+
},
|
|
2992
3198
|
delayMinutes: {
|
|
2993
3199
|
type: "number",
|
|
2994
|
-
description: "Minutes until the reminder triggers
|
|
3200
|
+
description: "Minutes until the reminder triggers. Use triggerAt instead when the user specifies a clock time."
|
|
2995
3201
|
},
|
|
2996
3202
|
reminderId: {
|
|
2997
3203
|
type: "string",
|
|
@@ -3023,6 +3229,7 @@ var init_reminder = __esm({
|
|
|
3023
3229
|
}
|
|
3024
3230
|
setReminder(input2, context) {
|
|
3025
3231
|
const message = input2.message;
|
|
3232
|
+
const triggerAtStr = input2.triggerAt;
|
|
3026
3233
|
const delayMinutes = input2.delayMinutes;
|
|
3027
3234
|
if (!message || typeof message !== "string") {
|
|
3028
3235
|
return {
|
|
@@ -3030,20 +3237,119 @@ var init_reminder = __esm({
|
|
|
3030
3237
|
error: 'Missing required field "message" for set action'
|
|
3031
3238
|
};
|
|
3032
3239
|
}
|
|
3033
|
-
|
|
3240
|
+
let triggerAt;
|
|
3241
|
+
if (triggerAtStr && typeof triggerAtStr === "string") {
|
|
3242
|
+
const parsed = this.parseTriggerAt(triggerAtStr, context.timezone);
|
|
3243
|
+
if (!parsed) {
|
|
3244
|
+
return {
|
|
3245
|
+
success: false,
|
|
3246
|
+
error: `Could not parse triggerAt "${triggerAtStr}". Use "HH:MM" for today or "YYYY-MM-DD HH:MM" for a specific date.`
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
if (parsed.getTime() <= Date.now()) {
|
|
3250
|
+
return {
|
|
3251
|
+
success: false,
|
|
3252
|
+
error: `The time "${triggerAtStr}" is in the past. Please specify a future time.`
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
triggerAt = parsed;
|
|
3256
|
+
} else if (delayMinutes !== void 0 && typeof delayMinutes === "number" && delayMinutes > 0) {
|
|
3257
|
+
triggerAt = new Date(Date.now() + delayMinutes * 60 * 1e3);
|
|
3258
|
+
} else {
|
|
3034
3259
|
return {
|
|
3035
3260
|
success: false,
|
|
3036
|
-
error: '
|
|
3261
|
+
error: 'Provide either "triggerAt" (e.g. "14:30") or "delayMinutes" (positive number) for set action.'
|
|
3037
3262
|
};
|
|
3038
3263
|
}
|
|
3039
|
-
const triggerAt = new Date(Date.now() + delayMinutes * 60 * 1e3);
|
|
3040
3264
|
const entry = this.reminderRepo.create(context.userId, context.platform, context.chatId, message, triggerAt);
|
|
3265
|
+
const delayMs = triggerAt.getTime() - Date.now();
|
|
3266
|
+
const mins = Math.round(delayMs / 6e4);
|
|
3267
|
+
const timeLabel = triggerAt.toLocaleTimeString("en-GB", {
|
|
3268
|
+
hour: "2-digit",
|
|
3269
|
+
minute: "2-digit",
|
|
3270
|
+
...context.timezone ? { timeZone: context.timezone } : {}
|
|
3271
|
+
});
|
|
3041
3272
|
return {
|
|
3042
3273
|
success: true,
|
|
3043
3274
|
data: { reminderId: entry.id, message, triggerAt: entry.triggerAt },
|
|
3044
|
-
display: `Reminder set (${entry.id}): "${message}" in ${
|
|
3275
|
+
display: `Reminder set (${entry.id}): "${message}" at ${timeLabel} (in ${mins} min)`
|
|
3045
3276
|
};
|
|
3046
3277
|
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Parse a trigger time string into a Date.
|
|
3280
|
+
*
|
|
3281
|
+
* Supported formats:
|
|
3282
|
+
* - "HH:MM" → today at that time in the given timezone
|
|
3283
|
+
* - "YYYY-MM-DD HH:MM" → specific date+time
|
|
3284
|
+
*/
|
|
3285
|
+
parseTriggerAt(str, timezone) {
|
|
3286
|
+
const trimmed = str.trim();
|
|
3287
|
+
const timeOnly = /^(\d{1,2}):(\d{2})$/.exec(trimmed);
|
|
3288
|
+
if (timeOnly) {
|
|
3289
|
+
const hours = parseInt(timeOnly[1], 10);
|
|
3290
|
+
const minutes = parseInt(timeOnly[2], 10);
|
|
3291
|
+
if (hours > 23 || minutes > 59)
|
|
3292
|
+
return void 0;
|
|
3293
|
+
return this.buildDateInTimezone(hours, minutes, void 0, timezone);
|
|
3294
|
+
}
|
|
3295
|
+
const dateTime = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{2})$/.exec(trimmed);
|
|
3296
|
+
if (dateTime) {
|
|
3297
|
+
const year = parseInt(dateTime[1], 10);
|
|
3298
|
+
const month = parseInt(dateTime[2], 10) - 1;
|
|
3299
|
+
const day = parseInt(dateTime[3], 10);
|
|
3300
|
+
const hours = parseInt(dateTime[4], 10);
|
|
3301
|
+
const minutes = parseInt(dateTime[5], 10);
|
|
3302
|
+
if (hours > 23 || minutes > 59 || month > 11 || day > 31)
|
|
3303
|
+
return void 0;
|
|
3304
|
+
return this.buildDateInTimezone(hours, minutes, { year, month, day }, timezone);
|
|
3305
|
+
}
|
|
3306
|
+
return void 0;
|
|
3307
|
+
}
|
|
3308
|
+
/**
|
|
3309
|
+
* Build a Date object for a given time in the user's timezone.
|
|
3310
|
+
* Uses iterative offset correction to handle DST edge cases.
|
|
3311
|
+
*/
|
|
3312
|
+
buildDateInTimezone(hours, minutes, date, timezone) {
|
|
3313
|
+
if (!timezone) {
|
|
3314
|
+
const d = date ? new Date(date.year, date.month, date.day, hours, minutes, 0, 0) : /* @__PURE__ */ new Date();
|
|
3315
|
+
if (!date) {
|
|
3316
|
+
d.setHours(hours, minutes, 0, 0);
|
|
3317
|
+
}
|
|
3318
|
+
return d;
|
|
3319
|
+
}
|
|
3320
|
+
const now = /* @__PURE__ */ new Date();
|
|
3321
|
+
const refDate = date ? new Date(Date.UTC(date.year, date.month, date.day, hours, minutes, 0)) : new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, 0));
|
|
3322
|
+
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
3323
|
+
timeZone: timezone,
|
|
3324
|
+
year: "numeric",
|
|
3325
|
+
month: "2-digit",
|
|
3326
|
+
day: "2-digit",
|
|
3327
|
+
hour: "2-digit",
|
|
3328
|
+
minute: "2-digit",
|
|
3329
|
+
second: "2-digit",
|
|
3330
|
+
hour12: false
|
|
3331
|
+
});
|
|
3332
|
+
if (!date) {
|
|
3333
|
+
const parts = formatter.formatToParts(now);
|
|
3334
|
+
const tzYear = parseInt(parts.find((p) => p.type === "year").value, 10);
|
|
3335
|
+
const tzMonth = parseInt(parts.find((p) => p.type === "month").value, 10) - 1;
|
|
3336
|
+
const tzDay = parseInt(parts.find((p) => p.type === "day").value, 10);
|
|
3337
|
+
let guess2 = new Date(Date.UTC(tzYear, tzMonth, tzDay, hours, minutes, 0));
|
|
3338
|
+
const guessParts2 = formatter.formatToParts(guess2);
|
|
3339
|
+
const guessHour2 = parseInt(guessParts2.find((p) => p.type === "hour").value, 10);
|
|
3340
|
+
const guessMinute2 = parseInt(guessParts2.find((p) => p.type === "minute").value, 10);
|
|
3341
|
+
const diffMinutes2 = (hours - guessHour2) * 60 + (minutes - guessMinute2);
|
|
3342
|
+
guess2 = new Date(guess2.getTime() + diffMinutes2 * 6e4);
|
|
3343
|
+
return guess2;
|
|
3344
|
+
}
|
|
3345
|
+
let guess = refDate;
|
|
3346
|
+
const guessParts = formatter.formatToParts(guess);
|
|
3347
|
+
const guessHour = parseInt(guessParts.find((p) => p.type === "hour").value, 10);
|
|
3348
|
+
const guessMinute = parseInt(guessParts.find((p) => p.type === "minute").value, 10);
|
|
3349
|
+
const diffMinutes = (hours - guessHour) * 60 + (minutes - guessMinute);
|
|
3350
|
+
guess = new Date(guess.getTime() + diffMinutes * 6e4);
|
|
3351
|
+
return guess;
|
|
3352
|
+
}
|
|
3047
3353
|
listReminders(context) {
|
|
3048
3354
|
const reminders = this.reminderRepo.getByUser(context.userId);
|
|
3049
3355
|
const reminderList = reminders.map((r) => ({
|
|
@@ -3576,12 +3882,15 @@ ${results.map((r) => `- ${r.key}: "${r.value}" (score: ${r.score.toFixed(2)})`).
|
|
|
3576
3882
|
});
|
|
3577
3883
|
|
|
3578
3884
|
// ../skills/dist/built-in/delegate.js
|
|
3579
|
-
var
|
|
3885
|
+
var DEFAULT_MAX_ITERATIONS, MAX_ALLOWED_ITERATIONS, INITIAL_TIMEOUT_MS, DelegateSkill;
|
|
3580
3886
|
var init_delegate = __esm({
|
|
3581
3887
|
"../skills/dist/built-in/delegate.js"() {
|
|
3582
3888
|
"use strict";
|
|
3583
3889
|
init_skill();
|
|
3584
|
-
|
|
3890
|
+
init_activity_tracker();
|
|
3891
|
+
DEFAULT_MAX_ITERATIONS = 5;
|
|
3892
|
+
MAX_ALLOWED_ITERATIONS = 15;
|
|
3893
|
+
INITIAL_TIMEOUT_MS = 12e4;
|
|
3585
3894
|
DelegateSkill = class extends Skill {
|
|
3586
3895
|
llm;
|
|
3587
3896
|
skillRegistry;
|
|
@@ -3589,11 +3898,10 @@ var init_delegate = __esm({
|
|
|
3589
3898
|
securityManager;
|
|
3590
3899
|
metadata = {
|
|
3591
3900
|
name: "delegate",
|
|
3592
|
-
description: 'Delegate a complex sub-task to an autonomous sub-agent that has full tool access. The sub-agent can use shell, web search, calculator, memory, email, and all other tools. Use when a task is independent enough to run in parallel or when it requires a focused, multi-step workflow (e.g. "research X and summarize", "find all TODO files and list them", "check the weather and draft a packing list").
|
|
3901
|
+
description: 'Delegate a complex sub-task to an autonomous sub-agent that has full tool access. The sub-agent can use shell, web search, calculator, memory, email, and all other tools. Use when a task is independent enough to run in parallel or when it requires a focused, multi-step workflow (e.g. "research X and summarize", "find all TODO files and list them", "check the weather and draft a packing list"). Control depth with max_iterations (default 5, max 15).',
|
|
3593
3902
|
riskLevel: "write",
|
|
3594
|
-
version: "
|
|
3595
|
-
timeoutMs:
|
|
3596
|
-
// 2 minutes — delegate chains multiple LLM calls + tool executions
|
|
3903
|
+
version: "3.0.0",
|
|
3904
|
+
timeoutMs: INITIAL_TIMEOUT_MS,
|
|
3597
3905
|
inputSchema: {
|
|
3598
3906
|
type: "object",
|
|
3599
3907
|
properties: {
|
|
@@ -3604,11 +3912,16 @@ var init_delegate = __esm({
|
|
|
3604
3912
|
context: {
|
|
3605
3913
|
type: "string",
|
|
3606
3914
|
description: "Additional context the sub-agent needs (optional)"
|
|
3915
|
+
},
|
|
3916
|
+
max_iterations: {
|
|
3917
|
+
type: "number",
|
|
3918
|
+
description: "Max tool iterations (1-15). Use higher values for complex multi-step tasks. Default: 5."
|
|
3607
3919
|
}
|
|
3608
3920
|
},
|
|
3609
3921
|
required: ["task"]
|
|
3610
3922
|
}
|
|
3611
3923
|
};
|
|
3924
|
+
onProgress;
|
|
3612
3925
|
constructor(llm, skillRegistry, skillSandbox, securityManager) {
|
|
3613
3926
|
super();
|
|
3614
3927
|
this.llm = llm;
|
|
@@ -3616,6 +3929,21 @@ var init_delegate = __esm({
|
|
|
3616
3929
|
this.skillSandbox = skillSandbox;
|
|
3617
3930
|
this.securityManager = securityManager;
|
|
3618
3931
|
}
|
|
3932
|
+
/**
|
|
3933
|
+
* Set a progress callback before execution.
|
|
3934
|
+
* The pipeline calls this so the user sees live status updates
|
|
3935
|
+
* like "Sub-agent using web_search (2/5)".
|
|
3936
|
+
*/
|
|
3937
|
+
setProgressCallback(cb) {
|
|
3938
|
+
this.onProgress = cb;
|
|
3939
|
+
}
|
|
3940
|
+
/**
|
|
3941
|
+
* Create an ActivityTracker for this execution.
|
|
3942
|
+
* The sandbox uses this to decide whether to extend or kill.
|
|
3943
|
+
*/
|
|
3944
|
+
createTracker() {
|
|
3945
|
+
return new ActivityTracker(this.onProgress);
|
|
3946
|
+
}
|
|
3619
3947
|
async execute(input2, context) {
|
|
3620
3948
|
const task = input2.task;
|
|
3621
3949
|
const additionalContext = input2.context;
|
|
@@ -3625,6 +3953,10 @@ var init_delegate = __esm({
|
|
|
3625
3953
|
error: 'Missing required field "task"'
|
|
3626
3954
|
};
|
|
3627
3955
|
}
|
|
3956
|
+
const requestedIterations = input2.max_iterations;
|
|
3957
|
+
const maxIterations = requestedIterations ? Math.max(1, Math.min(MAX_ALLOWED_ITERATIONS, Math.round(requestedIterations))) : DEFAULT_MAX_ITERATIONS;
|
|
3958
|
+
const tracker = new ActivityTracker(this.onProgress);
|
|
3959
|
+
tracker.ping("starting", { maxIterations });
|
|
3628
3960
|
const tools = this.buildSubAgentTools();
|
|
3629
3961
|
const systemPrompt = "You are a sub-agent of Alfred, a personal AI assistant. Complete the assigned task using the tools available to you. Work step by step: use tools to gather information, then synthesize a clear result. Be concise and return only the final answer when done.";
|
|
3630
3962
|
let userContent = task;
|
|
@@ -3641,6 +3973,7 @@ Additional context: ${additionalContext}`;
|
|
|
3641
3973
|
let totalInputTokens = 0;
|
|
3642
3974
|
let totalOutputTokens = 0;
|
|
3643
3975
|
while (true) {
|
|
3976
|
+
tracker.ping("llm_call", { iteration, maxIterations });
|
|
3644
3977
|
const response = await this.llm.complete({
|
|
3645
3978
|
messages,
|
|
3646
3979
|
system: systemPrompt,
|
|
@@ -3649,7 +3982,9 @@ Additional context: ${additionalContext}`;
|
|
|
3649
3982
|
});
|
|
3650
3983
|
totalInputTokens += response.usage.inputTokens;
|
|
3651
3984
|
totalOutputTokens += response.usage.outputTokens;
|
|
3652
|
-
|
|
3985
|
+
tracker.ping("processing", { iteration, maxIterations });
|
|
3986
|
+
if (!response.toolCalls || response.toolCalls.length === 0 || iteration >= maxIterations) {
|
|
3987
|
+
tracker.ping("done", { iteration, maxIterations });
|
|
3653
3988
|
return {
|
|
3654
3989
|
success: true,
|
|
3655
3990
|
data: {
|
|
@@ -3676,6 +4011,7 @@ Additional context: ${additionalContext}`;
|
|
|
3676
4011
|
messages.push({ role: "assistant", content: assistantContent });
|
|
3677
4012
|
const toolResultBlocks = [];
|
|
3678
4013
|
for (const toolCall of response.toolCalls) {
|
|
4014
|
+
tracker.ping("tool_call", { iteration, maxIterations, tool: toolCall.name });
|
|
3679
4015
|
const result = await this.executeSubAgentTool(toolCall, context);
|
|
3680
4016
|
toolResultBlocks.push({
|
|
3681
4017
|
type: "tool_result",
|
|
@@ -5673,6 +6009,7 @@ var init_dist6 = __esm({
|
|
|
5673
6009
|
init_skill();
|
|
5674
6010
|
init_skill_registry();
|
|
5675
6011
|
init_skill_sandbox();
|
|
6012
|
+
init_activity_tracker();
|
|
5676
6013
|
init_plugin_loader();
|
|
5677
6014
|
init_calculator();
|
|
5678
6015
|
init_system_info();
|
|
@@ -5746,6 +6083,9 @@ var init_message_pipeline = __esm({
|
|
|
5746
6083
|
speechTranscriber;
|
|
5747
6084
|
inboxPath;
|
|
5748
6085
|
embeddingService;
|
|
6086
|
+
/** Registry of currently running delegate agents, keyed by a unique agent ID. */
|
|
6087
|
+
activeAgents = /* @__PURE__ */ new Map();
|
|
6088
|
+
agentIdCounter = 0;
|
|
5749
6089
|
constructor(options) {
|
|
5750
6090
|
this.llm = options.llm;
|
|
5751
6091
|
this.conversationManager = options.conversationManager;
|
|
@@ -5804,13 +6144,18 @@ var init_message_pipeline = __esm({
|
|
|
5804
6144
|
}
|
|
5805
6145
|
} catch {
|
|
5806
6146
|
}
|
|
6147
|
+
const resolvedTimezone = userProfile?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5807
6148
|
const skillMetas = this.skillRegistry ? this.skillRegistry.getAll().map((s) => s.metadata) : void 0;
|
|
5808
6149
|
const tools = skillMetas ? this.promptBuilder.buildTools(skillMetas) : void 0;
|
|
5809
|
-
|
|
6150
|
+
let system = this.promptBuilder.buildSystemPrompt({
|
|
5810
6151
|
memories,
|
|
5811
6152
|
skills: skillMetas,
|
|
5812
6153
|
userProfile
|
|
5813
6154
|
});
|
|
6155
|
+
const agentStatusBlock = this.buildActiveAgentStatus();
|
|
6156
|
+
if (agentStatusBlock) {
|
|
6157
|
+
system += "\n\n" + agentStatusBlock;
|
|
6158
|
+
}
|
|
5814
6159
|
const allMessages = this.promptBuilder.buildMessages(history);
|
|
5815
6160
|
const userContent = await this.buildUserContent(message, onProgress);
|
|
5816
6161
|
allMessages.push({ role: "user", content: userContent });
|
|
@@ -5854,8 +6199,9 @@ var init_message_pipeline = __esm({
|
|
|
5854
6199
|
chatId: message.chatId,
|
|
5855
6200
|
chatType: message.chatType,
|
|
5856
6201
|
platform: message.platform,
|
|
5857
|
-
conversationId: conversation.id
|
|
5858
|
-
|
|
6202
|
+
conversationId: conversation.id,
|
|
6203
|
+
timezone: resolvedTimezone
|
|
6204
|
+
}, onProgress);
|
|
5859
6205
|
toolResultBlocks.push({
|
|
5860
6206
|
type: "tool_result",
|
|
5861
6207
|
tool_use_id: toolCall.id,
|
|
@@ -5885,7 +6231,7 @@ var init_message_pipeline = __esm({
|
|
|
5885
6231
|
throw error;
|
|
5886
6232
|
}
|
|
5887
6233
|
}
|
|
5888
|
-
async executeToolCall(toolCall, context) {
|
|
6234
|
+
async executeToolCall(toolCall, context, onProgress) {
|
|
5889
6235
|
const skill = this.skillRegistry?.get(toolCall.name);
|
|
5890
6236
|
if (!skill) {
|
|
5891
6237
|
this.logger.warn({ tool: toolCall.name }, "Unknown skill requested");
|
|
@@ -5909,11 +6255,33 @@ var init_message_pipeline = __esm({
|
|
|
5909
6255
|
}
|
|
5910
6256
|
}
|
|
5911
6257
|
if (this.skillSandbox) {
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
6258
|
+
let tracker;
|
|
6259
|
+
let agentId;
|
|
6260
|
+
if (toolCall.name === "delegate" && "setProgressCallback" in skill && "createTracker" in skill) {
|
|
6261
|
+
const delegateSkill = skill;
|
|
6262
|
+
if (onProgress) {
|
|
6263
|
+
delegateSkill.setProgressCallback(onProgress);
|
|
6264
|
+
}
|
|
6265
|
+
tracker = delegateSkill.createTracker();
|
|
6266
|
+
agentId = `agent-${++this.agentIdCounter}`;
|
|
6267
|
+
this.activeAgents.set(agentId, {
|
|
6268
|
+
chatId: context.chatId,
|
|
6269
|
+
task: String(toolCall.input.task ?? "").slice(0, 200),
|
|
6270
|
+
tracker,
|
|
6271
|
+
startedAt: Date.now()
|
|
6272
|
+
});
|
|
6273
|
+
}
|
|
6274
|
+
try {
|
|
6275
|
+
const result = await this.skillSandbox.execute(skill, toolCall.input, context, void 0, tracker);
|
|
6276
|
+
return {
|
|
6277
|
+
content: result.display ?? (result.success ? JSON.stringify(result.data) : result.error ?? "Unknown error"),
|
|
6278
|
+
isError: !result.success
|
|
6279
|
+
};
|
|
6280
|
+
} finally {
|
|
6281
|
+
if (agentId) {
|
|
6282
|
+
this.activeAgents.delete(agentId);
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
5917
6285
|
}
|
|
5918
6286
|
try {
|
|
5919
6287
|
const result = await skill.execute(toolCall.input, context);
|
|
@@ -5966,6 +6334,24 @@ var init_message_pipeline = __esm({
|
|
|
5966
6334
|
return `Using ${toolName}...`;
|
|
5967
6335
|
}
|
|
5968
6336
|
}
|
|
6337
|
+
/**
|
|
6338
|
+
* Build a status block describing currently running delegate agents.
|
|
6339
|
+
* Injected into the system prompt so the LLM can answer user questions
|
|
6340
|
+
* like "What is the agent doing right now?".
|
|
6341
|
+
*/
|
|
6342
|
+
buildActiveAgentStatus() {
|
|
6343
|
+
if (this.activeAgents.size === 0)
|
|
6344
|
+
return void 0;
|
|
6345
|
+
const lines = ["## Currently running sub-agents"];
|
|
6346
|
+
for (const [id, agent] of this.activeAgents) {
|
|
6347
|
+
const snapshot = agent.tracker.getSnapshot();
|
|
6348
|
+
const elapsedSec = Math.round(snapshot.totalElapsedMs / 1e3);
|
|
6349
|
+
lines.push(`- **${id}**: "${agent.task}"`, ` Status: ${agent.tracker.formatStatus()}`, ` Running for ${elapsedSec}s | Last activity ${Math.round(snapshot.idleMs / 1e3)}s ago`);
|
|
6350
|
+
}
|
|
6351
|
+
lines.push("");
|
|
6352
|
+
lines.push("If the user asks what you or the agent is doing, describe the above status in natural language.");
|
|
6353
|
+
return lines.join("\n");
|
|
6354
|
+
}
|
|
5969
6355
|
/**
|
|
5970
6356
|
* Trim messages to fit within the LLM's context window.
|
|
5971
6357
|
* Keeps the system prompt, the latest user message, and as many
|