From 9d1144acf43b5b31bb059bd2f1bfad924244a1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Varl=C4=B1?= Date: Thu, 5 Feb 2026 11:09:56 +0000 Subject: [PATCH 1/3] fix(types): Accept datetime with microsecond precision and timezone offsets Python and other tools that agents may use emit timestamps with higher precision than JavaScript's native Date.toISOString(). Update the datetime schema to accept variable fractional seconds and timezone offsets. --- src/core/storage/jsonl-storage.test.ts | 18 ++++++++++++++++ src/types.ts | 30 +++++++++++++++----------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/core/storage/jsonl-storage.test.ts b/src/core/storage/jsonl-storage.test.ts index 81a6a3d..40cca15 100644 --- a/src/core/storage/jsonl-storage.test.ts +++ b/src/core/storage/jsonl-storage.test.ts @@ -135,6 +135,24 @@ describe("JsonlStorage", () => { expect(() => storage.read()).toThrow(DataCorruptionError); }); + it("accepts datetime with microsecond precision", () => { + // Python and other tools that agents may use emit microsecond precision + // which is valid ISO 8601 but more precise than JavaScript's native Date.toISOString() + const task = createTask({ + id: "micro123", + completed: true, + completed_at: "2026-02-05T08:56:48.487608+00:00", // 6 decimal places + created_at: "2026-02-05T08:56:48.487608+00:00", + updated_at: "2026-02-05T08:56:48.487608+00:00", + }); + const tasksFile = path.join(tempDir, "tasks.jsonl"); + fs.writeFileSync(tasksFile, JSON.stringify(task) + "\n", "utf-8"); + + const store = storage.read(); + expect(store.tasks).toHaveLength(1); + expect(store.tasks[0].completed_at).toBe("2026-02-05T08:56:48.487608+00:00"); + }); + it("throws StorageError when file cannot be read", () => { const tasksFile = path.join(tempDir, "tasks.jsonl"); fs.writeFileSync(tasksFile, "test", "utf-8"); diff --git a/src/types.ts b/src/types.ts index 165b5bb..be2dd07 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,12 +3,18 @@ import { z } from "zod"; // Maximum content length (50KB) to prevent excessive file sizes const MAX_CONTENT_LENGTH = 50 * 1024; +// ISO 8601 datetime that accepts: +// - Timezone offsets (+00:00) in addition to Z suffix +// - Variable fractional seconds (0-9 digits) for compatibility with +// Python and other tools that agents may use to generate timestamps +const flexibleDatetime = () => z.string().datetime({ offset: true }); + export const CommitMetadataSchema = z.object({ sha: z.string().min(1), message: z.string().optional(), branch: z.string().optional(), url: z.string().url().optional(), - timestamp: z.string().datetime().optional(), + timestamp: flexibleDatetime().optional(), }); export type CommitMetadata = z.infer; @@ -66,10 +72,10 @@ const TaskSchemaBase = z.object({ .nullable() .default(null), metadata: TaskMetadataSchema.default(null), - created_at: z.string().datetime(), - updated_at: z.string().datetime(), - started_at: z.string().datetime().nullable().default(null), - completed_at: z.string().datetime().nullable().default(null), + created_at: flexibleDatetime(), + updated_at: flexibleDatetime(), + started_at: flexibleDatetime().nullable().default(null), + completed_at: flexibleDatetime().nullable().default(null), // Bidirectional blocking relationships blockedBy: z.array(z.string().min(1)).default([]), // Tasks that block this one blocks: z.array(z.string().min(1)).default([]), // Tasks this one blocks @@ -146,10 +152,10 @@ export const CreateTaskInputSchema = z.object({ .nullable() .optional(), metadata: TaskMetadataSchema.optional(), - created_at: z.string().datetime().optional(), - updated_at: z.string().datetime().optional(), - started_at: z.string().datetime().nullable().optional(), - completed_at: z.string().datetime().nullable().optional(), + created_at: flexibleDatetime().optional(), + updated_at: flexibleDatetime().optional(), + started_at: flexibleDatetime().nullable().optional(), + completed_at: flexibleDatetime().nullable().optional(), }); export type CreateTaskInput = z.infer; @@ -178,7 +184,7 @@ export const UpdateTaskInputSchema = z.object({ .max(MAX_CONTENT_LENGTH, "Result exceeds maximum length") .optional(), metadata: TaskMetadataSchema.optional(), - started_at: z.string().datetime().nullable().optional(), + started_at: flexibleDatetime().nullable().optional(), delete: z.boolean().optional(), add_blocked_by: z.array(z.string().min(1)).optional(), remove_blocked_by: z.array(z.string().min(1)).optional(), @@ -217,8 +223,8 @@ export const ArchivedTaskSchema = z.object({ name: z.string().min(1, "Name is required"), description: z.string().default(""), result: z.string().nullable().default(null), - completed_at: z.string().datetime().nullable().default(null), - archived_at: z.string().datetime(), + completed_at: flexibleDatetime().nullable().default(null), + archived_at: flexibleDatetime(), metadata: z .object({ github: GithubMetadataSchema.optional(), From 40e9c426ec32def6064166a677419413051ee3f8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 17 Feb 2026 13:06:09 -0800 Subject: [PATCH 2/3] style: convert flexibleDatetime to function declaration Co-Authored-By: Claude Opus 4.6 --- src/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index be2dd07..b1a25d6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,9 @@ const MAX_CONTENT_LENGTH = 50 * 1024; // - Timezone offsets (+00:00) in addition to Z suffix // - Variable fractional seconds (0-9 digits) for compatibility with // Python and other tools that agents may use to generate timestamps -const flexibleDatetime = () => z.string().datetime({ offset: true }); +function flexibleDatetime() { + return z.string().datetime({ offset: true }); +} export const CommitMetadataSchema = z.object({ sha: z.string().min(1), From b76e2e60f392d02a67f81c7ecdfdce8e277e2ab4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 17 Feb 2026 13:26:59 -0800 Subject: [PATCH 3/3] style: simplify flexibleDatetime comment for accuracy Zod's datetime() already accepts variable fractional seconds by default; the { offset: true } option only adds timezone offset support. Co-Authored-By: Claude Opus 4.6 --- src/types.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/types.ts b/src/types.ts index 0d27215..58f5f3d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,10 +3,8 @@ import { z } from "zod"; // Maximum content length (50KB) to prevent excessive file sizes const MAX_CONTENT_LENGTH = 50 * 1024; -// ISO 8601 datetime that accepts: -// - Timezone offsets (+00:00) in addition to Z suffix -// - Variable fractional seconds (0-9 digits) for compatibility with -// Python and other tools that agents may use to generate timestamps +// ISO 8601 datetime that accepts timezone offsets (+00:00) in addition to Z suffix, +// for compatibility with Python and other tools that agents may use to generate timestamps function flexibleDatetime() { return z.string().datetime({ offset: true }); }