agent.libx.js 0.93.45 → 0.94.1

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/index.d.ts CHANGED
@@ -293,6 +293,99 @@ declare class FakeAIClient implements ChatLike {
293
293
  /** Build a tool_call block (arguments JSON-stringified, matching the wire format). */
294
294
  declare const toolCall: (id: string, name: string, args: object) => ToolCall;
295
295
 
296
+ /**
297
+ * In-process scheduler — timers that fire prompts into the agent loop while the session is alive.
298
+ * Three trigger modes: one-off ({at}), interval ({everyMs}), cron ({cron}).
299
+ * The scheduler never calls `turn()` itself — it invokes an IoC `fire` callback with due jobs,
300
+ * keeping it testable and host-agnostic. Persistence is the host's responsibility (serialize jobs
301
+ * into the session and re-arm on resume).
302
+ *
303
+ * Survives --resume (host reloads); does NOT survive process exit (no daemon, no launchd).
304
+ */
305
+ type TriggerOneOff = {
306
+ at: number;
307
+ };
308
+ type TriggerInterval = {
309
+ everyMs: number;
310
+ };
311
+ type TriggerCron = {
312
+ cron: string;
313
+ };
314
+ type Trigger = TriggerOneOff | TriggerInterval | TriggerCron;
315
+ type ScheduledJobStatus = 'active' | 'done';
316
+ interface ScheduledJob {
317
+ id: string;
318
+ prompt: string;
319
+ trigger: Trigger;
320
+ status: ScheduledJobStatus;
321
+ created: number;
322
+ lastRun?: number;
323
+ runs: number;
324
+ /** Optional label for display (e.g. footer, /schedule list). */
325
+ label?: string;
326
+ }
327
+ /** Serializable snapshot for session persistence (same shape — kept as an alias for clarity at call sites). */
328
+ type ScheduledJobSnapshot = ScheduledJob;
329
+ interface SchedulerOptions {
330
+ /** Called when a job is due. Must not throw — the scheduler logs errors and continues. */
331
+ fire: (job: ScheduledJob) => Promise<void> | void;
332
+ /** Monotonic clock override for testing (default: Date.now). */
333
+ now?: () => number;
334
+ /** Tick interval in ms (default: 15_000 — 15s). */
335
+ tickMs?: number;
336
+ /** If true, suppress the setInterval (for unit tests that call tick() manually). */
337
+ manual?: boolean;
338
+ }
339
+ interface CronFields {
340
+ minute: number[];
341
+ hour: number[];
342
+ dom: number[];
343
+ month: number[];
344
+ dow: number[];
345
+ }
346
+ declare function parseCron(expr: string): CronFields;
347
+ declare function cronMatches(fields: CronFields, date: Date): boolean;
348
+ /** Next occurrence of `cron` after `after` (epoch ms). Returns epoch ms, or null if >366 days out (guard). */
349
+ declare function nextCronAfter(cron: string, after: number): number | null;
350
+ declare class Scheduler {
351
+ private jobs;
352
+ private seq;
353
+ private timer;
354
+ private firing;
355
+ private readonly fire;
356
+ private readonly now;
357
+ private readonly tickMs;
358
+ constructor(opts: SchedulerOptions);
359
+ /** Start the tick timer. Idempotent. */
360
+ start(): void;
361
+ /** Stop the tick timer. Does not clear jobs. */
362
+ stop(): void;
363
+ /** Add a scheduled job. Returns the job id. */
364
+ add(opts: {
365
+ prompt: string;
366
+ trigger: Trigger;
367
+ label?: string;
368
+ }): string;
369
+ /** Cancel a job. Returns false if not found. */
370
+ cancel(id: string): boolean;
371
+ get(id: string): ScheduledJob | undefined;
372
+ list(): ScheduledJob[];
373
+ active(): ScheduledJob[];
374
+ /** Compute when a job next fires (epoch ms), or null if done/unknown. */
375
+ nextFire(job: ScheduledJob): number | null;
376
+ /** Check all active jobs and fire any that are due. Reentrant-safe. */
377
+ tick(): Promise<void>;
378
+ /** Export all jobs for session persistence. */
379
+ snapshot(): ScheduledJobSnapshot[];
380
+ /** Restore jobs from a snapshot (e.g. on --resume). Clears existing jobs first. */
381
+ restore(snapshots: ScheduledJobSnapshot[]): void;
382
+ /** Number of active jobs. */
383
+ get size(): number;
384
+ destroy(): void;
385
+ }
386
+
387
+ declare function makeScheduleTools(scheduler: Scheduler): AgentTool[];
388
+
296
389
  /** Sandbox mode: an in-memory VFS — the real disk is never read or written. Edge/browser/test/dry-run. */
297
390
  declare function sandboxAgentOptions(opts?: Partial<AgentOptions>): Partial<AgentOptions>;
298
391
  /** Disk mode (the default): operate on the real filesystem, jailed to cwd (secrets hidden by DEFAULT_DENY).
@@ -1162,4 +1255,4 @@ declare class CartesiaTTS {
1162
1255
  close(): void;
1163
1256
  }
1164
1257
 
1165
- export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, type AudioSink, type AudioSource, type AuthProvider, BodDbFilesystem, CartesiaTTS, CartesiaTTSOptions, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, type ReflectOptions, RunResult, SCRATCH_DIR, STT_SAMPLE_RATE, Scratch, type ScratchOptions, ScriptedHostBridge, type SkillInfo, SonioxSTT, SonioxSTTOptions, type SttLike, TTS_SAMPLE_RATE, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type TtsLike, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, VoiceEngine, VoiceEngineOptions, type VoiceState, type WebFetchOptions, type WebSearchOptions, type WorkerTier, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, compileSynthesizedTool, decodeDdgUrl, diskAgentOptions, expandCommand, expandTemplate, forComponent, fullAgentOptions, globTool, grepTool, htmlToText, idfWeights, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchTool, mkdirp, multiEditTool, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, resolveAuth, rollbackTool, sandboxAgentOptions, slugify, tokenize, toolCall, topByRelevance, validateToolCode, webFetchTool, webSearchTool, writeFact, writeTool };
1258
+ export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, type AudioSink, type AudioSource, type AuthProvider, BodDbFilesystem, CartesiaTTS, CartesiaTTSOptions, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, type ReflectOptions, RunResult, SCRATCH_DIR, STT_SAMPLE_RATE, type ScheduledJob, type ScheduledJobSnapshot, Scheduler, type SchedulerOptions, Scratch, type ScratchOptions, ScriptedHostBridge, type SkillInfo, SonioxSTT, SonioxSTTOptions, type SttLike, TTS_SAMPLE_RATE, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type Trigger, type TriggerCron, type TriggerInterval, type TriggerOneOff, type TtsLike, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, VoiceEngine, VoiceEngineOptions, type VoiceState, type WebFetchOptions, type WebSearchOptions, type WorkerTier, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, compileSynthesizedTool, cronMatches, decodeDdgUrl, diskAgentOptions, expandCommand, expandTemplate, forComponent, fullAgentOptions, globTool, grepTool, htmlToText, idfWeights, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeScheduleTools, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchTool, mkdirp, multiEditTool, nextCronAfter, parseCron, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, resolveAuth, rollbackTool, sandboxAgentOptions, slugify, tokenize, toolCall, topByRelevance, validateToolCode, webFetchTool, webSearchTool, writeFact, writeTool };
package/dist/index.js CHANGED
@@ -3583,6 +3583,253 @@ var toolCall = (id, name, args) => ({
3583
3583
  // src/index.ts
3584
3584
  init_tools();
3585
3585
 
3586
+ // src/scheduler.ts
3587
+ function parseCronField(field, min, max) {
3588
+ const vals = /* @__PURE__ */ new Set();
3589
+ for (const part of field.split(",")) {
3590
+ const [rangeStr, stepStr] = part.split("/");
3591
+ const step = stepStr ? parseInt(stepStr, 10) : 1;
3592
+ if (isNaN(step) || step < 1) throw new Error(`invalid cron step: ${part}`);
3593
+ let lo, hi;
3594
+ if (rangeStr === "*") {
3595
+ lo = min;
3596
+ hi = max;
3597
+ } else if (rangeStr.includes("-")) {
3598
+ const [a, b] = rangeStr.split("-").map(Number);
3599
+ if (isNaN(a) || isNaN(b)) throw new Error(`invalid cron range: ${part}`);
3600
+ lo = a;
3601
+ hi = b;
3602
+ } else {
3603
+ const n = parseInt(rangeStr, 10);
3604
+ if (isNaN(n)) throw new Error(`invalid cron value: ${part}`);
3605
+ lo = n;
3606
+ hi = stepStr ? max : n;
3607
+ }
3608
+ for (let i = lo; i <= hi; i += step) vals.add(i);
3609
+ }
3610
+ return [...vals].sort((a, b) => a - b);
3611
+ }
3612
+ function parseCron(expr) {
3613
+ const parts = expr.trim().split(/\s+/);
3614
+ if (parts.length !== 5) throw new Error(`cron expression must have 5 fields (minute hour dom month dow), got ${parts.length}: "${expr}"`);
3615
+ return {
3616
+ minute: parseCronField(parts[0], 0, 59),
3617
+ hour: parseCronField(parts[1], 0, 23),
3618
+ dom: parseCronField(parts[2], 1, 31),
3619
+ month: parseCronField(parts[3], 1, 12),
3620
+ dow: parseCronField(parts[4], 0, 6)
3621
+ };
3622
+ }
3623
+ function cronMatches(fields, date) {
3624
+ return fields.minute.includes(date.getMinutes()) && fields.hour.includes(date.getHours()) && fields.dom.includes(date.getDate()) && fields.month.includes(date.getMonth() + 1) && fields.dow.includes(date.getDay());
3625
+ }
3626
+ function nextCronAfter(cron, after) {
3627
+ const fields = parseCron(cron);
3628
+ const d = new Date(after);
3629
+ d.setSeconds(0, 0);
3630
+ d.setMinutes(d.getMinutes() + 1);
3631
+ const limit = after + 366 * 864e5;
3632
+ while (d.getTime() <= limit) {
3633
+ if (cronMatches(fields, d)) return d.getTime();
3634
+ d.setMinutes(d.getMinutes() + 1);
3635
+ }
3636
+ return null;
3637
+ }
3638
+ var Scheduler = class {
3639
+ jobs = /* @__PURE__ */ new Map();
3640
+ seq = 0;
3641
+ timer = null;
3642
+ firing = false;
3643
+ fire;
3644
+ now;
3645
+ tickMs;
3646
+ constructor(opts) {
3647
+ this.fire = opts.fire;
3648
+ this.now = opts.now ?? Date.now;
3649
+ this.tickMs = opts.tickMs ?? 15e3;
3650
+ if (!opts.manual) this.start();
3651
+ }
3652
+ /** Start the tick timer. Idempotent. */
3653
+ start() {
3654
+ if (this.timer) return;
3655
+ this.timer = setInterval(() => this.tick(), this.tickMs);
3656
+ if (typeof this.timer === "object" && "unref" in this.timer) this.timer.unref();
3657
+ }
3658
+ /** Stop the tick timer. Does not clear jobs. */
3659
+ stop() {
3660
+ if (this.timer) {
3661
+ clearInterval(this.timer);
3662
+ this.timer = null;
3663
+ }
3664
+ }
3665
+ /** Add a scheduled job. Returns the job id. */
3666
+ add(opts) {
3667
+ if ("cron" in opts.trigger) parseCron(opts.trigger.cron);
3668
+ if ("at" in opts.trigger && opts.trigger.at < this.now()) {
3669
+ }
3670
+ if ("everyMs" in opts.trigger && opts.trigger.everyMs < 1e3) {
3671
+ throw new Error("interval must be >= 1000ms");
3672
+ }
3673
+ const id = `sched-${++this.seq}`;
3674
+ this.jobs.set(id, {
3675
+ id,
3676
+ prompt: opts.prompt,
3677
+ trigger: opts.trigger,
3678
+ status: "active",
3679
+ created: this.now(),
3680
+ runs: 0,
3681
+ label: opts.label
3682
+ });
3683
+ return id;
3684
+ }
3685
+ /** Cancel a job. Returns false if not found. */
3686
+ cancel(id) {
3687
+ const j = this.jobs.get(id);
3688
+ if (!j) return false;
3689
+ j.status = "done";
3690
+ return true;
3691
+ }
3692
+ get(id) {
3693
+ return this.jobs.get(id);
3694
+ }
3695
+ list() {
3696
+ return [...this.jobs.values()];
3697
+ }
3698
+ active() {
3699
+ return this.list().filter((j) => j.status === "active");
3700
+ }
3701
+ /** Compute when a job next fires (epoch ms), or null if done/unknown. */
3702
+ nextFire(job) {
3703
+ if (job.status !== "active") return null;
3704
+ const t = job.trigger;
3705
+ if ("at" in t) return t.at;
3706
+ if ("everyMs" in t) return (job.lastRun ?? job.created) + t.everyMs;
3707
+ if ("cron" in t) return nextCronAfter(t.cron, job.lastRun ?? job.created);
3708
+ return null;
3709
+ }
3710
+ /** Check all active jobs and fire any that are due. Reentrant-safe. */
3711
+ async tick() {
3712
+ if (this.firing) return;
3713
+ this.firing = true;
3714
+ try {
3715
+ const now4 = this.now();
3716
+ for (const job of this.jobs.values()) {
3717
+ if (job.status !== "active") continue;
3718
+ const due = this.nextFire(job);
3719
+ if (due == null || due > now4) continue;
3720
+ job.lastRun = now4;
3721
+ job.runs++;
3722
+ if ("at" in job.trigger) job.status = "done";
3723
+ try {
3724
+ await this.fire(job);
3725
+ } catch {
3726
+ }
3727
+ }
3728
+ } finally {
3729
+ this.firing = false;
3730
+ }
3731
+ }
3732
+ /** Export all jobs for session persistence. */
3733
+ snapshot() {
3734
+ return this.list().filter((j) => j.status === "active").map((j) => ({ ...j }));
3735
+ }
3736
+ /** Restore jobs from a snapshot (e.g. on --resume). Clears existing jobs first. */
3737
+ restore(snapshots) {
3738
+ this.jobs.clear();
3739
+ for (const s of snapshots) {
3740
+ this.seq = Math.max(this.seq, parseInt(s.id.replace("sched-", ""), 10) || 0);
3741
+ this.jobs.set(s.id, { ...s });
3742
+ }
3743
+ }
3744
+ /** Number of active jobs. */
3745
+ get size() {
3746
+ return this.active().length;
3747
+ }
3748
+ destroy() {
3749
+ this.stop();
3750
+ this.jobs.clear();
3751
+ }
3752
+ };
3753
+ function makeScheduleTools(scheduler) {
3754
+ return [
3755
+ {
3756
+ name: "ScheduleTask",
3757
+ description: 'Schedule a prompt to fire automatically while this session is alive.\nModes:\n \u2022 One-off: {at: <epoch_ms>} \u2014 fires once at that time, then done.\n \u2022 Interval: {everyMs: <ms>} \u2014 fires repeatedly (\u22651s).\n \u2022 Cron: {cron: "min hr dom mon dow"} \u2014 standard 5-field cron.\nReturns the job id. Jobs only fire while the CLI session is running \u2014 they do NOT survive quitting.',
3758
+ parameters: {
3759
+ type: "object",
3760
+ required: ["prompt", "trigger"],
3761
+ properties: {
3762
+ prompt: { type: "string", description: "The prompt to inject when the job fires." },
3763
+ trigger: {
3764
+ type: "object",
3765
+ description: 'One of: {at: epoch_ms}, {everyMs: ms}, {cron: "5-field expr"}.',
3766
+ properties: {
3767
+ at: { type: "number" },
3768
+ everyMs: { type: "number" },
3769
+ cron: { type: "string" }
3770
+ }
3771
+ },
3772
+ label: { type: "string", description: "Short label for display (optional)." }
3773
+ }
3774
+ },
3775
+ async run({ prompt, trigger, label }) {
3776
+ try {
3777
+ const id = scheduler.add({ prompt, trigger, label });
3778
+ const job = scheduler.get(id);
3779
+ const next = scheduler.nextFire(job);
3780
+ return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}.`;
3781
+ } catch (e) {
3782
+ return `Error: ${e?.message ?? e}`;
3783
+ }
3784
+ }
3785
+ },
3786
+ {
3787
+ name: "ScheduleList",
3788
+ description: "List all scheduled jobs and their next fire time.",
3789
+ parameters: { type: "object", properties: {} },
3790
+ async run() {
3791
+ const jobs = scheduler.list();
3792
+ if (!jobs.length) return "(no scheduled jobs)";
3793
+ return jobs.map((j) => {
3794
+ const next = scheduler.nextFire(j);
3795
+ const trig = "at" in j.trigger ? `once @ ${new Date(j.trigger.at).toLocaleString()}` : "everyMs" in j.trigger ? `every ${(j.trigger.everyMs / 1e3).toFixed(0)}s` : `cron: ${j.trigger.cron}`;
3796
+ return `${j.id} ${j.status} ${trig} runs:${j.runs} next:${next ? new Date(next).toLocaleTimeString() : "\u2014"}${j.label ? " " + j.label : ""}`;
3797
+ }).join("\n");
3798
+ }
3799
+ },
3800
+ {
3801
+ name: "ScheduleCancel",
3802
+ description: "Cancel a scheduled job by id.",
3803
+ parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
3804
+ async run({ id }) {
3805
+ return scheduler.cancel(String(id)) ? `Cancelled ${id}.` : `Error: no scheduled job '${id}'. Use ScheduleList to see jobs.`;
3806
+ }
3807
+ },
3808
+ {
3809
+ name: "Wakeup",
3810
+ description: 'Self-pacing: schedule a one-off re-invocation of the agent after a delay.\nUse this to resume work later in the session (e.g. "check back in 5 minutes").\nThe prompt fires once, while the session is alive. Equivalent to ScheduleTask with {at: now + delayMs}.',
3811
+ parameters: {
3812
+ type: "object",
3813
+ required: ["delayMs", "prompt"],
3814
+ properties: {
3815
+ delayMs: { type: "number", description: "Delay in milliseconds (minimum 5000)." },
3816
+ prompt: { type: "string", description: "The prompt to inject when waking up." },
3817
+ label: { type: "string" }
3818
+ }
3819
+ },
3820
+ async run({ delayMs, prompt, label }) {
3821
+ const delay = Math.max(5e3, Number(delayMs) || 5e3);
3822
+ try {
3823
+ const id = scheduler.add({ prompt, trigger: { at: Date.now() + delay }, label: label ?? "wakeup" });
3824
+ return `Wakeup ${id} in ${(delay / 1e3).toFixed(0)}s.`;
3825
+ } catch (e) {
3826
+ return `Error: ${e?.message ?? e}`;
3827
+ }
3828
+ }
3829
+ }
3830
+ ];
3831
+ }
3832
+
3586
3833
  // src/presets.ts
3587
3834
  init_tools();
3588
3835
  import { MemFilesystem } from "@livx.cc/wcli/core";
@@ -5380,6 +5627,7 @@ export {
5380
5627
  SCRATCH_DIR,
5381
5628
  STT_SAMPLE_RATE,
5382
5629
  SandboxJobRegistry,
5630
+ Scheduler,
5383
5631
  Scratch,
5384
5632
  ScriptedHostBridge,
5385
5633
  SonioxSTT,
@@ -5398,6 +5646,7 @@ export {
5398
5646
  compileSynthesizedTool,
5399
5647
  composeHooks,
5400
5648
  contentText,
5649
+ cronMatches,
5401
5650
  decodeDdgUrl,
5402
5651
  defaultTools,
5403
5652
  diskAgentOptions,
@@ -5425,6 +5674,7 @@ export {
5425
5674
  makeLazyMcpToolSearch,
5426
5675
  makeMcpToolSearch,
5427
5676
  makeMcpToolSearchFromMounted,
5677
+ makeScheduleTools,
5428
5678
  makeTaskBatchTool,
5429
5679
  makeTaskTool,
5430
5680
  makeWebFetchTool,
@@ -5433,6 +5683,8 @@ export {
5433
5683
  mcpToolsToAgentTools,
5434
5684
  mkdirp,
5435
5685
  multiEditTool,
5686
+ nextCronAfter,
5687
+ parseCron,
5436
5688
  parseDdgHtml,
5437
5689
  planMode,
5438
5690
  raceAttempts,