@aigne/afs-scheduler 1.12.0-beta.5

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.
Files changed (75) hide show
  1. package/LICENSE.md +26 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/cron-parser.cjs +202 -0
  5. package/dist/cron-parser.d.cts +65 -0
  6. package/dist/cron-parser.d.cts.map +1 -0
  7. package/dist/cron-parser.d.mts +65 -0
  8. package/dist/cron-parser.d.mts.map +1 -0
  9. package/dist/cron-parser.mjs +200 -0
  10. package/dist/cron-parser.mjs.map +1 -0
  11. package/dist/do-executor.cjs +59 -0
  12. package/dist/do-executor.d.cts +30 -0
  13. package/dist/do-executor.d.cts.map +1 -0
  14. package/dist/do-executor.d.mts +30 -0
  15. package/dist/do-executor.d.mts.map +1 -0
  16. package/dist/do-executor.mjs +59 -0
  17. package/dist/do-executor.mjs.map +1 -0
  18. package/dist/do-job-executor.cjs +53 -0
  19. package/dist/do-job-executor.d.cts +28 -0
  20. package/dist/do-job-executor.d.cts.map +1 -0
  21. package/dist/do-job-executor.d.mts +28 -0
  22. package/dist/do-job-executor.d.mts.map +1 -0
  23. package/dist/do-job-executor.mjs +53 -0
  24. package/dist/do-job-executor.mjs.map +1 -0
  25. package/dist/do-types.d.cts +66 -0
  26. package/dist/do-types.d.cts.map +1 -0
  27. package/dist/do-types.d.mts +66 -0
  28. package/dist/do-types.d.mts.map +1 -0
  29. package/dist/index.cjs +34 -0
  30. package/dist/index.d.cts +11 -0
  31. package/dist/index.d.mts +11 -0
  32. package/dist/index.mjs +11 -0
  33. package/dist/job-coordinator.cjs +216 -0
  34. package/dist/job-coordinator.d.cts +8 -0
  35. package/dist/job-coordinator.d.cts.map +1 -0
  36. package/dist/job-coordinator.d.mts +8 -0
  37. package/dist/job-coordinator.d.mts.map +1 -0
  38. package/dist/job-coordinator.mjs +216 -0
  39. package/dist/job-coordinator.mjs.map +1 -0
  40. package/dist/local-executor.cjs +25 -0
  41. package/dist/local-executor.d.cts +12 -0
  42. package/dist/local-executor.d.cts.map +1 -0
  43. package/dist/local-executor.d.mts +12 -0
  44. package/dist/local-executor.d.mts.map +1 -0
  45. package/dist/local-executor.mjs +25 -0
  46. package/dist/local-executor.mjs.map +1 -0
  47. package/dist/scheduler-provider.cjs +1236 -0
  48. package/dist/scheduler-provider.d.cts +95 -0
  49. package/dist/scheduler-provider.d.cts.map +1 -0
  50. package/dist/scheduler-provider.d.mts +95 -0
  51. package/dist/scheduler-provider.d.mts.map +1 -0
  52. package/dist/scheduler-provider.mjs +1237 -0
  53. package/dist/scheduler-provider.mjs.map +1 -0
  54. package/dist/task-runner.cjs +70 -0
  55. package/dist/task-runner.d.cts +25 -0
  56. package/dist/task-runner.d.cts.map +1 -0
  57. package/dist/task-runner.d.mts +25 -0
  58. package/dist/task-runner.d.mts.map +1 -0
  59. package/dist/task-runner.mjs +70 -0
  60. package/dist/task-runner.mjs.map +1 -0
  61. package/dist/task-store.cjs +186 -0
  62. package/dist/task-store.d.cts +60 -0
  63. package/dist/task-store.d.cts.map +1 -0
  64. package/dist/task-store.d.mts +60 -0
  65. package/dist/task-store.d.mts.map +1 -0
  66. package/dist/task-store.mjs +187 -0
  67. package/dist/task-store.mjs.map +1 -0
  68. package/dist/types.cjs +185 -0
  69. package/dist/types.d.cts +384 -0
  70. package/dist/types.d.cts.map +1 -0
  71. package/dist/types.d.mts +384 -0
  72. package/dist/types.d.mts.map +1 -0
  73. package/dist/types.mjs +176 -0
  74. package/dist/types.mjs.map +1 -0
  75. package/package.json +59 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler-provider.mjs","names":["job"],"sources":["../src/scheduler-provider.ts"],"sourcesContent":["import type {\n AFSAccessMode,\n AFSEntry,\n AFSExecResult,\n AFSExplainResult,\n AFSListOptions,\n AFSListResult,\n AFSReadOptions,\n AFSReadResult,\n AFSRoot,\n ProviderManifest,\n} from \"@aigne/afs\";\nimport { AFSConflictError, makeNsLog } from \"@aigne/afs\";\nimport {\n Actions,\n AFSBaseProvider,\n Explain,\n List,\n Meta,\n PersistenceHelper,\n Read,\n type RouteContext,\n} from \"@aigne/afs/provider\";\nimport { v7 } from \"@aigne/uuid\";\nimport { joinURL } from \"ufo\";\nimport { z } from \"zod/v4\";\nimport { DOJobExecutor } from \"./do-job-executor.js\";\nimport { LocalExecutor } from \"./local-executor.js\";\nimport { TaskStore } from \"./task-store.js\";\nimport {\n CancelArgsSchema,\n CancelJobArgsSchema,\n ClaimArgsSchema,\n DispatchArgsSchema,\n HeartbeatArgsSchema,\n type JobEntry,\n RetryArgsSchema,\n ScheduleArgsSchema,\n type SchedulerProviderOptions,\n type SchedulerSnapshot,\n SchedulerSnapshotSchema,\n SubmitArgsSchema,\n SubmitJobArgsSchema,\n type TaskContext,\n type TaskEntry,\n type TaskExecutor,\n type TaskStatus,\n type TickResult,\n UnscheduleArgsSchema,\n} from \"./types.js\";\n\nconst log = makeNsLog(\"provider:scheduler\");\n\nconst DEFAULT_SCHEDULED_TASK_TIMEOUT_MS = 0;\n/** Default lease duration for claim-based tasks (#1067) when `claim` omits `leaseDuration`. */\nconst DEFAULT_LEASE_DURATION_MS = 5 * 60 * 1000;\n\nconst ACTION_NAMES = [\n \"dispatch\",\n \"cancel\",\n \"retry\",\n \"schedule\",\n \"unschedule\",\n \"heartbeat\",\n \"submit-job\",\n \"cancel-job\",\n \"claim\",\n \"submit\",\n] as const;\n\nexport class AFSScheduler extends AFSBaseProvider {\n static manifest(): ProviderManifest {\n return {\n name: \"scheduler\",\n description:\n \"Task dispatch and execution management.\\n- Dispatch tasks for background execution with optional proc budget integration\\n- Cron-based scheduling with duplicate protection\\n- Map-reduce job coordination with batch processing\",\n uriTemplate: \"scheduler://\",\n category: \"compute\",\n schema: z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n maxHistory: z.number().int().positive().optional(),\n storagePath: z.string().optional(),\n procPath: z.string().optional(),\n budgetBlockedTTL: z.number().int().nonnegative().optional(),\n }),\n tags: [\"scheduler\", \"dispatch\", \"cron\", \"background\"],\n capabilityTags: [\"read-write\", \"auth:none\", \"local\"],\n security: {\n riskLevel: \"system\",\n resourceAccess: [\"process-spawn\"],\n dataSensitivity: [\"code\"],\n notes: [\"Executes tasks by delegating to AFS exec, can spawn background processes\"],\n },\n capabilities: {\n crossProvider: { afsAccess: true },\n },\n };\n }\n\n readonly name: string;\n readonly description?: string;\n override readonly accessMode: AFSAccessMode = \"readwrite\";\n\n /** @internal — exposed for conformance test pre-population */\n readonly store: TaskStore;\n private executor!: TaskExecutor;\n private jobExecutor?: DOJobExecutor;\n private root!: AFSRoot;\n private readonly options: SchedulerProviderOptions;\n private readonly persistence: PersistenceHelper<SchedulerSnapshot>;\n\n constructor(options: SchedulerProviderOptions = {}) {\n super();\n this.name = options.name ?? \"scheduler\";\n this.description = options.description ?? \"Task dispatch and execution management\";\n this.options = options;\n this.store = new TaskStore(options.maxHistory ?? 100, options.budgetBlockedTTL);\n this.persistence = new PersistenceHelper<SchedulerSnapshot>({\n storagePath: options.storagePath,\n getSnapshot: () => this.store.snapshot(),\n applySnapshot: (data, now) => this.store.restore(data, now),\n snapshotVersion: 3,\n validateSnapshot: (raw) => SchedulerSnapshotSchema.parse(raw) as SchedulerSnapshot,\n });\n }\n\n onMount(root: AFSRoot, _mountPath?: string): void {\n this.root = root;\n this.executor = this.options.executor ?? new LocalExecutor(() => this.root);\n if (this.options.coordinatorNamespace) {\n this.jobExecutor = new DOJobExecutor(this.options.coordinatorNamespace);\n }\n this.persistence.attachRoot(root);\n }\n\n /** Await all pending snapshot writes. Use before reading persisted state. */\n async flush(): Promise<void> {\n await this.persistence.flush();\n }\n\n // ─── Lifecycle overrides (ensureReady gate) ───\n\n override async read(path: string, options?: AFSReadOptions): Promise<AFSReadResult> {\n await this.persistence.ensureReady();\n return super.read(path, options);\n }\n\n override async list(path: string, options?: AFSListOptions): Promise<AFSListResult> {\n await this.persistence.ensureReady();\n return super.list(path, options);\n }\n\n // ─── Read ───\n\n @Read(\"/\")\n readRoot(ctx: RouteContext): AFSEntry | undefined {\n return this.buildEntry(ctx.path, {\n content: this.description,\n meta: { kind: \"scheduler:root\", childrenCount: 4 },\n });\n }\n\n @Read(\"/tasks\")\n readTasks(ctx: RouteContext): AFSEntry | undefined {\n const tasks = this.store.listActive();\n return this.buildEntry(ctx.path, {\n content: `${tasks.length} active tasks`,\n meta: { kind: \"scheduler:task-list\", childrenCount: tasks.length },\n });\n }\n\n @Read(\"/tasks/:id\")\n readTask(ctx: RouteContext<{ id: string }>): AFSEntry | undefined {\n const entry = this.store.getAny(ctx.params.id);\n if (!entry) return undefined;\n return this.buildEntry(ctx.path, {\n id: entry.id,\n content: entry,\n meta: { kind: \"scheduler:task\" },\n });\n }\n\n @Read(\"/tasks/:id/:field\")\n readTaskField(ctx: RouteContext<{ id: string; field: string }>): AFSEntry | undefined {\n const entry = this.store.getAny(ctx.params.id);\n if (!entry) return undefined;\n return this.readEntryField(ctx.path, entry, ctx.params.field);\n }\n\n @Read(\"/history\")\n readHistory(ctx: RouteContext): AFSEntry | undefined {\n const history = this.store.listHistory();\n return this.buildEntry(ctx.path, {\n content: `${history.length} history entries`,\n meta: { kind: \"scheduler:history\", childrenCount: history.length },\n });\n }\n\n @Read(\"/history/:id\")\n readHistoryTask(ctx: RouteContext<{ id: string }>): AFSEntry | undefined {\n const entry = this.store.getFromHistory(ctx.params.id);\n if (!entry) return undefined;\n return this.buildEntry(ctx.path, {\n id: entry.id,\n content: entry,\n meta: { kind: \"scheduler:task\" },\n });\n }\n\n @Read(\"/history/:id/:field\")\n readHistoryTaskField(ctx: RouteContext<{ id: string; field: string }>): AFSEntry | undefined {\n const entry = this.store.getFromHistory(ctx.params.id);\n if (!entry) return undefined;\n return this.readEntryField(ctx.path, entry, ctx.params.field);\n }\n\n @Read(\"/schedules\")\n readSchedules(ctx: RouteContext): AFSEntry | undefined {\n const schedules = this.store.listSchedules();\n return this.buildEntry(ctx.path, {\n content: `${schedules.length} schedules`,\n meta: { kind: \"scheduler:schedule-list\", childrenCount: schedules.length },\n });\n }\n\n @Read(\"/schedules/:id\")\n readSchedule(ctx: RouteContext<{ id: string }>): AFSEntry | undefined {\n const schedule = this.store.getSchedule(ctx.params.id);\n if (!schedule) return undefined;\n return this.buildEntry(ctx.path, {\n id: schedule.id,\n content: schedule,\n meta: { kind: \"scheduler:schedule\" },\n });\n }\n\n @Read(\"/jobs\")\n readJobs(ctx: RouteContext): AFSEntry | undefined {\n const jobs = this.store.listJobs();\n return this.buildEntry(ctx.path, {\n content: `${jobs.length} jobs`,\n meta: { kind: \"scheduler:job-list\", childrenCount: jobs.length },\n });\n }\n\n @Read(\"/jobs/:id\")\n async readJob(ctx: RouteContext<{ id: string }>): Promise<AFSEntry | undefined> {\n // When using Coordinator DOs, query the DO for job status\n if (this.jobExecutor) {\n const job = await this.jobExecutor.getStatus(ctx.params.id);\n if (!job) return undefined;\n return this.buildEntry(ctx.path, {\n id: job.id,\n content: job,\n meta: { kind: \"scheduler:job\" },\n });\n }\n\n const job = this.store.getJob(ctx.params.id);\n if (!job) return undefined;\n return this.buildEntry(ctx.path, {\n id: job.id,\n content: job,\n meta: { kind: \"scheduler:job\" },\n });\n }\n\n @Read(\"/.meta/.capabilities\")\n readCapabilities(ctx: RouteContext): AFSEntry | undefined {\n return this.buildEntry(ctx.path, {\n content: { operations: this.getOperationsDeclaration() },\n });\n }\n\n // ─── List ───\n\n @List(\"/\")\n listRoot(ctx: RouteContext): AFSListResult {\n return {\n data: [\n this.buildEntry(joinURL(ctx.path, \"tasks\"), {\n meta: {\n kind: \"scheduler:task-list\",\n childrenCount: this.store.listActive().length,\n },\n }),\n this.buildEntry(joinURL(ctx.path, \"history\"), {\n meta: {\n kind: \"scheduler:history\",\n childrenCount: this.store.listHistory().length,\n },\n }),\n this.buildEntry(joinURL(ctx.path, \"schedules\"), {\n meta: {\n kind: \"scheduler:schedule-list\",\n childrenCount: this.store.listSchedules().length,\n },\n }),\n this.buildEntry(joinURL(ctx.path, \"jobs\"), {\n meta: {\n kind: \"scheduler:job-list\",\n childrenCount: this.store.listJobs().length,\n },\n }),\n ],\n };\n }\n\n @List(\"/tasks\")\n listTasks(ctx: RouteContext): AFSListResult {\n const tasks = this.store.listActive();\n return {\n data: tasks.map((t) =>\n this.buildEntry(joinURL(ctx.path, t.id), {\n id: t.id,\n content: t,\n meta: { kind: \"scheduler:task\" },\n }),\n ),\n };\n }\n\n @List(\"/history\")\n listHistory(ctx: RouteContext): AFSListResult {\n const history = this.store.listHistory();\n return {\n data: history.map((t) =>\n this.buildEntry(joinURL(ctx.path, t.id), {\n id: t.id,\n content: t,\n meta: { kind: \"scheduler:task\" },\n }),\n ),\n };\n }\n\n @List(\"/schedules\")\n listSchedules(ctx: RouteContext): AFSListResult {\n const schedules = this.store.listSchedules();\n return {\n data: schedules.map((s) =>\n this.buildEntry(joinURL(ctx.path, s.id), {\n id: s.id,\n content: s,\n meta: { kind: \"scheduler:schedule\" },\n }),\n ),\n };\n }\n\n @List(\"/jobs\")\n listJobsHandler(ctx: RouteContext): AFSListResult {\n const jobs = this.store.listJobs();\n return {\n data: jobs.map((j) =>\n this.buildEntry(joinURL(ctx.path, j.id), {\n id: j.id,\n content: j,\n meta: { kind: \"scheduler:job\" },\n }),\n ),\n };\n }\n\n // ─── Meta ───\n\n @Meta(\"/\")\n readRootMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"scheduler:root\", childrenCount: 4 };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/tasks\")\n readTasksMeta(ctx: RouteContext): AFSEntry | undefined {\n const count = this.store.listActive().length;\n const meta = { kind: \"scheduler:task-list\", childrenCount: count };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/history\")\n readHistoryMeta(ctx: RouteContext): AFSEntry | undefined {\n const count = this.store.listHistory().length;\n const meta = { kind: \"scheduler:history\", childrenCount: count };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/schedules\")\n readSchedulesMeta(ctx: RouteContext): AFSEntry | undefined {\n const count = this.store.listSchedules().length;\n const meta = { kind: \"scheduler:schedule-list\", childrenCount: count };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/jobs\")\n readJobsMeta(ctx: RouteContext): AFSEntry | undefined {\n const count = this.store.listJobs().length;\n const meta = { kind: \"scheduler:job-list\", childrenCount: count };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n // ─── Explain ───\n\n @Explain(\"/\")\n explainRoot(): AFSExplainResult {\n return {\n content:\n \"Scheduler provider — dispatches tasks for background execution. \" +\n \"Use dispatch to submit tasks, cancel to stop them, retry to re-run failed tasks. \" +\n \"Read /tasks/{id}/status to check progress.\",\n format: \"text\",\n };\n }\n\n @Explain(\"/:path+\")\n explainPath(ctx: RouteContext<{ path: string }>): AFSExplainResult {\n return {\n content: `Scheduler path: /${ctx.params.path}`,\n format: \"text\",\n };\n }\n\n // ─── Actions ───\n\n @Actions(\"/\")\n listActions(ctx: RouteContext): AFSListResult {\n const entries = ACTION_NAMES.map((name) =>\n this.buildEntry(joinURL(ctx.path, name), {\n id: name,\n meta: { kind: \"afs:executable\" },\n }),\n );\n return { data: entries };\n }\n\n @Actions.Exec(\"/\", \"dispatch\", \"Submit a task for execution\", { effect: \"write\" })\n async execDispatch(ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = DispatchArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n let procId: string | undefined;\n\n // If proc config provided, register proc first\n if (parsed.data.proc) {\n const procPath = this.options.procPath ?? \"/proc\";\n if (!this.root.exec) {\n return {\n success: false,\n error: {\n code: \"PROC_REGISTRATION_FAILED\",\n message: \"AFS root does not support exec\",\n },\n };\n }\n\n try {\n const procData = {\n ...parsed.data.proc,\n parentId: parsed.data.proc.parentId ?? (args.parentProcId as string | undefined),\n };\n const regResult = await this.root.exec(\n joinURL(procPath, \".actions\", \"register\"),\n procData,\n {},\n );\n if (!regResult.success) {\n return {\n success: false,\n error: {\n code: \"PROC_REGISTRATION_FAILED\",\n message: regResult.error?.message ?? \"Failed to register proc\",\n },\n };\n }\n procId = (regResult.data as { procId: string }).procId;\n } catch (err) {\n return {\n success: false,\n error: {\n code: \"PROC_REGISTRATION_FAILED\",\n message: String(err),\n },\n };\n }\n }\n\n // Claim-based (pull model) task: `assignee` present (even \"\") means an\n // external worker must `claim` this task — do NOT auto-execute it here\n // (#1067). Absent `assignee` keeps the existing push/auto-execute behavior.\n const isClaimable = parsed.data.assignee !== undefined;\n\n const entry = this.store.create(\n parsed.data.task,\n parsed.data.args,\n procId,\n parsed.data.estimate,\n {\n ownerKey: this.getCallerKey(ctx),\n claimable: isClaimable,\n assignee: parsed.data.assignee,\n },\n );\n this.persistence.schedulePersist();\n\n if (isClaimable) {\n return { success: true, data: { taskId: entry.id, procId, claimable: true } };\n }\n\n // Fire-and-forget — .catch prevents unhandled rejection if executeTask() itself throws\n const taskPromise = this.executeTask(entry.id).catch((err) => {\n log.error(\"[scheduler] executeTask failed:\", err);\n });\n this.options.waitUntil?.(taskPromise);\n return { success: true, data: { taskId: entry.id, procId } };\n }\n\n @Actions.Exec(\"/\", \"cancel\", \"Cancel a running task\", { effect: \"write\" })\n async execCancel(ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = CancelArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n const { taskId } = parsed.data;\n const entry = this.store.get(taskId);\n if (!entry) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: `Task ${taskId} not found` },\n };\n }\n\n const ownerError = this.checkTaskOwner(entry, ctx);\n if (ownerError) return ownerError;\n\n if (entry.status === \"done\" || entry.status === \"failed\" || entry.status === \"timeout\") {\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is already ${entry.status}`,\n },\n };\n }\n\n if (entry.status === \"cancelled\") {\n return { success: true, data: { cancelled: true, alreadyCancelled: true } };\n }\n\n this.store.update(taskId, { status: \"cancelled\", ended: Date.now() });\n this.store.moveToHistory(taskId);\n this.persistence.schedulePersist();\n\n if (this.executor.cancel) {\n try {\n await this.executor.cancel(taskId);\n } catch {\n // Best-effort cancel — executor may have already completed\n }\n }\n\n // Complete proc if procId exists\n if (entry.procId) {\n await this.completeProc(entry.procId, \"failed\");\n }\n\n return { success: true, data: { cancelled: true } };\n }\n\n @Actions.Exec(\"/\", \"retry\", \"Retry a failed task\", { effect: \"write\" })\n async execRetry(ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = RetryArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n const { taskId } = parsed.data;\n\n // Check active store first, then history\n let entry = this.store.get(taskId);\n if (entry) {\n const ownerError = this.checkTaskOwner(entry, ctx);\n if (ownerError) return ownerError;\n if (entry.status === \"running\" || entry.status === \"pending\") {\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is ${entry.status}, cannot retry`,\n },\n };\n }\n if (entry.status === \"done\") {\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is done, cannot retry`,\n },\n };\n }\n // budget-blocked, failed, cancelled, timeout — all retryable from active store\n } else {\n entry = this.store.restoreFromHistory(taskId);\n if (!entry) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: `Task ${taskId} not found` },\n };\n }\n const ownerError = this.checkTaskOwner(entry, ctx);\n if (ownerError) {\n this.store.moveToHistory(taskId);\n return ownerError;\n }\n if (entry.status === \"done\") {\n // Put it back in history\n this.store.moveToHistory(taskId);\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is done, cannot retry`,\n },\n };\n }\n }\n\n // Reset for retry — only failed, cancelled, and budget-blocked are retryable\n this.store.update(taskId, {\n status: \"pending\",\n result: undefined,\n error: undefined,\n started: undefined,\n ended: undefined,\n });\n this.persistence.schedulePersist();\n\n // Fire-and-forget — .catch prevents unhandled rejection if executeTask() itself throws\n const retryPromise = this.executeTask(taskId).catch((err) => {\n log.error(\"[scheduler] executeTask failed:\", err);\n });\n this.options.waitUntil?.(retryPromise);\n return { success: true, data: { taskId } };\n }\n\n @Actions.Exec(\"/\", \"schedule\", \"Create a cron schedule\", { effect: \"write\" })\n async execSchedule(ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = ScheduleArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n try {\n const schedule = this.store.addSchedule({\n name: parsed.data.name,\n cron: parsed.data.cron,\n taskPath: parsed.data.task,\n args: parsed.data.args,\n enabled: parsed.data.enabled,\n ownerKey: this.getCallerKey(ctx),\n });\n this.persistence.schedulePersist();\n return { success: true, data: { scheduleId: schedule.id } };\n } catch (err) {\n return {\n success: false,\n error: {\n code: \"SCHEDULE_ERROR\",\n message: err instanceof Error ? err.message : String(err),\n },\n };\n }\n }\n\n @Actions.Exec(\"/\", \"unschedule\", \"Remove a cron schedule\", { effect: \"write\" })\n async execUnschedule(ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = UnscheduleArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n let removed = false;\n let scheduleOwnerError: AFSExecResult | undefined;\n if (parsed.data.scheduleId) {\n const schedule = this.store.getSchedule(parsed.data.scheduleId);\n scheduleOwnerError = schedule ? this.checkScheduleOwner(schedule, ctx) : undefined;\n if (!scheduleOwnerError) removed = this.store.removeSchedule(parsed.data.scheduleId);\n } else if (parsed.data.name) {\n const schedule = this.store.getScheduleByName(parsed.data.name);\n if (schedule) {\n scheduleOwnerError = this.checkScheduleOwner(schedule, ctx);\n if (!scheduleOwnerError) removed = this.store.removeSchedule(schedule.id);\n }\n }\n\n if (scheduleOwnerError) return scheduleOwnerError;\n\n if (!removed) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: \"Schedule not found\" },\n };\n }\n\n this.persistence.schedulePersist();\n return { success: true };\n }\n\n @Actions.Exec(\"/\", \"heartbeat\", \"Send heartbeat for a running task\", { effect: \"write\" })\n async execHeartbeat(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = HeartbeatArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n const { taskId } = parsed.data;\n const entry = this.store.get(taskId);\n if (!entry) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: `Task ${taskId} not found` },\n };\n }\n\n if (entry.status !== \"running\") {\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is ${entry.status}, not running`,\n },\n };\n }\n\n if (!entry.timeout || !entry.deadline) {\n return {\n success: false,\n error: {\n code: \"NO_TIMEOUT\",\n message: `Task ${taskId} has no timeout configured`,\n },\n };\n }\n\n // Sliding window: extend deadline by timeout duration\n const now = Date.now();\n this.store.update(taskId, {\n lastHeartbeat: now,\n deadline: now + entry.timeout,\n });\n this.persistence.schedulePersist();\n return { success: true };\n }\n\n @Actions.Exec(\n \"/\",\n \"claim\",\n \"Atomically claim an unclaimed (or lease-expired) task for external worker execution\",\n { effect: \"write\" },\n )\n async execClaim(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = ClaimArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n const { taskId, workerId, leaseDuration, ifMatch } = parsed.data;\n const entry = this.store.get(taskId);\n if (!entry) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: `Task ${taskId} not found` },\n };\n }\n\n if (!entry.claimable) {\n return {\n success: false,\n error: {\n code: \"NOT_CLAIMABLE\",\n message: `Task ${taskId} was not dispatched for claiming (missing assignee field)`,\n },\n };\n }\n\n if (entry.assignee && entry.assignee !== workerId) {\n return {\n success: false,\n error: {\n code: \"PERMISSION_DENIED\",\n message: `Task ${taskId} is reserved for a different assignee`,\n },\n };\n }\n\n const now = Date.now();\n\n // Optimistic-concurrency check (ifMatch / AFSConflictError contract, see\n // docs/guides/provider-authoring.md) runs BEFORE the specific-reason\n // checks below: the caller supplies the version it last observed (via\n // read/list). A mismatch means claim state has moved on since — another\n // worker won a race, or the lease was renewed/reclaimed — and the caller\n // needs the generic \"your view is stale, re-read and retry\" signal\n // rather than a specific-but-possibly-misleading ALREADY_CLAIMED/\n // INVALID_STATE reason. Absent ifMatch skips the check (last-claim-wins).\n if (ifMatch !== undefined && ifMatch !== entry.version) {\n throw new AFSConflictError(joinURL(\"/tasks\", taskId), { cid: entry.version ?? null });\n }\n\n // Lazy lease-expiry reclaim: no background sweep needed — a stale lease\n // (deadline elapsed with no heartbeat) is simply treated as claimable by\n // the next `claim` call, whoever that worker is.\n const leaseExpired =\n entry.claimedBy !== undefined && entry.deadline !== undefined && entry.deadline < now;\n\n if (entry.claimedBy !== undefined && entry.claimedBy !== workerId && !leaseExpired) {\n return {\n success: false,\n error: {\n code: \"ALREADY_CLAIMED\",\n message: `Task ${taskId} is already claimed by another worker`,\n },\n };\n }\n\n if (entry.status !== \"pending\" && !(entry.status === \"running\" && leaseExpired)) {\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is ${entry.status}, cannot claim`,\n },\n };\n }\n\n const lease = leaseDuration ?? this.options.defaultLeaseDuration ?? DEFAULT_LEASE_DURATION_MS;\n const newVersion = v7();\n\n this.store.update(taskId, {\n status: \"running\",\n claimedBy: workerId,\n claimedAt: now,\n started: entry.started ?? now,\n attempts: entry.attempts + 1,\n lastHeartbeat: now,\n deadline: now + lease,\n timeout: lease,\n version: newVersion,\n });\n this.persistence.schedulePersist();\n\n return {\n success: true,\n data: { taskId, claimedBy: workerId, leaseDeadline: now + lease, version: newVersion },\n };\n }\n\n @Actions.Exec(\n \"/\",\n \"submit\",\n \"Submit worker completion + artifacts for a claimed task, enqueuing a verification task\",\n { effect: \"write\" },\n )\n async execSubmit(ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = SubmitArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n const { taskId, workerId, success, result, artifacts, verifyArgs } = parsed.data;\n const verifyTaskPath = parsed.data.verifyTask ?? this.options.verifyTaskPath;\n if (!verifyTaskPath) {\n // Verification is itself an agent task, never a script gate (#777\n // decision) — submit must never silently skip enqueuing it.\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message:\n \"verifyTask is required (no verifyTaskPath configured on the scheduler provider)\",\n },\n };\n }\n\n const entry = this.store.get(taskId);\n if (!entry) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: `Task ${taskId} not found` },\n };\n }\n\n if (!entry.claimable) {\n return {\n success: false,\n error: { code: \"NOT_CLAIMABLE\", message: `Task ${taskId} is not a claim-based task` },\n };\n }\n\n if (entry.claimedBy !== workerId) {\n return {\n success: false,\n error: {\n code: \"PERMISSION_DENIED\",\n message: `Task ${taskId} is not claimed by ${workerId}`,\n },\n };\n }\n\n if (entry.status !== \"running\") {\n return {\n success: false,\n error: {\n code: \"INVALID_STATE\",\n message: `Task ${taskId} is ${entry.status}, cannot submit`,\n },\n };\n }\n\n const finalStatus: TaskStatus = success === false ? \"failed\" : \"done\";\n const now = Date.now();\n\n this.store.update(taskId, {\n status: finalStatus,\n result: { success: success !== false, data: result },\n ended: now,\n version: v7(),\n });\n this.store.moveToHistory(taskId);\n\n if (entry.procId) {\n await this.completeProc(entry.procId, finalStatus === \"done\" ? \"done\" : \"failed\");\n }\n\n // Verification is itself an agent task (#777 decision) — enqueue it\n // rather than running a script gate inline.\n const verifyEntry = this.store.create(\n verifyTaskPath,\n {\n ...(typeof verifyArgs === \"object\" && verifyArgs !== null ? verifyArgs : {}),\n originalTaskId: taskId,\n workerId,\n result,\n artifacts,\n },\n undefined,\n undefined,\n { ownerKey: this.getCallerKey(ctx) },\n );\n const verifyTaskId = verifyEntry.id;\n const verifyPromise = this.executeTask(verifyTaskId).catch((err) => {\n log.error(\"[scheduler] executeTask failed:\", err);\n });\n this.options.waitUntil?.(verifyPromise);\n\n this.persistence.schedulePersist();\n\n return { success: true, data: { taskId, status: finalStatus, verifyTaskId } };\n }\n\n @Actions.Exec(\"/\", \"submit-job\", \"Submit a map-reduce job\", { effect: \"write\" })\n async execSubmitJob(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = SubmitJobArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n const batchSize = parsed.data.batchSize ?? 100;\n const concurrency = parsed.data.concurrency ?? 10;\n const jobId = v7();\n\n // Delegate to Coordinator DO when configured\n if (this.jobExecutor) {\n try {\n const result = await this.jobExecutor.submitJob({\n jobId,\n name: parsed.data.name,\n mapTask: parsed.data.mapTask,\n reduceTask: parsed.data.reduceTask,\n totalItems: parsed.data.totalItems,\n batchSize,\n concurrency,\n args: parsed.data.args,\n timeout: parsed.data.timeout,\n });\n return { success: true, data: { jobId: result.jobId } };\n } catch (err) {\n return {\n success: false,\n error: { code: \"COORDINATOR_ERROR\", message: String(err) },\n };\n }\n }\n\n // In-memory coordination (LocalExecutor)\n const totalBatches = Math.ceil(parsed.data.totalItems / batchSize);\n\n const job: JobEntry = {\n id: jobId,\n name: parsed.data.name,\n status: \"mapping\",\n mapTask: parsed.data.mapTask,\n reduceTask: parsed.data.reduceTask,\n args: parsed.data.args,\n totalItems: parsed.data.totalItems,\n batchSize,\n concurrency,\n timeout: parsed.data.timeout,\n totalBatches,\n progress: { pending: totalBatches, running: 0, done: 0, failed: 0 },\n mapTaskIds: new Array<string>(totalBatches).fill(\"\"),\n mapResults: new Array<unknown>(totalBatches).fill(null),\n created: Date.now(),\n started: Date.now(),\n };\n\n this.store.addJob(job);\n this.persistence.schedulePersist();\n\n // Dispatch first wave\n const firstWave = Math.min(concurrency, totalBatches);\n for (let i = 0; i < firstWave; i++) {\n this.dispatchMapTask(job, i);\n }\n\n return { success: true, data: { jobId: job.id } };\n }\n\n @Actions.Exec(\"/\", \"cancel-job\", \"Cancel a map-reduce job\", { effect: \"write\" })\n async execCancelJob(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n await this.persistence.ensureReady();\n\n const parsed = CancelJobArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: parsed.error.message },\n };\n }\n\n // Delegate to Coordinator DO when configured\n if (this.jobExecutor) {\n try {\n await this.jobExecutor.cancelJob(parsed.data.jobId);\n return { success: true };\n } catch (err) {\n return {\n success: false,\n error: { code: \"COORDINATOR_ERROR\", message: String(err) },\n };\n }\n }\n\n // In-memory coordination\n const job = this.store.getJob(parsed.data.jobId);\n if (!job) {\n return {\n success: false,\n error: { code: \"NOT_FOUND\", message: `Job ${parsed.data.jobId} not found` },\n };\n }\n\n // Cancel all active map/reduce tasks belonging to this job\n let cancelledTasks = 0;\n for (const task of this.store.listActive()) {\n if (task.jobId === job.id && (task.status === \"pending\" || task.status === \"running\")) {\n this.store.update(task.id, { status: \"cancelled\", ended: Date.now() });\n this.store.moveToHistory(task.id);\n cancelledTasks++;\n }\n }\n\n this.store.updateJob(job.id, {\n status: \"cancelled\",\n ended: Date.now(),\n });\n this.persistence.schedulePersist();\n\n return { success: true, data: { cancelledTasks } };\n }\n\n // ─── tick() — cron dispatch + timeout scanner ───\n\n /**\n * Evaluate cron schedules and scan for expired deadlines.\n * Call from Worker `scheduled()` handler or `setInterval` in non-Worker environments.\n */\n async tick(): Promise<TickResult> {\n await this.persistence.ensureReady();\n const now = Date.now();\n let dispatched = 0;\n let timedOut = 0;\n let scheduleAdvanced = false;\n\n // 1. Reap timed-out active tasks before schedule duplicate protection runs.\n // This lets a stale cron task free its schedule and be\n // re-dispatched in the same tick instead of blocking until the next one.\n for (const task of this.store.listActive()) {\n const isScheduledTask = Boolean(task.scheduleId);\n if (task.status !== \"running\" && !(isScheduledTask && task.status === \"pending\")) continue;\n // Claim-based tasks (#1067) use lazy lease-expiry reclaim inside the\n // `claim` action itself — an expired lease just makes the task\n // claimable again for the next worker. Do NOT terminalize them here;\n // that would race with a worker still finishing up under an\n // about-to-be-superseded lease.\n if (task.claimable) continue;\n const deadline = this.getEffectiveDeadline(task);\n if (deadline == null) continue;\n if (deadline > now) continue;\n\n this.store.update(task.id, {\n status: \"timeout\",\n error: `Task timed out (deadline exceeded by ${now - deadline}ms)`,\n ended: now,\n });\n\n if (task.procId) {\n await this.completeProc(task.procId, \"failed\");\n }\n\n this.store.moveToHistory(task.id);\n timedOut++;\n }\n\n // 2. Evaluate cron schedules\n for (const schedule of this.store.listSchedules()) {\n if (!schedule.enabled) continue;\n if (!schedule.nextRun || schedule.nextRun > now) continue;\n\n // Duplicate protection: skip if there's already an active task for this schedule\n const activeForSchedule = this.store\n .listActive()\n .some(\n (t) => t.scheduleId === schedule.id && (t.status === \"pending\" || t.status === \"running\"),\n );\n if (activeForSchedule) {\n // Update nextRun so we don't keep re-checking\n this.store.recordScheduleRun(schedule.id, now);\n scheduleAdvanced = true;\n continue;\n }\n\n // Dispatch task for this schedule\n const entry = this.store.create(schedule.taskPath, schedule.args, undefined, undefined, {\n ownerKey: schedule.ownerKey,\n scheduleId: schedule.id,\n });\n const scheduledTaskTimeoutMs = this.getScheduledTaskTimeoutMs();\n if (scheduledTaskTimeoutMs > 0) {\n this.store.update(entry.id, {\n deadline: now + scheduledTaskTimeoutMs,\n timeout: scheduledTaskTimeoutMs,\n });\n }\n this.store.recordScheduleRun(schedule.id, now);\n\n // Fire-and-forget execution\n const taskPromise = this.executeTask(entry.id).catch((err) => {\n log.error(\"[scheduler] executeTask failed:\", err);\n });\n this.options.waitUntil?.(taskPromise);\n dispatched++;\n }\n\n if (dispatched > 0 || timedOut > 0 || scheduleAdvanced) {\n this.persistence.schedulePersist();\n }\n\n return { dispatched, timedOut };\n }\n\n private getScheduledTaskTimeoutMs(): number {\n return this.options.scheduledTaskTimeoutMs ?? DEFAULT_SCHEDULED_TASK_TIMEOUT_MS;\n }\n\n private getEffectiveDeadline(task: TaskEntry): number | undefined {\n if (task.deadline != null) return task.deadline;\n if (!task.scheduleId) return undefined;\n const scheduledTaskTimeoutMs = this.getScheduledTaskTimeoutMs();\n if (scheduledTaskTimeoutMs <= 0) return undefined;\n return (task.started ?? task.created) + scheduledTaskTimeoutMs;\n }\n\n // ─── Internal ───\n\n private getCallerKey(ctx: RouteContext): string | undefined {\n const context = ctx.context;\n const userId = typeof context?.userId === \"string\" ? context.userId : undefined;\n const sessionId = typeof context?.sessionId === \"string\" ? context.sessionId : undefined;\n return userId ?? sessionId;\n }\n\n private checkTaskOwner(entry: TaskEntry, ctx: RouteContext): AFSExecResult | undefined {\n if (!entry.ownerKey) return undefined;\n if (entry.ownerKey === this.getCallerKey(ctx)) return undefined;\n return {\n success: false,\n error: {\n code: \"PERMISSION_DENIED\",\n message: `Task ${entry.id} is owned by a different caller`,\n },\n };\n }\n\n private checkScheduleOwner(\n schedule: { id: string; ownerKey?: string },\n ctx: RouteContext,\n ): AFSExecResult | undefined {\n if (!schedule.ownerKey) return undefined;\n if (schedule.ownerKey === this.getCallerKey(ctx)) return undefined;\n return {\n success: false,\n error: {\n code: \"PERMISSION_DENIED\",\n message: `Schedule ${schedule.id} is owned by a different caller`,\n },\n };\n }\n\n private async executeTask(taskId: string): Promise<void> {\n const entry = this.store.get(taskId);\n if (!entry) return;\n\n // Budget gate: check budget before running if proc is associated\n if (entry.procId) {\n const budgetCheck = await this.checkTaskBudget(entry.procId, entry.estimate);\n if (!budgetCheck.allowed) {\n this.store.update(taskId, {\n status: \"budget-blocked\",\n error: `Budget check failed: ${budgetCheck.reason}`,\n });\n this.persistence.schedulePersist();\n // Await the write — see the flush() at the end of executeTask for why a\n // merely-scheduled persist can be dropped when this runs on waitUntil.\n await this.persistence.flush();\n return;\n }\n }\n\n this.store.update(taskId, {\n status: \"running\",\n started: Date.now(),\n attempts: entry.attempts + 1,\n });\n this.persistence.schedulePersist();\n\n const context: TaskContext | undefined = entry.procId ? { procId: entry.procId } : undefined;\n\n // Auto-inject job_dir so scripts don't need to derive it from the task ID.\n let taskArgs = entry.args;\n if (taskArgs && typeof taskArgs === \"object\" && !Array.isArray(taskArgs)) {\n const a = taskArgs as Record<string, unknown>;\n if (!a.job_dir) {\n taskArgs = { ...a, job_dir: `/data/jobs/${taskId}` };\n }\n }\n\n try {\n const result = await this.executor.run(taskId, entry.taskPath, taskArgs, context);\n\n // Race guard: cancel may have moved task to history during execution\n const current = this.store.get(taskId);\n if (!current || current.status !== \"running\") return;\n\n this.store.update(taskId, { status: \"done\", result, ended: Date.now() });\n } catch (err) {\n // Race guard\n const current = this.store.get(taskId);\n if (!current || current.status !== \"running\") return;\n\n this.store.update(taskId, {\n status: \"failed\",\n error: String(err),\n ended: Date.now(),\n });\n }\n\n // Complete proc if procId exists\n const finalEntry = this.store.get(taskId);\n if (finalEntry?.procId) {\n await this.completeProc(finalEntry.procId, finalEntry.status === \"done\" ? \"done\" : \"failed\");\n }\n\n this.store.moveToHistory(taskId);\n\n // Job completion hook: if this task belongs to a job, update job state\n const completedEntry = this.store.getFromHistory(taskId);\n if (completedEntry?.jobId) {\n await this.onJobTaskComplete(completedEntry);\n }\n\n this.persistence.schedulePersist();\n // Durably land the terminal state (done/failed → history) BEFORE returning.\n // executeTask is dispatched fire-and-forget onto the runtime's waitUntil\n // (tick()/execDispatch/execRetry); on Cloudflare that waitUntil belongs to the\n // scheduled() invocation, and the isolate can be torn down the moment this\n // resolves — a still-pending schedulePersist() write is then dropped, leaving\n // the task stuck showing \"running\" in the persisted snapshot. (Its done-write\n // otherwise only lands via a LATER tick's flush, so the last task before cron\n // delivery stalls looks wedged when it actually completed in seconds.)\n await this.persistence.flush();\n }\n\n private async checkTaskBudget(\n procId: string,\n estimate?: { tokens?: number; duration?: number; calls?: number },\n ): Promise<{ allowed: boolean; reason?: string }> {\n const procPath = this.options.procPath ?? \"/proc\";\n try {\n if (!this.root.exec) {\n return { allowed: false, reason: \"AFS root does not support exec\" };\n }\n const args: Record<string, unknown> = { id: procId };\n if (estimate) args.estimate = estimate;\n const result = await this.root.exec(joinURL(procPath, \".actions\", \"check-budget\"), args, {});\n if (!result.success) {\n return { allowed: false, reason: result.error?.message ?? \"Budget check failed\" };\n }\n const data = result.data as { allowed: boolean; reason?: string };\n return { allowed: data.allowed, reason: data.reason };\n } catch (err) {\n // Infrastructure failure — fail-closed\n return { allowed: false, reason: `Budget check error: ${String(err)}` };\n }\n }\n\n private async completeProc(procId: string, status: \"done\" | \"failed\"): Promise<void> {\n const procPath = this.options.procPath ?? \"/proc\";\n try {\n if (this.root.exec) {\n await this.root.exec(joinURL(procPath, \".actions\", \"complete\"), { id: procId, status }, {});\n }\n } catch {\n // Best-effort: proc.complete failure = log + ignore\n }\n }\n\n // ─── Map-Reduce dispatch helpers ───\n\n private dispatchMapTask(job: JobEntry, batchIndex: number): void {\n const batchStart = batchIndex * job.batchSize;\n const batchEnd = Math.min(batchStart + job.batchSize, job.totalItems);\n\n const taskArgs: Record<string, unknown> = {\n ...((job.args as Record<string, unknown>) ?? {}),\n jobId: job.id,\n batchIndex,\n batchSize: job.batchSize,\n batchStart,\n batchEnd,\n totalItems: job.totalItems,\n };\n\n const entry = this.store.create(job.mapTask, taskArgs, undefined, undefined, {\n jobId: job.id,\n batchIndex,\n });\n\n job.mapTaskIds[batchIndex] = entry.id;\n job.progress.pending--;\n job.progress.running++;\n\n const taskPromise = this.executeTask(entry.id).catch((err) => {\n log.error(\"[scheduler] executeTask failed:\", err);\n });\n this.options.waitUntil?.(taskPromise);\n }\n\n private async onJobTaskComplete(task: TaskEntry): Promise<void> {\n const job = this.store.getJob(task.jobId!);\n if (!job || job.status === \"cancelled\") return;\n\n // Check if this is the reduce task\n if (job.reduceTaskId === task.id) {\n if (task.status === \"done\" && task.result) {\n this.store.updateJob(job.id, {\n status: \"done\",\n result: task.result.data,\n ended: Date.now(),\n });\n } else {\n this.store.updateJob(job.id, {\n status: \"failed\",\n error: task.error ?? \"Reduce task failed\",\n ended: Date.now(),\n });\n }\n this.persistence.schedulePersist();\n return;\n }\n\n // This is a map task\n const batchIndex = task.batchIndex!;\n const taskSucceeded = task.status === \"done\" && task.result?.success !== false;\n\n if (taskSucceeded && task.result) {\n job.mapResults[batchIndex] = task.result.data;\n } else {\n job.mapResults[batchIndex] = null;\n }\n\n // Update progress\n job.progress.running = Math.max(0, job.progress.running - 1);\n if (taskSucceeded) {\n job.progress.done++;\n } else {\n job.progress.failed++;\n }\n\n // Dispatch next pending batch if any\n const nextBatch = this.store.nextPendingBatch(job.id);\n if (nextBatch >= 0) {\n this.dispatchMapTask(job, nextBatch);\n }\n\n // Check if all batches are complete\n const allDone = job.progress.done + job.progress.failed === job.totalBatches;\n if (allDone) {\n if (job.reduceTask) {\n // Dispatch reduce task\n job.status = \"reducing\";\n const reduceArgs: Record<string, unknown> = {\n ...((job.args as Record<string, unknown>) ?? {}),\n jobId: job.id,\n mapResults: job.mapResults,\n totalBatches: job.totalBatches,\n };\n const reduceEntry = this.store.create(job.reduceTask, reduceArgs, undefined, undefined, {\n jobId: job.id,\n });\n job.reduceTaskId = reduceEntry.id;\n\n const reducePromise = this.executeTask(reduceEntry.id).catch((err) => {\n log.error(\"[scheduler] executeTask failed:\", err);\n });\n this.options.waitUntil?.(reducePromise);\n } else {\n // No reduce — job is done\n this.store.updateJob(job.id, {\n status: \"done\",\n result: job.mapResults,\n ended: Date.now(),\n });\n }\n }\n\n this.persistence.schedulePersist();\n }\n\n private readEntryField(path: string, entry: TaskEntry, field: string): AFSEntry | undefined {\n let content: unknown;\n switch (field) {\n case \"status\":\n content = entry.status;\n break;\n case \"result\":\n content = entry.result ?? null;\n break;\n case \"error\":\n content = entry.error ?? null;\n break;\n case \"task\":\n content = { taskPath: entry.taskPath, args: entry.args };\n break;\n case \"created\":\n content = entry.created;\n break;\n case \"started\":\n content = entry.started ?? null;\n break;\n case \"ended\":\n content = entry.ended ?? null;\n break;\n case \"attempts\":\n content = entry.attempts;\n break;\n default:\n return undefined;\n }\n return this.buildEntry(path, {\n content,\n meta: { kind: `scheduler:task:${field}` },\n });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+
