@poncho-ai/harness 0.31.3 → 0.32.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.js CHANGED
@@ -1052,6 +1052,23 @@ The system auto-discovers all Telegram chats the bot has interacted with and sen
1052
1052
 
1053
1053
  The bot must have received at least one message from a user before it can send proactive messages to that chat (Telegram API requirement).
1054
1054
 
1055
+ #### One-off reminders
1056
+
1057
+ Unlike cron jobs (which are recurring and static), reminders are one-off and dynamic \u2014 created by the agent during conversations. Enable in \`poncho.config.js\`:
1058
+
1059
+ \`\`\`javascript
1060
+ export default {
1061
+ reminders: {
1062
+ enabled: true,
1063
+ pollSchedule: '*/10 * * * *',
1064
+ },
1065
+ };
1066
+ \`\`\`
1067
+
1068
+ When enabled, the agent gets \`set_reminder\`, \`list_reminders\`, and \`cancel_reminder\` tools. If the original conversation is on a messaging channel, the reminder fires as a reply in that conversation. Otherwise, a new \`[reminder]\` conversation is created.
1069
+
1070
+ Reminders are checked on a polling interval (configured by \`pollSchedule\`). Reminders due within the next poll window fire early rather than late.
1071
+
1055
1072
  ### Email (Resend)
1056
1073
 
1057
1074
  #### 1. Set up Resend
@@ -1592,6 +1609,12 @@ export default {
1592
1609
  // cdpUrl: 'wss://...', // Or connect via CDP URL (alternative to provider)
1593
1610
  // },
1594
1611
 
1612
+ // One-off reminders (enabled by default in new projects)
1613
+ reminders: {
1614
+ enabled: true,
1615
+ // pollSchedule: '*/10 * * * *', // how often to check for due reminders
1616
+ },
1617
+
1595
1618
  // Headless mode: disable the built-in Web UI (API-only)
1596
1619
  // webUi: false,
1597
1620
 
@@ -2110,9 +2133,9 @@ var ponchoDocsTool = defineTool({
2110
2133
 
2111
2134
  // src/harness.ts
2112
2135
  import { randomUUID as randomUUID3 } from "crypto";
2113
- import { readFile as readFile9 } from "fs/promises";
2114
- import { resolve as resolve11 } from "path";
2115
- import { defineTool as defineTool7, getTextContent as getTextContent2 } from "@poncho-ai/sdk";
2136
+ import { readFile as readFile10 } from "fs/promises";
2137
+ import { resolve as resolve12 } from "path";
2138
+ import { defineTool as defineTool8, getTextContent as getTextContent2 } from "@poncho-ai/sdk";
2116
2139
 
2117
2140
  // src/upload-store.ts
2118
2141
  import { createHash as createHash2 } from "crypto";
@@ -3178,6 +3201,379 @@ var createTodoTools = (store) => {
3178
3201
  ];
3179
3202
  };
3180
3203
 
3204
+ // src/reminder-store.ts
3205
+ import { mkdir as mkdir5, readFile as readFile7, rename as rename3, writeFile as writeFile6 } from "fs/promises";
3206
+ import { dirname as dirname4, resolve as resolve8 } from "path";
3207
+ var REMINDERS_FILE = "reminders.json";
3208
+ var STALE_CANCELLED_MS = 7 * 24 * 60 * 60 * 1e3;
3209
+ var writeJsonAtomic3 = async (filePath, payload) => {
3210
+ await mkdir5(dirname4(filePath), { recursive: true });
3211
+ const tmpPath = `${filePath}.tmp`;
3212
+ await writeFile6(tmpPath, JSON.stringify(payload, null, 2), "utf8");
3213
+ await rename3(tmpPath, filePath);
3214
+ };
3215
+ var isValidReminder = (item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.task === "string" && typeof item.scheduledAt === "number" && typeof item.status === "string";
3216
+ var parseReminderList = (raw) => {
3217
+ if (!Array.isArray(raw)) return [];
3218
+ return raw.filter(isValidReminder);
3219
+ };
3220
+ var pruneStale = (reminders) => {
3221
+ const cutoff = Date.now() - STALE_CANCELLED_MS;
3222
+ return reminders.filter(
3223
+ (r) => r.status === "pending" || r.status === "cancelled" && r.createdAt > cutoff
3224
+ );
3225
+ };
3226
+ var generateId2 = () => (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`).slice(0, 8);
3227
+ var InMemoryReminderStore = class {
3228
+ reminders = [];
3229
+ async list() {
3230
+ this.reminders = pruneStale(this.reminders);
3231
+ return [...this.reminders];
3232
+ }
3233
+ async create(input) {
3234
+ const reminder = {
3235
+ id: generateId2(),
3236
+ task: input.task,
3237
+ scheduledAt: input.scheduledAt,
3238
+ timezone: input.timezone,
3239
+ status: "pending",
3240
+ createdAt: Date.now(),
3241
+ conversationId: input.conversationId,
3242
+ ownerId: input.ownerId
3243
+ };
3244
+ this.reminders = pruneStale(this.reminders);
3245
+ this.reminders.push(reminder);
3246
+ return reminder;
3247
+ }
3248
+ async cancel(id) {
3249
+ const reminder = this.reminders.find((r) => r.id === id);
3250
+ if (!reminder) throw new Error(`Reminder "${id}" not found`);
3251
+ if (reminder.status !== "pending") {
3252
+ throw new Error(`Reminder "${id}" is already ${reminder.status}`);
3253
+ }
3254
+ reminder.status = "cancelled";
3255
+ return reminder;
3256
+ }
3257
+ async delete(id) {
3258
+ this.reminders = this.reminders.filter((r) => r.id !== id);
3259
+ }
3260
+ };
3261
+ var FileReminderStore = class {
3262
+ workingDir;
3263
+ filePath = "";
3264
+ constructor(workingDir) {
3265
+ this.workingDir = workingDir;
3266
+ }
3267
+ async ensureFilePath() {
3268
+ if (this.filePath) return this.filePath;
3269
+ const identity = await ensureAgentIdentity(this.workingDir);
3270
+ this.filePath = resolve8(getAgentStoreDirectory(identity), REMINDERS_FILE);
3271
+ return this.filePath;
3272
+ }
3273
+ async readAll() {
3274
+ try {
3275
+ const fp = await this.ensureFilePath();
3276
+ const raw = await readFile7(fp, "utf8");
3277
+ return parseReminderList(JSON.parse(raw));
3278
+ } catch {
3279
+ return [];
3280
+ }
3281
+ }
3282
+ async writeAll(reminders) {
3283
+ const fp = await this.ensureFilePath();
3284
+ await writeJsonAtomic3(fp, reminders);
3285
+ }
3286
+ async list() {
3287
+ const all = await this.readAll();
3288
+ const pruned = pruneStale(all);
3289
+ if (pruned.length !== all.length) await this.writeAll(pruned);
3290
+ return pruned;
3291
+ }
3292
+ async create(input) {
3293
+ const reminder = {
3294
+ id: generateId2(),
3295
+ task: input.task,
3296
+ scheduledAt: input.scheduledAt,
3297
+ timezone: input.timezone,
3298
+ status: "pending",
3299
+ createdAt: Date.now(),
3300
+ conversationId: input.conversationId,
3301
+ ownerId: input.ownerId
3302
+ };
3303
+ let reminders = await this.readAll();
3304
+ reminders = pruneStale(reminders);
3305
+ reminders.push(reminder);
3306
+ await this.writeAll(reminders);
3307
+ return reminder;
3308
+ }
3309
+ async cancel(id) {
3310
+ const reminders = await this.readAll();
3311
+ const reminder = reminders.find((r) => r.id === id);
3312
+ if (!reminder) throw new Error(`Reminder "${id}" not found`);
3313
+ if (reminder.status !== "pending") {
3314
+ throw new Error(`Reminder "${id}" is already ${reminder.status}`);
3315
+ }
3316
+ reminder.status = "cancelled";
3317
+ await this.writeAll(reminders);
3318
+ return reminder;
3319
+ }
3320
+ async delete(id) {
3321
+ const reminders = await this.readAll();
3322
+ await this.writeAll(reminders.filter((r) => r.id !== id));
3323
+ }
3324
+ };
3325
+ var KVBackedReminderStore = class {
3326
+ kv;
3327
+ key;
3328
+ ttl;
3329
+ memoryFallback = new InMemoryReminderStore();
3330
+ constructor(kv, key, ttl) {
3331
+ this.kv = kv;
3332
+ this.key = key;
3333
+ this.ttl = ttl;
3334
+ }
3335
+ async readAll() {
3336
+ try {
3337
+ const raw = await this.kv.get(this.key);
3338
+ if (!raw) return [];
3339
+ return parseReminderList(JSON.parse(raw));
3340
+ } catch {
3341
+ return this.memoryFallback.list();
3342
+ }
3343
+ }
3344
+ async writeAll(reminders) {
3345
+ try {
3346
+ const serialized = JSON.stringify(reminders);
3347
+ if (typeof this.ttl === "number") {
3348
+ await this.kv.setWithTtl(this.key, serialized, Math.max(1, this.ttl));
3349
+ } else {
3350
+ await this.kv.set(this.key, serialized);
3351
+ }
3352
+ } catch {
3353
+ }
3354
+ }
3355
+ async list() {
3356
+ const all = await this.readAll();
3357
+ const pruned = pruneStale(all);
3358
+ if (pruned.length !== all.length) await this.writeAll(pruned);
3359
+ return pruned;
3360
+ }
3361
+ async create(input) {
3362
+ let reminders;
3363
+ try {
3364
+ reminders = await this.readAll();
3365
+ } catch {
3366
+ return this.memoryFallback.create(input);
3367
+ }
3368
+ const reminder = {
3369
+ id: generateId2(),
3370
+ task: input.task,
3371
+ scheduledAt: input.scheduledAt,
3372
+ timezone: input.timezone,
3373
+ status: "pending",
3374
+ createdAt: Date.now(),
3375
+ conversationId: input.conversationId,
3376
+ ownerId: input.ownerId
3377
+ };
3378
+ reminders = pruneStale(reminders);
3379
+ reminders.push(reminder);
3380
+ await this.writeAll(reminders);
3381
+ return reminder;
3382
+ }
3383
+ async cancel(id) {
3384
+ let reminders;
3385
+ try {
3386
+ reminders = await this.readAll();
3387
+ } catch {
3388
+ return this.memoryFallback.cancel(id);
3389
+ }
3390
+ const reminder = reminders.find((r) => r.id === id);
3391
+ if (!reminder) throw new Error(`Reminder "${id}" not found`);
3392
+ if (reminder.status !== "pending") {
3393
+ throw new Error(`Reminder "${id}" is already ${reminder.status}`);
3394
+ }
3395
+ reminder.status = "cancelled";
3396
+ await this.writeAll(reminders);
3397
+ return reminder;
3398
+ }
3399
+ async delete(id) {
3400
+ let reminders;
3401
+ try {
3402
+ reminders = await this.readAll();
3403
+ } catch {
3404
+ return this.memoryFallback.delete(id);
3405
+ }
3406
+ await this.writeAll(reminders.filter((r) => r.id !== id));
3407
+ }
3408
+ };
3409
+ var createReminderStore = (agentId, config, options) => {
3410
+ const provider = config?.provider ?? "local";
3411
+ const ttl = config?.ttl;
3412
+ const workingDir = options?.workingDir ?? process.cwd();
3413
+ if (provider === "local") {
3414
+ return new FileReminderStore(workingDir);
3415
+ }
3416
+ if (provider === "memory") {
3417
+ return new InMemoryReminderStore();
3418
+ }
3419
+ const kv = createRawKVStore(config);
3420
+ if (kv) {
3421
+ const key = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:reminders`;
3422
+ return new KVBackedReminderStore(kv, key, ttl);
3423
+ }
3424
+ return new InMemoryReminderStore();
3425
+ };
3426
+
3427
+ // src/reminder-tools.ts
3428
+ import { defineTool as defineTool4 } from "@poncho-ai/sdk";
3429
+ var VALID_STATUSES2 = ["pending", "cancelled"];
3430
+ var createReminderTools = (store) => [
3431
+ defineTool4({
3432
+ name: "set_reminder",
3433
+ description: "Set a one-time reminder that will fire at the specified date and time. Use this when the user asks to be reminded about something. The datetime must be an ISO 8601 string in the future. When the reminder fires, the task message will be delivered to the user.",
3434
+ inputSchema: {
3435
+ type: "object",
3436
+ properties: {
3437
+ task: {
3438
+ type: "string",
3439
+ description: "What to remind about"
3440
+ },
3441
+ datetime: {
3442
+ type: "string",
3443
+ description: "ISO 8601 datetime for when the reminder should fire (e.g. '2026-03-23T09:00:00Z')"
3444
+ },
3445
+ timezone: {
3446
+ type: "string",
3447
+ description: "IANA timezone for interpreting the datetime if it lacks an offset (e.g. 'America/New_York'). Defaults to UTC."
3448
+ }
3449
+ },
3450
+ required: ["task", "datetime"],
3451
+ additionalProperties: false
3452
+ },
3453
+ handler: async (input, context) => {
3454
+ const task = typeof input.task === "string" ? input.task.trim() : "";
3455
+ if (!task) throw new Error("task is required");
3456
+ const datetimeStr = typeof input.datetime === "string" ? input.datetime.trim() : "";
3457
+ if (!datetimeStr) throw new Error("datetime is required");
3458
+ const timezone = typeof input.timezone === "string" ? input.timezone.trim() : void 0;
3459
+ let scheduledAt;
3460
+ if (timezone && !datetimeStr.includes("Z") && !/[+-]\d{2}:\d{2}$/.test(datetimeStr)) {
3461
+ try {
3462
+ const formatted = new Intl.DateTimeFormat("en-US", {
3463
+ timeZone: timezone,
3464
+ year: "numeric",
3465
+ month: "2-digit",
3466
+ day: "2-digit",
3467
+ hour: "2-digit",
3468
+ minute: "2-digit",
3469
+ second: "2-digit",
3470
+ hour12: false
3471
+ }).format(/* @__PURE__ */ new Date());
3472
+ void formatted;
3473
+ } catch {
3474
+ throw new Error(`Invalid timezone: "${timezone}"`);
3475
+ }
3476
+ const baseDate = new Date(datetimeStr);
3477
+ if (isNaN(baseDate.getTime())) {
3478
+ throw new Error(`Invalid datetime: "${datetimeStr}"`);
3479
+ }
3480
+ const utcStr = baseDate.toLocaleString("en-US", { timeZone: "UTC" });
3481
+ const tzStr = baseDate.toLocaleString("en-US", { timeZone: timezone });
3482
+ const offsetMs = new Date(utcStr).getTime() - new Date(tzStr).getTime();
3483
+ scheduledAt = baseDate.getTime() + offsetMs;
3484
+ } else {
3485
+ const parsed = new Date(datetimeStr);
3486
+ if (isNaN(parsed.getTime())) {
3487
+ throw new Error(`Invalid datetime: "${datetimeStr}"`);
3488
+ }
3489
+ scheduledAt = parsed.getTime();
3490
+ }
3491
+ if (scheduledAt <= Date.now()) {
3492
+ throw new Error("Reminder datetime must be in the future");
3493
+ }
3494
+ const conversationId = context.conversationId || context.runId;
3495
+ const reminder = await store.create({
3496
+ task,
3497
+ scheduledAt,
3498
+ timezone,
3499
+ conversationId
3500
+ });
3501
+ return {
3502
+ ok: true,
3503
+ reminder: {
3504
+ id: reminder.id,
3505
+ task: reminder.task,
3506
+ scheduledAt: new Date(reminder.scheduledAt).toISOString(),
3507
+ timezone: reminder.timezone ?? "UTC",
3508
+ status: reminder.status
3509
+ }
3510
+ };
3511
+ }
3512
+ }),
3513
+ defineTool4({
3514
+ name: "list_reminders",
3515
+ description: "List reminders for this agent. Returns all reminders by default; use the status filter to show only pending or cancelled ones. Fired reminders are automatically deleted after delivery.",
3516
+ inputSchema: {
3517
+ type: "object",
3518
+ properties: {
3519
+ status: {
3520
+ type: "string",
3521
+ enum: VALID_STATUSES2,
3522
+ description: "Filter by status (omit to list all)"
3523
+ }
3524
+ },
3525
+ additionalProperties: false
3526
+ },
3527
+ handler: async (input) => {
3528
+ let reminders = await store.list();
3529
+ const status = typeof input.status === "string" ? input.status : void 0;
3530
+ if (status && VALID_STATUSES2.includes(status)) {
3531
+ reminders = reminders.filter((r) => r.status === status);
3532
+ }
3533
+ return {
3534
+ reminders: reminders.map((r) => ({
3535
+ id: r.id,
3536
+ task: r.task,
3537
+ scheduledAt: new Date(r.scheduledAt).toISOString(),
3538
+ timezone: r.timezone ?? "UTC",
3539
+ status: r.status,
3540
+ createdAt: new Date(r.createdAt).toISOString()
3541
+ })),
3542
+ count: reminders.length
3543
+ };
3544
+ }
3545
+ }),
3546
+ defineTool4({
3547
+ name: "cancel_reminder",
3548
+ description: "Cancel a pending reminder by its ID.",
3549
+ inputSchema: {
3550
+ type: "object",
3551
+ properties: {
3552
+ id: {
3553
+ type: "string",
3554
+ description: "ID of the reminder to cancel"
3555
+ }
3556
+ },
3557
+ required: ["id"],
3558
+ additionalProperties: false
3559
+ },
3560
+ handler: async (input) => {
3561
+ const id = typeof input.id === "string" ? input.id.trim() : "";
3562
+ if (!id) throw new Error("id is required");
3563
+ const cancelled = await store.cancel(id);
3564
+ return {
3565
+ ok: true,
3566
+ reminder: {
3567
+ id: cancelled.id,
3568
+ task: cancelled.task,
3569
+ scheduledAt: new Date(cancelled.scheduledAt).toISOString(),
3570
+ status: cancelled.status
3571
+ }
3572
+ };
3573
+ }
3574
+ })
3575
+ ];
3576
+
3181
3577
  // src/mcp.ts
3182
3578
  var McpHttpError = class extends Error {
3183
3579
  status;
@@ -3621,8 +4017,8 @@ import { createAnthropic } from "@ai-sdk/anthropic";
3621
4017
 
3622
4018
  // src/openai-codex-auth.ts
3623
4019
  import { homedir as homedir3 } from "os";
3624
- import { dirname as dirname4, resolve as resolve8 } from "path";
3625
- import { mkdir as mkdir5, readFile as readFile7, chmod, writeFile as writeFile6, rm as rm3 } from "fs/promises";
4020
+ import { dirname as dirname5, resolve as resolve9 } from "path";
4021
+ import { mkdir as mkdir6, readFile as readFile8, chmod, writeFile as writeFile7, rm as rm3 } from "fs/promises";
3626
4022
  var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
3627
4023
  var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
3628
4024
  var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
@@ -3654,14 +4050,14 @@ var getOpenAICodexAuthFilePath = (config) => {
3654
4050
  const env = defaultedConfig(config);
3655
4051
  const fromEnv = process.env[env.authFilePathEnv];
3656
4052
  if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
3657
- return resolve8(fromEnv);
4053
+ return resolve9(fromEnv);
3658
4054
  }
3659
- return resolve8(homedir3(), ".poncho", "auth", "openai-codex.json");
4055
+ return resolve9(homedir3(), ".poncho", "auth", "openai-codex.json");
3660
4056
  };
3661
4057
  var readOpenAICodexSession = async (config) => {
3662
4058
  const filePath = getOpenAICodexAuthFilePath(config);
3663
4059
  try {
3664
- const content = await readFile7(filePath, "utf8");
4060
+ const content = await readFile8(filePath, "utf8");
3665
4061
  const parsed = JSON.parse(content);
3666
4062
  if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
3667
4063
  return void 0;
@@ -3678,12 +4074,12 @@ var readOpenAICodexSession = async (config) => {
3678
4074
  };
3679
4075
  var writeOpenAICodexSession = async (session, config) => {
3680
4076
  const filePath = getOpenAICodexAuthFilePath(config);
3681
- await mkdir5(dirname4(filePath), { recursive: true });
4077
+ await mkdir6(dirname5(filePath), { recursive: true });
3682
4078
  const payload = {
3683
4079
  ...session,
3684
4080
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3685
4081
  };
3686
- await writeFile6(filePath, `${JSON.stringify(payload, null, 2)}
4082
+ await writeFile7(filePath, `${JSON.stringify(payload, null, 2)}
3687
4083
  `, "utf8");
3688
4084
  await chmod(filePath, 384);
3689
4085
  };
@@ -3996,8 +4392,8 @@ var createModelProvider = (provider, config) => {
3996
4392
  };
3997
4393
 
3998
4394
  // src/skill-context.ts
3999
- import { readFile as readFile8, readdir as readdir2, stat } from "fs/promises";
4000
- import { dirname as dirname5, resolve as resolve9, normalize } from "path";
4395
+ import { readFile as readFile9, readdir as readdir2, stat } from "fs/promises";
4396
+ import { dirname as dirname6, resolve as resolve10, normalize } from "path";
4001
4397
  import YAML3 from "yaml";
4002
4398
  var DEFAULT_SKILL_DIRS = ["skills"];
4003
4399
  var resolveSkillDirs = (workingDir, extraPaths) => {
@@ -4009,7 +4405,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
4009
4405
  }
4010
4406
  }