2
+ //#region src/task-runner.ts
3
+ /**
4
+ * Handle a task execution request inside a Durable Object.
5
+ *
6
+ * This is the DO-side counterpart to DOExecutor. It receives HTTP requests,
7
+ * executes AFS tasks, and returns results.
8
+ *
9
+ * Usage in a CF Worker Durable Object:
10
+ * ```typescript
11
+ * export class TaskRunnerDO implements DurableObject {
12
+ * constructor(private state: DurableObjectState, private env: Env) {}
13
+ *
14
+ * async fetch(request: Request): Promise<Response> {
15
+ * const afs = createWorkerAFS(this.env);
16
+ * return handleTaskRequest(request, afs);
17
+ * }
18
+ * }
19
+ * ```
20
+ */
21
+ async function handleTaskRequest(request, afs) {
22
+ const url = new URL(request.url);
23
+ if (url.pathname === "/run" && request.method === "POST") return handleRun(request, afs);
24
+ if (url.pathname === "/cancel" && request.method === "POST") return Response.json({ cancelled: true });
25
+ return new Response("Not found", { status: 404 });
26
+ }
27
+ async function handleRun(request, afs) {
28
+ let body;
29
+ try {
30
+ body = await request.json();
31
+ } catch {
32
+ return jsonResponse({
33
+ success: false,
34
+ error: "Invalid JSON body"
35
+ }, 400);
36
+ }
37
+ const { taskPath, args, context } = body;
38
+ if (!taskPath || typeof taskPath !== "string") return jsonResponse({
39
+ success: false,
40
+ error: "Missing or invalid taskPath"
41
+ }, 400);
42
+ if (!afs.exec) return jsonResponse({
43
+ success: false,
44
+ error: "AFS does not support exec"
45
+ }, 500);
46
+ try {
47
+ const execOptions = {};
48
+ if (context) execOptions.context = context;
49
+ const result = await afs.exec(taskPath, args ?? {}, execOptions);
50
+ return jsonResponse({
51
+ success: result.success,
52
+ data: result.data,
53
+ error: result.error?.message
54
+ });
55
+ } catch (err) {
56
+ return jsonResponse({
57
+ success: false,
58
+ error: String(err)
59
+ }, 500);
60
+ }
61
+ }
62
+ function jsonResponse(body, status = 200) {
63
+ return new Response(JSON.stringify(body), {
64
+ status,
65
+ headers: { "Content-Type": "application/json" }
66
+ });
67
+ }
68
+
69
+ //#endregion
70
+ exports.handleTaskRequest = handleTaskRequest;
@@ -0,0 +1,25 @@
1
+ import { AFSRoot } from "@aigne/afs";
2
+
3
+ //#region src/task-runner.d.ts
4
+ /**
5
+ * Handle a task execution request inside a Durable Object.
6
+ *
7
+ * This is the DO-side counterpart to DOExecutor. It receives HTTP requests,
8
+ * executes AFS tasks, and returns results.
9
+ *
10
+ * Usage in a CF Worker Durable Object:
11
+ * ```typescript
12
+ * export class TaskRunnerDO implements DurableObject {
13
+ * constructor(private state: DurableObjectState, private env: Env) {}
14
+ *
15
+ * async fetch(request: Request): Promise<Response> {
16
+ * const afs = createWorkerAFS(this.env);
17
+ * return handleTaskRequest(request, afs);
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ declare function handleTaskRequest(request: Request, afs: AFSRoot): Promise<Response>;
23
+ //#endregion
24
+ export { handleTaskRequest };
25
+ //# sourceMappingURL=task-runner.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-runner.d.cts","names":[],"sources":["../src/task-runner.ts"],"mappings":";;;;;AAqBA;;;;;;;;;;;;;;;;iBAAsB,iBAAA,CAAkB,OAAA,EAAS,OAAA,EAAS,GAAA,EAAK,OAAA,GAAU,OAAA,CAAQ,QAAA"}
@@ -0,0 +1,25 @@
1
+ import { AFSRoot } from "@aigne/afs";
2
+
3
+ //#region src/task-runner.d.ts
4
+ /**
5
+ * Handle a task execution request inside a Durable Object.
6
+ *
7
+ * This is the DO-side counterpart to DOExecutor. It receives HTTP requests,
8
+ * executes AFS tasks, and returns results.
9
+ *
10
+ * Usage in a CF Worker Durable Object:
11
+ * ```typescript
12
+ * export class TaskRunnerDO implements DurableObject {
13
+ * constructor(private state: DurableObjectState, private env: Env) {}
14
+ *
15
+ * async fetch(request: Request): Promise<Response> {
16
+ * const afs = createWorkerAFS(this.env);
17
+ * return handleTaskRequest(request, afs);
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ declare function handleTaskRequest(request: Request, afs: AFSRoot): Promise<Response>;
23
+ //#endregion
24
+ export { handleTaskRequest };
25
+ //# sourceMappingURL=task-runner.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-runner.d.mts","names":[],"sources":["../src/task-runner.ts"],"mappings":";;;;;AAqBA;;;;;;;;;;;;;;;;iBAAsB,iBAAA,CAAkB,OAAA,EAAS,OAAA,EAAS,GAAA,EAAK,OAAA,GAAU,OAAA,CAAQ,QAAA"}
@@ -0,0 +1,70 @@
1
+ //#region src/task-runner.ts
2
+ /**
3
+ * Handle a task execution request inside a Durable Object.
4
+ *
5
+ * This is the DO-side counterpart to DOExecutor. It receives HTTP requests,
6
+ * executes AFS tasks, and returns results.
7
+ *
8
+ * Usage in a CF Worker Durable Object:
9
+ * ```typescript
10
+ * export class TaskRunnerDO implements DurableObject {
11
+ * constructor(private state: DurableObjectState, private env: Env) {}
12
+ *
13
+ * async fetch(request: Request): Promise<Response> {
14
+ * const afs = createWorkerAFS(this.env);
15
+ * return handleTaskRequest(request, afs);
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ async function handleTaskRequest(request, afs) {
21
+ const url = new URL(request.url);
22
+ if (url.pathname === "/run" && request.method === "POST") return handleRun(request, afs);
23
+ if (url.pathname === "/cancel" && request.method === "POST") return Response.json({ cancelled: true });
24
+ return new Response("Not found", { status: 404 });
25
+ }
26
+ async function handleRun(request, afs) {
27
+ let body;
28
+ try {
29
+ body = await request.json();
30
+ } catch {
31
+ return jsonResponse({
32
+ success: false,
33
+ error: "Invalid JSON body"
34
+ }, 400);
35
+ }
36
+ const { taskPath, args, context } = body;
37
+ if (!taskPath || typeof taskPath !== "string") return jsonResponse({
38
+ success: false,
39
+ error: "Missing or invalid taskPath"
40
+ }, 400);
41
+ if (!afs.exec) return jsonResponse({
42
+ success: false,
43
+ error: "AFS does not support exec"
44
+ }, 500);
45
+ try {
46
+ const execOptions = {};
47
+ if (context) execOptions.context = context;
48
+ const result = await afs.exec(taskPath, args ?? {}, execOptions);
49
+ return jsonResponse({
50
+ success: result.success,
51
+ data: result.data,
52
+ error: result.error?.message
53
+ });
54
+ } catch (err) {
55
+ return jsonResponse({
56
+ success: false,
57
+ error: String(err)
58
+ }, 500);
59
+ }
60
+ }
61
+ function jsonResponse(body, status = 200) {
62
+ return new Response(JSON.stringify(body), {
63
+ status,
64
+ headers: { "Content-Type": "application/json" }
65
+ });
66
+ }
67
+
68
+ //#endregion
69
+ export { handleTaskRequest };
70
+ //# sourceMappingURL=task-runner.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-runner.mjs","names":[],"sources":["../src/task-runner.ts"],"sourcesContent":["import type { AFSRoot } from \"@aigne/afs\";\nimport type { TaskRunRequest, TaskRunResponse } from \"./do-types.js\";\n\n/**\n * Handle a task execution request inside a Durable Object.\n *\n * This is the DO-side counterpart to DOExecutor. It receives HTTP requests,\n * executes AFS tasks, and returns results.\n *\n * Usage in a CF Worker Durable Object:\n * ```typescript\n * export class TaskRunnerDO implements DurableObject {\n * constructor(private state: DurableObjectState, private env: Env) {}\n *\n * async fetch(request: Request): Promise<Response> {\n * const afs = createWorkerAFS(this.env);\n * return handleTaskRequest(request, afs);\n * }\n * }\n * ```\n */\nexport async function handleTaskRequest(request: Request, afs: AFSRoot): Promise<Response> {\n const url = new URL(request.url);\n\n if (url.pathname === \"/run\" && request.method === \"POST\") {\n return handleRun(request, afs);\n }\n\n if (url.pathname === \"/cancel\" && request.method === \"POST\") {\n return Response.json({ cancelled: true });\n }\n\n return new Response(\"Not found\", { status: 404 });\n}\n\nasync function handleRun(request: Request, afs: AFSRoot): Promise<Response> {\n let body: TaskRunRequest;\n try {\n body = await request.json();\n } catch {\n return jsonResponse({ success: false, error: \"Invalid JSON body\" }, 400);\n }\n\n const { taskPath, args, context } = body;\n\n if (!taskPath || typeof taskPath !== \"string\") {\n return jsonResponse({ success: false, error: \"Missing or invalid taskPath\" }, 400);\n }\n\n if (!afs.exec) {\n return jsonResponse({ success: false, error: \"AFS does not support exec\" }, 500);\n }\n\n try {\n const execOptions: Record<string, unknown> = {};\n if (context) {\n execOptions.context = context;\n }\n\n const result = await afs.exec(taskPath, (args ?? {}) as Record<string, unknown>, execOptions);\n\n return jsonResponse({\n success: result.success,\n data: result.data,\n error: result.error?.message,\n });\n } catch (err) {\n return jsonResponse({ success: false, error: String(err) }, 500);\n }\n}\n\nfunction jsonResponse(body: TaskRunResponse, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,eAAsB,kBAAkB,SAAkB,KAAiC;CACzF,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;AAEhC,KAAI,IAAI,aAAa,UAAU,QAAQ,WAAW,OAChD,QAAO,UAAU,SAAS,IAAI;AAGhC,KAAI,IAAI,aAAa,aAAa,QAAQ,WAAW,OACnD,QAAO,SAAS,KAAK,EAAE,WAAW,MAAM,CAAC;AAG3C,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;;AAGnD,eAAe,UAAU,SAAkB,KAAiC;CAC1E,IAAI;AACJ,KAAI;AACF,SAAO,MAAM,QAAQ,MAAM;SACrB;AACN,SAAO,aAAa;GAAE,SAAS;GAAO,OAAO;GAAqB,EAAE,IAAI;;CAG1E,MAAM,EAAE,UAAU,MAAM,YAAY;AAEpC,KAAI,CAAC,YAAY,OAAO,aAAa,SACnC,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAA+B,EAAE,IAAI;AAGpF,KAAI,CAAC,IAAI,KACP,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAA6B,EAAE,IAAI;AAGlF,KAAI;EACF,MAAM,cAAuC,EAAE;AAC/C,MAAI,QACF,aAAY,UAAU;EAGxB,MAAM,SAAS,MAAM,IAAI,KAAK,UAAW,QAAQ,EAAE,EAA8B,YAAY;AAE7F,SAAO,aAAa;GAClB,SAAS,OAAO;GAChB,MAAM,OAAO;GACb,OAAO,OAAO,OAAO;GACtB,CAAC;UACK,KAAK;AACZ,SAAO,aAAa;GAAE,SAAS;GAAO,OAAO,OAAO,IAAI;GAAE,EAAE,IAAI;;;AAIpE,SAAS,aAAa,MAAuB,SAAS,KAAe;AACnE,QAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE;EACxC;EACA,SAAS,EAAE,gBAAgB,oBAAoB;EAChD,CAAC"}
@@ -0,0 +1,186 @@
1
+ const require_cron_parser = require('./cron-parser.cjs');
2
+ let _aigne_uuid = require("@aigne/uuid");
3
+
4
+ //#region src/task-store.ts
5
+ const DEFAULT_BUDGET_BLOCKED_TTL = 864e5;
6
+ var TaskStore = class TaskStore {
7
+ tasks = /* @__PURE__ */ new Map();
8
+ historyList = [];
9
+ schedules = /* @__PURE__ */ new Map();
10
+ constructor(maxHistory = 100, budgetBlockedTTL = DEFAULT_BUDGET_BLOCKED_TTL) {
11
+ this.maxHistory = maxHistory;
12
+ this.budgetBlockedTTL = budgetBlockedTTL;
13
+ }
14
+ create(taskPath, args, procId, estimate, extras) {
15
+ const entry = {
16
+ id: (0, _aigne_uuid.v7)(),
17
+ status: "pending",
18
+ taskPath,
19
+ args,
20
+ estimate,
21
+ created: Date.now(),
22
+ attempts: 0,
23
+ procId,
24
+ ownerKey: extras?.ownerKey,
25
+ scheduleId: extras?.scheduleId,
26
+ jobId: extras?.jobId,
27
+ batchIndex: extras?.batchIndex,
28
+ claimable: extras?.claimable,
29
+ assignee: extras?.assignee,
30
+ version: (0, _aigne_uuid.v7)()
31
+ };
32
+ this.tasks.set(entry.id, entry);
33
+ return entry;
34
+ }
35
+ get(id) {
36
+ return this.tasks.get(id);
37
+ }
38
+ getFromHistory(id) {
39
+ return this.historyList.find((e) => e.id === id);
40
+ }
41
+ getAny(id) {
42
+ return this.tasks.get(id) ?? this.historyList.find((e) => e.id === id);
43
+ }
44
+ /** Fields that must never be overwritten via update() */
45
+ static IMMUTABLE_FIELDS = new Set(["id", "taskPath"]);
46
+ update(id, patch) {
47
+ const entry = this.tasks.get(id);
48
+ if (!entry) throw new Error(`Task ${id} not found in active store`);
49
+ const safePatch = { ...patch };
50
+ for (const key of TaskStore.IMMUTABLE_FIELDS) delete safePatch[key];
51
+ Object.assign(entry, safePatch);
52
+ }
53
+ listActive() {
54
+ return [...this.tasks.values()];
55
+ }
56
+ listHistory() {
57
+ return [...this.historyList];
58
+ }
59
+ moveToHistory(id) {
60
+ const entry = this.tasks.get(id);
61
+ if (!entry) return;
62
+ this.tasks.delete(id);
63
+ this.historyList.push(entry);
64
+ while (this.historyList.length > this.maxHistory) this.historyList.shift();
65
+ }
66
+ restoreFromHistory(id) {
67
+ const idx = this.historyList.findIndex((e) => e.id === id);
68
+ if (idx === -1) return void 0;
69
+ const entry = this.historyList[idx];
70
+ this.historyList.splice(idx, 1);
71
+ this.tasks.set(entry.id, entry);
72
+ return entry;
73
+ }
74
+ addSchedule(opts) {
75
+ require_cron_parser.parseCron(opts.cron);
76
+ for (const existing of this.schedules.values()) if (existing.name === opts.name) throw new Error(`Schedule "${opts.name}" already exists`);
77
+ const now = /* @__PURE__ */ new Date();
78
+ const entry = {
79
+ id: (0, _aigne_uuid.v7)(),
80
+ name: opts.name,
81
+ cron: opts.cron,
82
+ taskPath: opts.taskPath,
83
+ args: opts.args,
84
+ enabled: opts.enabled ?? true,
85
+ created: Date.now(),
86
+ ownerKey: opts.ownerKey,
87
+ nextRun: require_cron_parser.nextRun(opts.cron, now).getTime()
88
+ };
89
+ this.schedules.set(entry.id, entry);
90
+ return entry;
91
+ }
92
+ getSchedule(id) {
93
+ return this.schedules.get(id);
94
+ }
95
+ getScheduleByName(name) {
96
+ for (const entry of this.schedules.values()) if (entry.name === name) return entry;
97
+ }
98
+ listSchedules() {
99
+ return [...this.schedules.values()];
100
+ }
101
+ updateSchedule(id, patch) {
102
+ const entry = this.schedules.get(id);
103
+ if (!entry) throw new Error(`Schedule ${id} not found`);
104
+ const safePatch = { ...patch };
105
+ delete safePatch.id;
106
+ Object.assign(entry, safePatch);
107
+ if (patch.cron) {
108
+ require_cron_parser.parseCron(patch.cron);
109
+ entry.nextRun = require_cron_parser.nextRun(patch.cron, /* @__PURE__ */ new Date()).getTime();
110
+ }
111
+ }
112
+ removeSchedule(id) {
113
+ return this.schedules.delete(id);
114
+ }
115
+ recordScheduleRun(id, now) {
116
+ const entry = this.schedules.get(id);
117
+ if (!entry) return;
118
+ entry.lastRun = now;
119
+ entry.nextRun = require_cron_parser.nextRun(entry.cron, new Date(now)).getTime();
120
+ }
121
+ jobs = /* @__PURE__ */ new Map();
122
+ addJob(job) {
123
+ this.jobs.set(job.id, job);
124
+ }
125
+ getJob(id) {
126
+ return this.jobs.get(id);
127
+ }
128
+ listJobs() {
129
+ return [...this.jobs.values()];
130
+ }
131
+ updateJob(id, patch) {
132
+ const job = this.jobs.get(id);
133
+ if (!job) throw new Error(`Job ${id} not found`);
134
+ const safePatch = { ...patch };
135
+ delete safePatch.id;
136
+ Object.assign(job, safePatch);
137
+ }
138
+ removeJob(id) {
139
+ return this.jobs.delete(id);
140
+ }
141
+ /** Get the next pending batch index for a job, or -1 if none */
142
+ nextPendingBatch(jobId) {
143
+ const job = this.jobs.get(jobId);
144
+ if (!job) return -1;
145
+ for (let i = 0; i < job.totalBatches; i++) if (job.mapTaskIds[i] === "") return i;
146
+ return -1;
147
+ }
148
+ snapshot() {
149
+ const tasks = {};
150
+ for (const [id, entry] of this.tasks) tasks[id] = { ...entry };
151
+ const schedules = this.listSchedules().map((s) => ({ ...s }));
152
+ const jobs = this.listJobs().map((j) => ({ ...j }));
153
+ return {
154
+ version: 3,
155
+ tasks,
156
+ history: this.historyList.map((e) => ({ ...e })),
157
+ schedules,
158
+ jobs
159
+ };
160
+ }
161
+ restore(snapshot, now) {
162
+ this.tasks.clear();
163
+ this.historyList = [];
164
+ this.schedules.clear();
165
+ this.jobs.clear();
166
+ for (const [_id, entry] of Object.entries(snapshot.tasks)) if (entry.status === "running" || entry.status === "pending") {
167
+ entry.status = "failed";
168
+ entry.error = "Stale: recovered after restart";
169
+ entry.ended = now;
170
+ this.historyList.push(entry);
171
+ } else if (entry.status === "budget-blocked") if (this.budgetBlockedTTL > 0 && now - entry.created > this.budgetBlockedTTL) {
172
+ entry.status = "failed";
173
+ entry.error = "Budget-blocked task evicted: exceeded staleness TTL";
174
+ entry.ended = now;
175
+ this.historyList.push(entry);
176
+ } else this.tasks.set(entry.id, entry);
177
+ else if (entry.status === "done" || entry.status === "failed" || entry.status === "cancelled" || entry.status === "timeout") this.historyList.push(entry);
178
+ for (const entry of snapshot.history) this.historyList.push(entry);
179
+ while (this.historyList.length > this.maxHistory) this.historyList.shift();
180
+ if (snapshot.schedules) for (const schedule of snapshot.schedules) this.schedules.set(schedule.id, { ...schedule });
181
+ if (snapshot.jobs) for (const job of snapshot.jobs) this.jobs.set(job.id, { ...job });
182
+ }
183
+ };
184
+
185
+ //#endregion
186
+ exports.TaskStore = TaskStore;
@@ -0,0 +1,60 @@
1
+ import { BudgetEstimate, JobEntry, ScheduleEntry, SchedulerSnapshot, TaskEntry } from "./types.cjs";
2
+
3
+ //#region src/task-store.d.ts
4
+ /** Extra fields for task creation */
5
+ interface CreateExtras {
6
+ ownerKey?: string;
7
+ scheduleId?: string;
8
+ jobId?: string;
9
+ batchIndex?: number;
10
+ /** Claim-based (pull model) task — see #1067. */
11
+ claimable?: boolean;
12
+ /** Claim constraint: "" = any worker, DID = only that worker. Only meaningful when `claimable`. */
13
+ assignee?: string;
14
+ }
15
+ declare class TaskStore {
16
+ private maxHistory;
17
+ private budgetBlockedTTL;
18
+ private tasks;
19
+ private historyList;
20
+ private schedules;
21
+ constructor(maxHistory?: number, budgetBlockedTTL?: number);
22
+ create(taskPath: string, args?: unknown, procId?: string, estimate?: BudgetEstimate, extras?: CreateExtras): TaskEntry;
23
+ get(id: string): TaskEntry | undefined;
24
+ getFromHistory(id: string): TaskEntry | undefined;
25
+ getAny(id: string): TaskEntry | undefined;
26
+ /** Fields that must never be overwritten via update() */
27
+ private static readonly IMMUTABLE_FIELDS;
28
+ update(id: string, patch: Partial<TaskEntry>): void;
29
+ listActive(): TaskEntry[];
30
+ listHistory(): TaskEntry[];
31
+ moveToHistory(id: string): void;
32
+ restoreFromHistory(id: string): TaskEntry | undefined;
33
+ addSchedule(opts: {
34
+ name: string;
35
+ cron: string;
36
+ taskPath: string;
37
+ args?: unknown;
38
+ enabled?: boolean;
39
+ ownerKey?: string;
40
+ }): ScheduleEntry;
41
+ getSchedule(id: string): ScheduleEntry | undefined;
42
+ getScheduleByName(name: string): ScheduleEntry | undefined;
43
+ listSchedules(): ScheduleEntry[];
44
+ updateSchedule(id: string, patch: Partial<ScheduleEntry>): void;
45
+ removeSchedule(id: string): boolean;
46
+ recordScheduleRun(id: string, now: number): void;
47
+ private jobs;
48
+ addJob(job: JobEntry): void;
49
+ getJob(id: string): JobEntry | undefined;
50
+ listJobs(): JobEntry[];
51
+ updateJob(id: string, patch: Partial<JobEntry>): void;
52
+ removeJob(id: string): boolean;
53
+ /** Get the next pending batch index for a job, or -1 if none */
54
+ nextPendingBatch(jobId: string): number;
55
+ snapshot(): SchedulerSnapshot;
56
+ restore(snapshot: SchedulerSnapshot, now: number): void;
57
+ }
58
+ //#endregion
59
+ export { TaskStore };
60
+ //# sourceMappingURL=task-store.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-store.d.cts","names":[],"sources":["../src/task-store.ts"],"mappings":";;;;UAaU,YAAA;EACR,QAAA;EACA,UAAA;EACA,KAAA;EACA,UAAA;EAHA;EAKA,SAAA;EAHA;EAKA,QAAA;AAAA;AAAA,cAGW,SAAA;EAAA,QAMD,UAAA;EAAA,QACA,gBAAA;EAAA,QANF,KAAA;EAAA,QACA,WAAA;EAAA,QACA,SAAA;cAGE,UAAA,WACA,gBAAA;EAGV,MAAA,CACE,QAAA,UACA,IAAA,YACA,MAAA,WACA,QAAA,GAAW,cAAA,EACX,MAAA,GAAS,YAAA,GACR,SAAA;EAsBH,GAAA,CAAI,EAAA,WAAa,SAAA;EAIjB,cAAA,CAAe,EAAA,WAAa,SAAA;EAI5B,MAAA,CAAO,EAAA,WAAa,SAAA;EAAA;EAAA,wBAKI,gBAAA;EAExB,MAAA,CAAO,EAAA,UAAY,KAAA,EAAO,OAAA,CAAQ,SAAA;EAUlC,UAAA,CAAA,GAAc,SAAA;EAId,WAAA,CAAA,GAAe,SAAA;EAIf,aAAA,CAAc,EAAA;EAUd,kBAAA,CAAmB,EAAA,WAAa,SAAA;EAahC,WAAA,CAAY,IAAA;IACV,IAAA;IACA,IAAA;IACA,QAAA;IACA,IAAA;IACA,OAAA;IACA,QAAA;EAAA,IACE,aAAA;EA2BJ,WAAA,CAAY,EAAA,WAAa,aAAA;EAIzB,iBAAA,CAAkB,IAAA,WAAe,aAAA;EAOjC,aAAA,CAAA,GAAiB,aAAA;EAIjB,cAAA,CAAe,EAAA,UAAY,KAAA,EAAO,OAAA,CAAQ,aAAA;EAgB1C,cAAA,CAAe,EAAA;EAIf,iBAAA,CAAkB,EAAA,UAAY,GAAA;EAAA,QAWtB,IAAA;EAER,MAAA,CAAO,GAAA,EAAK,QAAA;EAIZ,MAAA,CAAO,EAAA,WAAa,QAAA;EAIpB,QAAA,CAAA,GAAY,QAAA;EAIZ,SAAA,CAAU,EAAA,UAAY,KAAA,EAAO,OAAA,CAAQ,QAAA;EAQrC,SAAA,CAAU,EAAA;EA9LA;EAmMV,gBAAA,CAAiB,KAAA;EAejB,QAAA,CAAA,GAAY,iBAAA;EAgBZ,OAAA,CAAQ,QAAA,EAAU,iBAAA,EAAmB,GAAA;AAAA"}