4011
4407
  }
4012
- return dirs.map((d) => resolve9(workingDir, d));
4408
+ return dirs.map((d) => resolve10(workingDir, d));
4013
4409
  };
4014
4410
  var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
4015
4411
  var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
@@ -4078,7 +4474,7 @@ var collectSkillManifests = async (directory) => {
4078
4474
  const entries = await readdir2(directory, { withFileTypes: true });
4079
4475
  const files = [];
4080
4476
  for (const entry of entries) {
4081
- const fullPath = resolve9(directory, entry.name);
4477
+ const fullPath = resolve10(directory, entry.name);
4082
4478
  let isDir = entry.isDirectory();
4083
4479
  let isFile = entry.isFile();
4084
4480
  if (entry.isSymbolicLink()) {
@@ -4113,13 +4509,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
4113
4509
  const seen = /* @__PURE__ */ new Set();
4114
4510
  for (const manifest of allManifests) {
4115
4511
  try {
4116
- const content = await readFile8(manifest, "utf8");
4512
+ const content = await readFile9(manifest, "utf8");
4117
4513
  const parsed = parseSkillFrontmatter(content);
4118
4514
  if (parsed && !seen.has(parsed.name)) {
4119
4515
  seen.add(parsed.name);
4120
4516
  skills.push({
4121
4517
  ...parsed,
4122
- skillDir: dirname5(manifest),
4518
+ skillDir: dirname6(manifest),
4123
4519
  skillPath: manifest
4124
4520
  });
4125
4521
  }
@@ -4158,7 +4554,7 @@ ${xmlSkills}
4158
4554
  };
4159
4555
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
4160
4556
  var loadSkillInstructions = async (skill) => {
4161
- const content = await readFile8(skill.skillPath, "utf8");
4557
+ const content = await readFile9(skill.skillPath, "utf8");
4162
4558
  const match = content.match(FRONTMATTER_PATTERN3);
4163
4559
  return match ? match[2].trim() : content.trim();
4164
4560
  };
@@ -4167,11 +4563,11 @@ var readSkillResource = async (skill, relativePath) => {
4167
4563
  if (normalized.startsWith("..") || normalized.startsWith("/")) {
4168
4564
  throw new Error("Path must be relative and within the skill directory");
4169
4565
  }
4170
- const fullPath = resolve9(skill.skillDir, normalized);
4566
+ const fullPath = resolve10(skill.skillDir, normalized);
4171
4567
  if (!fullPath.startsWith(skill.skillDir)) {
4172
4568
  throw new Error("Path escapes the skill directory");
4173
4569
  }
4174
- return await readFile8(fullPath, "utf8");
4570
+ return await readFile9(fullPath, "utf8");
4175
4571
  };
4176
4572
  var MAX_INSTRUCTIONS_PER_SKILL = 1200;
4177
4573
  var loadSkillContext = async (workingDir) => {
@@ -4288,16 +4684,16 @@ function convertSchema(schema) {
4288
4684
  }
4289
4685
 
4290
4686
  // src/skill-tools.ts
4291
- import { defineTool as defineTool4 } from "@poncho-ai/sdk";
4687
+ import { defineTool as defineTool5 } from "@poncho-ai/sdk";
4292
4688
  import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
4293
- import { extname, normalize as normalize2, resolve as resolve10, sep as sep2 } from "path";
4689
+ import { extname, normalize as normalize2, resolve as resolve11, sep as sep2 } from "path";
4294
4690
  import { pathToFileURL } from "url";
4295
4691
  import { createJiti as createJiti2 } from "jiti";
4296
4692
  var createSkillTools = (skills, options) => {
4297
4693
  const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
4298
4694
  const knownNames = skills.length > 0 ? skills.map((skill) => skill.name).join(", ") : "(none)";
4299
4695
  return [
4300
- defineTool4({
4696
+ defineTool5({
4301
4697
  name: "activate_skill",
4302
4698
  description: `Load the full instructions for an available skill. Use this when a user's request matches a skill's description. Available skills: ${knownNames}`,
4303
4699
  inputSchema: {
@@ -4334,7 +4730,7 @@ var createSkillTools = (skills, options) => {
4334
4730
  }
4335
4731
  }
4336
4732
  }),
4337
- defineTool4({
4733
+ defineTool5({
4338
4734
  name: "deactivate_skill",
4339
4735
  description: "Deactivate a previously activated skill and update scoped tool availability.",
4340
4736
  inputSchema: {
@@ -4363,7 +4759,7 @@ var createSkillTools = (skills, options) => {
4363
4759
  }
4364
4760
  }
4365
4761
  }),
4366
- defineTool4({
4762
+ defineTool5({
4367
4763
  name: "list_active_skills",
4368
4764
  description: "List currently active skills with scoped MCP tools.",
4369
4765
  inputSchema: {
@@ -4375,7 +4771,7 @@ var createSkillTools = (skills, options) => {
4375
4771
  activeSkills: options?.onListActiveSkills ? options.onListActiveSkills() : []
4376
4772
  })
4377
4773
  }),
4378
- defineTool4({
4774
+ defineTool5({
4379
4775
  name: "read_skill_resource",
4380
4776
  description: `Read a file from a skill's directory (references, scripts, assets). Use relative paths from the skill root. Available skills: ${knownNames}`,
4381
4777
  inputSchema: {
@@ -4415,7 +4811,7 @@ var createSkillTools = (skills, options) => {
4415
4811
  }
4416
4812
  }
4417
4813
  }),
4418
- defineTool4({
4814
+ defineTool5({
4419
4815
  name: "list_skill_scripts",
4420
4816
  description: `List JavaScript/TypeScript script files available under a skill directory (recursive). Available skills: ${knownNames}`,
4421
4817
  inputSchema: {
@@ -4450,7 +4846,7 @@ var createSkillTools = (skills, options) => {
4450
4846
  }
4451
4847
  }
4452
4848
  }),
4453
- defineTool4({
4849
+ defineTool5({
4454
4850
  name: "run_skill_script",
4455
4851
  description: `Run a JavaScript/TypeScript module in a skill or project directory. Uses default export function or named run/main/handler function. Available skills: ${knownNames}`,
4456
4852
  inputSchema: {
@@ -4548,7 +4944,7 @@ var collectScriptFiles = async (directory) => {
4548
4944
  if (entry.name === "node_modules") {
4549
4945
  continue;
4550
4946
  }
4551
- const fullPath = resolve10(directory, entry.name);
4947
+ const fullPath = resolve11(directory, entry.name);
4552
4948
  let isDir = entry.isDirectory();
4553
4949
  let isFile = entry.isFile();
4554
4950
  if (entry.isSymbolicLink()) {
@@ -4587,8 +4983,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
4587
4983
  };
4588
4984
  var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
4589
4985
  const normalized = normalizeScriptPolicyPath(relativePath);
4590
- const fullPath = resolve10(baseDir, normalized);
4591
- const boundary = resolve10(containmentDir ?? baseDir);
4986
+ const fullPath = resolve11(baseDir, normalized);
4987
+ const boundary = resolve11(containmentDir ?? baseDir);
4592
4988
  if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
4593
4989
  throw new Error("Script path must stay inside the allowed directory");
4594
4990
  }
@@ -4665,7 +5061,7 @@ var extractRunnableFunction = (value) => {
4665
5061
 
4666
5062
  // src/search-tools.ts
4667
5063
  import { load as cheerioLoad } from "cheerio";
4668
- import { defineTool as defineTool5 } from "@poncho-ai/sdk";
5064
+ import { defineTool as defineTool6 } from "@poncho-ai/sdk";
4669
5065
  var SEARCH_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
4670
5066
  var FETCH_TIMEOUT_MS = 15e3;
4671
5067
  var SEARCH_MAX_RETRIES = 4;
@@ -4694,8 +5090,8 @@ function applyRateLimitCooldown(retryAfterHeader) {
4694
5090
  async function runWithSearchThrottle(fn) {
4695
5091
  const previous = searchQueue;
4696
5092
  let release;
4697
- searchQueue = new Promise((resolve13) => {
4698
- release = resolve13;
5093
+ searchQueue = new Promise((resolve14) => {
5094
+ release = resolve14;
4699
5095
  });
4700
5096
  await previous.catch(() => {
4701
5097
  });
@@ -4787,7 +5183,7 @@ function extractReadableText($, maxLength) {
4787
5183
  return { title, content };
4788
5184
  }
4789
5185
  var createSearchTools = () => [
4790
- defineTool5({
5186
+ defineTool6({
4791
5187
  name: "web_search",
4792
5188
  description: "Search the web and return a list of results (title, URL, snippet). Use this instead of opening a browser when you need to find information online.",
4793
5189
  inputSchema: {
@@ -4826,7 +5222,7 @@ var createSearchTools = () => [
4826
5222
  }
4827
5223
  }
4828
5224
  }),
4829
- defineTool5({
5225
+ defineTool6({
4830
5226
  name: "web_fetch",
4831
5227
  description: "Fetch a web page and return its text content (HTML tags stripped). Useful for reading articles, documentation, or any web page without opening a browser.",
4832
5228
  inputSchema: {
@@ -4872,9 +5268,9 @@ var createSearchTools = () => [
4872
5268
  ];
4873
5269
 
4874
5270
  // src/subagent-tools.ts
4875
- import { defineTool as defineTool6 } from "@poncho-ai/sdk";
5271
+ import { defineTool as defineTool7 } from "@poncho-ai/sdk";
4876
5272
  var createSubagentTools = (manager) => [
4877
- defineTool6({
5273
+ defineTool7({
4878
5274
  name: "spawn_subagent",
4879
5275
  description: "Spawn a subagent to work on a task in the background. Returns immediately with a subagent ID. The subagent runs independently and its result will be delivered to you as a message in the conversation when it completes.\n\nGuidelines:\n- Spawn all needed subagents in a SINGLE response (they run concurrently), then end your turn with a brief message to the user.\n- Do NOT spawn more subagents in follow-up steps. Wait for results to be delivered before deciding if more work is needed.\n- Prefer doing work yourself for simple or quick tasks. Spawn subagents for substantial, self-contained work.\n- The subagent has no memory of your conversation -- write thorough, self-contained instructions in the task.",
4880
5276
  inputSchema: {
@@ -4906,7 +5302,7 @@ var createSubagentTools = (manager) => [
4906
5302
  return { subagentId, status: "running" };
4907
5303
  }
4908
5304
  }),
4909
- defineTool6({
5305
+ defineTool7({
4910
5306
  name: "message_subagent",
4911
5307
  description: "Send a follow-up message to a completed or stopped subagent. The subagent restarts in the background and its result will be delivered to you as a message when it completes. Only works when the subagent is not currently running.",
4912
5308
  inputSchema: {
@@ -4934,7 +5330,7 @@ var createSubagentTools = (manager) => [
4934
5330
  return { subagentId: id, status: "running" };
4935
5331
  }
4936
5332
  }),
4937
- defineTool6({
5333
+ defineTool7({
4938
5334
  name: "stop_subagent",
4939
5335
  description: "Stop a running subagent. The subagent's conversation is preserved but it will stop processing. Use this to cancel work that is no longer needed.",
4940
5336
  inputSchema: {
@@ -4957,7 +5353,7 @@ var createSubagentTools = (manager) => [
4957
5353
  return { message: `Subagent "${subagentId}" has been stopped.` };
4958
5354
  }
4959
5355
  }),
4960
- defineTool6({
5356
+ defineTool7({
4961
5357
  name: "list_subagents",
4962
5358
  description: "List all subagents that have been spawned in this conversation. Returns each subagent's ID, original task, current status, and message count. Use this to look up subagent IDs before calling message_subagent or stop_subagent.",
4963
5359
  inputSchema: {
@@ -5632,6 +6028,7 @@ var AgentHarness = class _AgentHarness {
5632
6028
  skillContextWindow = "";
5633
6029
  memoryStore;
5634
6030
  todoStore;
6031
+ reminderStore;
5635
6032
  loadedConfig;
5636
6033
  loadedSkills = [];
5637
6034
  skillFingerprint = "";
@@ -5726,7 +6123,7 @@ var AgentHarness = class _AgentHarness {
5726
6123
  }
5727
6124
  }
5728
6125
  createGetToolResultByIdTool() {
5729
- return defineTool7({
6126
+ return defineTool8({
5730
6127
  name: "get_tool_result_by_id",
5731
6128
  description: "Retrieve a previously archived full tool result by id for the current conversation. Use this when older tool outputs were truncated in prompt history.",
5732
6129
  inputSchema: {
@@ -6118,8 +6515,8 @@ var AgentHarness = class _AgentHarness {
6118
6515
  return false;
6119
6516
  }
6120
6517
  try {
6121
- const agentFilePath = resolve11(this.workingDir, "AGENT.md");
6122
- const rawContent = await readFile9(agentFilePath, "utf8");
6518
+ const agentFilePath = resolve12(this.workingDir, "AGENT.md");
6519
+ const rawContent = await readFile10(agentFilePath, "utf8");
6123
6520
  if (rawContent === this.agentFileFingerprint) {
6124
6521
  return false;
6125
6522
  }
@@ -6188,8 +6585,8 @@ var AgentHarness = class _AgentHarness {
6188
6585
  }
6189
6586
  }
6190
6587
  async initialize() {
6191
- const agentFilePath = resolve11(this.workingDir, "AGENT.md");
6192
- const agentRawContent = await readFile9(agentFilePath, "utf8");
6588
+ const agentFilePath = resolve12(this.workingDir, "AGENT.md");
6589
+ const agentRawContent = await readFile10(agentFilePath, "utf8");
6193
6590
  this.parsedAgent = parseAgentMarkdown(agentRawContent);
6194
6591
  this.agentFileFingerprint = agentRawContent;
6195
6592
  const identity = await ensureAgentIdentity(this.workingDir);
@@ -6232,6 +6629,14 @@ var AgentHarness = class _AgentHarness {
6232
6629
  this.registerIfMissing(tool);
6233
6630
  }
6234
6631
  }
6632
+ if (config?.reminders?.enabled) {
6633
+ this.reminderStore = createReminderStore(agentId, stateConfig, { workingDir: this.workingDir });
6634
+ for (const tool of createReminderTools(this.reminderStore)) {
6635
+ if (this.isToolEnabled(tool.name)) {
6636
+ this.registerIfMissing(tool);
6637
+ }
6638
+ }
6639
+ }
6235
6640
  if (config?.browser) {
6236
6641
  await this.initBrowserTools(config).catch((e) => {
6237
6642
  console.warn(
@@ -6296,14 +6701,14 @@ var AgentHarness = class _AgentHarness {
6296
6701
  const filePath = pathResolve(stateDir, `${sessionId}.json`);
6297
6702
  return {
6298
6703
  async save(json) {
6299
- const { mkdir: mkdir7, writeFile: writeFile8 } = await import("fs/promises");
6300
- await mkdir7(stateDir, { recursive: true });
6301
- await writeFile8(filePath, json, "utf8");
6704
+ const { mkdir: mkdir8, writeFile: writeFile9 } = await import("fs/promises");
6705
+ await mkdir8(stateDir, { recursive: true });
6706
+ await writeFile9(filePath, json, "utf8");
6302
6707
  },
6303
6708
  async load() {
6304
- const { readFile: readFile11 } = await import("fs/promises");
6709
+ const { readFile: readFile12 } = await import("fs/promises");
6305
6710
  try {
6306
- return await readFile11(filePath, "utf8");
6711
+ return await readFile12(filePath, "utf8");
6307
6712
  } catch {
6308
6713
  return void 0;
6309
6714
  }
@@ -6369,7 +6774,7 @@ var AgentHarness = class _AgentHarness {
6369
6774
  let browserMod;
6370
6775
  try {
6371
6776
  const { existsSync } = await import("fs");
6372
- const { join, dirname: dirname7 } = await import("path");
6777
+ const { join, dirname: dirname8 } = await import("path");
6373
6778
  const { pathToFileURL: pathToFileURL2 } = await import("url");
6374
6779
  let searchDir = this.workingDir;
6375
6780
  let entryPath;
@@ -6379,7 +6784,7 @@ var AgentHarness = class _AgentHarness {
6379
6784
  entryPath = candidate;
6380
6785
  break;
6381
6786
  }
6382
- const parent = dirname7(searchDir);
6787
+ const parent = dirname8(searchDir);
6383
6788
  if (parent === searchDir) break;
6384
6789
  searchDir = parent;
6385
6790
  }
@@ -6484,9 +6889,9 @@ var AgentHarness = class _AgentHarness {
6484
6889
  for await (const event of this.run(input)) {
6485
6890
  eventQueue.push(event);
6486
6891
  if (queueResolve) {
6487
- const resolve13 = queueResolve;
6892
+ const resolve14 = queueResolve;
6488
6893
  queueResolve = null;
6489
- resolve13();
6894
+ resolve14();
6490
6895
  }
6491
6896
  }
6492
6897
  } catch (error) {
@@ -6505,8 +6910,8 @@ var AgentHarness = class _AgentHarness {
6505
6910
  if (eventQueue.length > 0) {
6506
6911
  yield eventQueue.shift();
6507
6912
  } else if (!generatorDone) {
6508
- await new Promise((resolve13) => {
6509
- queueResolve = resolve13;
6913
+ await new Promise((resolve14) => {
6914
+ queueResolve = resolve14;
6510
6915
  });
6511
6916
  }
6512
6917
  }
@@ -6653,7 +7058,10 @@ ${boundedMainMemory.trim()}` : "";
6653
7058
  const promptWithSkills = this.skillContextWindow ? `${agentPrompt}${developmentContext}
6654
7059
 
6655
7060
  ${this.skillContextWindow}${browserContext}` : `${agentPrompt}${developmentContext}${browserContext}`;
6656
- return `${promptWithSkills}${memoryContext}
7061
+ const timeContext = this.reminderStore ? `
7062
+
7063
+ Current UTC time: ${(/* @__PURE__ */ new Date()).toISOString()}` : "";
7064
+ return `${promptWithSkills}${memoryContext}${timeContext}
6657
7065
 
6658
7066
  ## Execution Integrity
6659
7067
 
@@ -7126,8 +7534,8 @@ ${textContent}` };
7126
7534
  let timer;
7127
7535
  nextPart = await Promise.race([
7128
7536
  fullStreamIterator.next(),
7129
- new Promise((resolve13) => {
7130
- timer = setTimeout(() => resolve13(null), effectiveTimeout);
7537
+ new Promise((resolve14) => {
7538
+ timer = setTimeout(() => resolve14(null), effectiveTimeout);
7131
7539
  })
7132
7540
  ]);
7133
7541
  clearTimeout(timer);
@@ -7462,7 +7870,7 @@ ${textContent}` };
7462
7870
  const raced = await Promise.race([
7463
7871
  this.dispatcher.executeBatch(approvedCalls, toolContext),
7464
7872
  new Promise(
7465
- (resolve13) => setTimeout(() => resolve13(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
7873
+ (resolve14) => setTimeout(() => resolve14(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
7466
7874
  )
7467
7875
  ]);
7468
7876
  if (raced === TOOL_DEADLINE_SENTINEL) {
@@ -7853,8 +8261,8 @@ var LatitudeCapture = class {
7853
8261
 
7854
8262
  // src/state.ts
7855
8263
  import { randomUUID as randomUUID4 } from "crypto";
7856
- import { mkdir as mkdir6, readFile as readFile10, readdir as readdir4, rename as rename3, rm as rm4, writeFile as writeFile7 } from "fs/promises";
7857
- import { dirname as dirname6, resolve as resolve12 } from "path";
8264
+ import { mkdir as mkdir7, readFile as readFile11, readdir as readdir4, rename as rename4, rm as rm4, writeFile as writeFile8 } from "fs/promises";
8265
+ import { dirname as dirname7, resolve as resolve13 } from "path";
7858
8266
  var DEFAULT_OWNER = "local-owner";
7859
8267
  var LOCAL_STATE_FILE = "state.json";
7860
8268
  var CONVERSATIONS_DIRECTORY = "conversations";
@@ -7869,11 +8277,11 @@ var toStoreIdentity = async ({
7869
8277
  }
7870
8278
  return { name: ensured.name, id: agentId };
7871
8279
  };
7872
- var writeJsonAtomic3 = async (filePath, payload) => {
7873
- await mkdir6(dirname6(filePath), { recursive: true });
8280
+ var writeJsonAtomic4 = async (filePath, payload) => {
8281
+ await mkdir7(dirname7(filePath), { recursive: true });
7874
8282
  const tmpPath = `${filePath}.tmp`;
7875
- await writeFile7(tmpPath, JSON.stringify(payload, null, 2), "utf8");
7876
- await rename3(tmpPath, filePath);
8283
+ await writeFile8(tmpPath, JSON.stringify(payload, null, 2), "utf8");
8284
+ await rename4(tmpPath, filePath);
7877
8285
  };
7878
8286
  var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
7879
8287
  var normalizeTitle = (title) => {
@@ -8061,8 +8469,8 @@ var FileConversationStore = class {
8061
8469
  agentId: this.agentId
8062
8470
  });
8063
8471
  const agentDir = getAgentStoreDirectory(identity);
8064
- const conversationsDir = resolve12(agentDir, CONVERSATIONS_DIRECTORY);
8065
- const indexPath = resolve12(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
8472
+ const conversationsDir = resolve13(agentDir, CONVERSATIONS_DIRECTORY);
8473
+ const indexPath = resolve13(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
8066
8474
  this.paths = { conversationsDir, indexPath };
8067
8475
  return this.paths;
8068
8476
  }
@@ -8072,13 +8480,13 @@ var FileConversationStore = class {
8072
8480
  schemaVersion: STORAGE_SCHEMA_VERSION,
8073
8481
  conversations: Array.from(this.conversations.values()).sort((a, b) => b.updatedAt - a.updatedAt)
8074
8482
  };
8075
- await writeJsonAtomic3(indexPath, payload);
8483
+ await writeJsonAtomic4(indexPath, payload);
8076
8484
  }
8077
8485
  async readConversationFile(fileName) {
8078
8486
  const { conversationsDir } = await this.resolvePaths();
8079
- const filePath = resolve12(conversationsDir, fileName);
8487
+ const filePath = resolve13(conversationsDir, fileName);
8080
8488
  try {
8081
- const raw = await readFile10(filePath, "utf8");
8489
+ const raw = await readFile11(filePath, "utf8");
8082
8490
  return JSON.parse(raw);
8083
8491
  } catch {
8084
8492
  return void 0;
@@ -8124,7 +8532,7 @@ var FileConversationStore = class {
8124
8532
  this.loaded = true;
8125
8533
  const { indexPath } = await this.resolvePaths();
8126
8534
  try {
8127
- const raw = await readFile10(indexPath, "utf8");
8535
+ const raw = await readFile11(indexPath, "utf8");
8128
8536
  const parsed = JSON.parse(raw);
8129
8537
  for (const conversation of parsed.conversations ?? []) {
8130
8538
  this.conversations.set(conversation.conversationId, conversation);
@@ -8147,9 +8555,9 @@ var FileConversationStore = class {
8147
8555
  const { conversationsDir } = await this.resolvePaths();
8148
8556
  const existing = this.conversations.get(conversation.conversationId);
8149
8557
  const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
8150
- const filePath = resolve12(conversationsDir, fileName);
8558
+ const filePath = resolve13(conversationsDir, fileName);
8151
8559
  this.writing = this.writing.then(async () => {
8152
- await writeJsonAtomic3(filePath, conversation);
8560
+ await writeJsonAtomic4(filePath, conversation);
8153
8561
  this.conversations.set(conversation.conversationId, {
8154
8562
  conversationId: conversation.conversationId,
8155
8563
  title: conversation.title,
@@ -8245,7 +8653,7 @@ var FileConversationStore = class {
8245
8653
  if (removed) {
8246
8654
  this.writing = this.writing.then(async () => {
8247
8655
  if (existing) {
8248
- await rm4(resolve12(conversationsDir, existing.fileName), { force: true });
8656
+ await rm4(resolve13(conversationsDir, existing.fileName), { force: true });
8249
8657
  }
8250
8658
  await this.writeIndex();
8251
8659
  });
@@ -8267,14 +8675,14 @@ var FileConversationStore = class {
8267
8675
  const summary = this.conversations.get(conversationId);
8268
8676
  if (!summary) return void 0;
8269
8677
  const { conversationsDir } = await this.resolvePaths();
8270
- const filePath = resolve12(conversationsDir, summary.fileName);
8678
+ const filePath = resolve13(conversationsDir, summary.fileName);
8271
8679
  let result;
8272
8680
  this.writing = this.writing.then(async () => {
8273
8681
  const conv = await this.readConversationFile(summary.fileName);
8274
8682
  if (!conv) return;
8275
8683
  conv.runningCallbackSince = void 0;
8276
8684
  conv.updatedAt = Date.now();
8277
- await writeJsonAtomic3(filePath, conv);
8685
+ await writeJsonAtomic4(filePath, conv);
8278
8686
  this.conversations.set(conversationId, {
8279
8687
  ...summary,
8280
8688
  updatedAt: conv.updatedAt
@@ -8306,7 +8714,7 @@ var FileStateStore = class {
8306
8714
  workingDir: this.workingDir,
8307
8715
  agentId: this.agentId
8308
8716
  });
8309
- this.filePath = resolve12(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
8717
+ this.filePath = resolve13(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
8310
8718
  }
8311
8719
  isExpired(state) {
8312
8720
  return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
@@ -8318,7 +8726,7 @@ var FileStateStore = class {
8318
8726
  }
8319
8727
  this.loaded = true;
8320
8728
  try {
8321
- const raw = await readFile10(this.filePath, "utf8");
8729
+ const raw = await readFile11(this.filePath, "utf8");
8322
8730
  const parsed = JSON.parse(raw);
8323
8731
  for (const state of parsed.states ?? []) {
8324
8732
  this.states.set(state.runId, state);
@@ -8331,7 +8739,7 @@ var FileStateStore = class {
8331
8739
  states: Array.from(this.states.values())
8332
8740
  };
8333
8741
  this.writing = this.writing.then(async () => {
8334
- await writeJsonAtomic3(this.filePath, payload);
8742
+ await writeJsonAtomic4(this.filePath, payload);
8335
8743
  });
8336
8744
  await this.writing;
8337
8745
  }
@@ -9019,7 +9427,7 @@ var createConversationStore = (config, options) => {
9019
9427
  };
9020
9428
 
9021
9429
  // src/index.ts
9022
- import { defineTool as defineTool8 } from "@poncho-ai/sdk";
9430
+ import { defineTool as defineTool9 } from "@poncho-ai/sdk";
9023
9431
  export {
9024
9432
  AgentHarness,
9025
9433
  InMemoryConversationStore,
@@ -9046,13 +9454,15 @@ export {
9046
9454
  createMemoryStore,
9047
9455
  createMemoryTools,
9048
9456
  createModelProvider,
9457
+ createReminderStore,
9458
+ createReminderTools,
9049
9459
  createSearchTools,
9050
9460
  createSkillTools,
9051
9461
  createStateStore,
9052
9462
  createSubagentTools,
9053
9463
  createUploadStore,
9054
9464
  createWriteTool,
9055
- defineTool8 as defineTool,
9465
+ defineTool9 as defineTool,
9056
9466
  deleteOpenAICodexSession,
9057
9467
  deriveUploadKey,
9058
9468
  ensureAgentIdentity,