stitchkit 0.96.1 → 0.98.0

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 (40) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/agent-runtime/schedule-contract.d.ts +52 -0
  3. package/dist/agent-runtime/schedule-contract.d.ts.map +1 -0
  4. package/dist/agent-runtime/schedule-dispatch.d.ts +26 -0
  5. package/dist/agent-runtime/schedule-dispatch.d.ts.map +1 -0
  6. package/dist/agent-runtime/schedule-records.d.ts +8 -14
  7. package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
  8. package/dist/agent-runtime/schedule-tools.d.ts +1 -1
  9. package/dist/agent-runtime/schedule-tools.d.ts.map +1 -1
  10. package/dist/agent-runtime/schedules.d.ts +7 -92
  11. package/dist/agent-runtime/schedules.d.ts.map +1 -1
  12. package/dist/agent-runtime/sqlite-schema.d.ts.map +1 -1
  13. package/dist/agent-runtime/store-migrations/v3-to-v4.d.ts +4 -0
  14. package/dist/agent-runtime/store-migrations/v3-to-v4.d.ts.map +1 -0
  15. package/dist/entrypoints/agent-runtime/sqlite/bun.js +1 -1
  16. package/dist/entrypoints/agent-runtime/sqlite/node.js +1 -1
  17. package/dist/entrypoints/agent-runtime.d.ts +2 -1
  18. package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
  19. package/dist/entrypoints/agent-runtime.js +289 -214
  20. package/dist/entrypoints/cli.js +1 -1
  21. package/dist/entrypoints/tools.js +1 -1
  22. package/dist/{index-p9e0w8hs.js → index-1m2thnq0.js} +25 -1
  23. package/dist/{index-t9rhjr9f.js → index-f1nb70ae.js} +45 -9
  24. package/dist/tools/cli/config.d.ts +5 -1
  25. package/dist/tools/cli/config.d.ts.map +1 -1
  26. package/dist/tools/cli/io.d.ts.map +1 -1
  27. package/dist/tools/cli/stdin.d.ts +6 -0
  28. package/dist/tools/cli/stdin.d.ts.map +1 -0
  29. package/llms/agent-runtime.2.txt +224 -0
  30. package/llms/agent-runtime.3.txt +1 -1
  31. package/llms/agent-runtime.txt +18 -225
  32. package/llms/cli.txt +15 -0
  33. package/llms/stitchkit.txt +3 -3
  34. package/llms/{upgrading-0.37.0-0.56.0.txt → upgrading-0.37.0-0.58.0.txt} +82 -3
  35. package/llms/{upgrading-0.57.0-0.69.0.txt → upgrading-0.59.0-0.71.0.txt} +108 -82
  36. package/llms/{upgrading-0.70.0-0.96.0.txt → upgrading-0.72.0-0.98.0.txt} +64 -108
  37. package/llms/upgrading.txt +3 -3
  38. package/llms-full.txt +98 -5
  39. package/llms.txt +5 -5
  40. package/package.json +1 -1
@@ -1902,10 +1902,67 @@ function defineAgentProtocol(config) {
1902
1902
  }
1903
1903
  };
1904
1904
  }
1905
- // src/agent-runtime/schedule-tools.ts
1905
+ // src/agent-runtime/schedule-contract.ts
1906
1906
  import { z as z8 } from "zod";
1907
- var ScheduledOutputSchema = z8.object({ scheduleId: z8.string(), nextAt: z8.string() }).strict();
1908
- var ConversationInputSchema = z8.object({ conversationId: z8.string().min(1) }).strict();
1907
+ var AgentScheduleSchema = z8.object({
1908
+ id: z8.string().min(1),
1909
+ conversationId: z8.string().min(1),
1910
+ kind: z8.enum(["at", "after", "every"]),
1911
+ nextAt: z8.iso.datetime({ offset: true }),
1912
+ intervalMs: z8.int().positive().optional(),
1913
+ timeZone: z8.string().min(1).optional(),
1914
+ input: z8.json(),
1915
+ state: z8.enum(["scheduled", "cancelled", "completed", "failed"]),
1916
+ occurrence: z8.int().nonnegative(),
1917
+ attempts: z8.int().nonnegative().optional(),
1918
+ retryAt: z8.iso.datetime({ offset: true }).optional(),
1919
+ lastError: z8.string().optional(),
1920
+ createdAt: z8.iso.datetime({ offset: true }),
1921
+ updatedAt: z8.iso.datetime({ offset: true })
1922
+ }).strict();
1923
+ var ScheduleRowSchema = z8.object({
1924
+ id: z8.string(),
1925
+ conversation_id: z8.string(),
1926
+ kind: z8.enum(["at", "after", "every"]),
1927
+ next_at: z8.string(),
1928
+ interval_ms: z8.number().int().positive().nullable(),
1929
+ time_zone: z8.string().nullable(),
1930
+ input_payload: z8.string(),
1931
+ state: z8.enum(["scheduled", "cancelled", "completed", "failed"]),
1932
+ occurrence: z8.int().nonnegative(),
1933
+ attempts: z8.int().nonnegative(),
1934
+ retry_at: z8.string().nullable(),
1935
+ last_error: z8.string().nullable(),
1936
+ created_at: z8.string(),
1937
+ updated_at: z8.string()
1938
+ });
1939
+ function parseSchedule(raw) {
1940
+ const row = ScheduleRowSchema.parse(raw);
1941
+ return AgentScheduleSchema.parse({
1942
+ id: row.id,
1943
+ conversationId: row.conversation_id,
1944
+ kind: row.kind,
1945
+ nextAt: row.next_at,
1946
+ ...row.interval_ms !== null && { intervalMs: row.interval_ms },
1947
+ ...row.time_zone !== null && { timeZone: row.time_zone },
1948
+ input: JSON.parse(row.input_payload),
1949
+ state: row.state,
1950
+ occurrence: row.occurrence,
1951
+ attempts: row.attempts,
1952
+ ...row.retry_at !== null && { retryAt: row.retry_at },
1953
+ ...row.last_error !== null && { lastError: row.last_error },
1954
+ createdAt: row.created_at,
1955
+ updatedAt: row.updated_at
1956
+ });
1957
+ }
1958
+ var AgentScheduleDeliveryOutcomeSchema = z8.discriminatedUnion("status", [
1959
+ z8.object({ status: z8.literal("retry"), reason: z8.string().min(1) }).strict(),
1960
+ z8.object({ status: z8.literal("terminal"), reason: z8.string().min(1) }).strict()
1961
+ ]);
1962
+ // src/agent-runtime/schedule-tools.ts
1963
+ import { z as z9 } from "zod";
1964
+ var ScheduledOutputSchema = z9.object({ scheduleId: z9.string(), nextAt: z9.string() }).strict();
1965
+ var ConversationInputSchema = z9.object({ conversationId: z9.string().min(1) }).strict();
1909
1966
  function createAgentScheduleTools(service) {
1910
1967
  const schedule = async (request) => {
1911
1968
  const result = await service.scheduleInput(request);
@@ -1916,10 +1973,10 @@ function createAgentScheduleTools(service) {
1916
1973
  name: "schedule_after",
1917
1974
  description: "Schedule durable input after a relative delay.",
1918
1975
  identity: { serviceName: "agent-schedules", action: "after", method: "POST" },
1919
- input: z8.object({
1920
- conversationId: z8.string().min(1),
1921
- input: z8.json(),
1922
- afterMs: z8.int().positive()
1976
+ input: z9.object({
1977
+ conversationId: z9.string().min(1),
1978
+ input: z9.json(),
1979
+ afterMs: z9.int().positive()
1923
1980
  }).strict(),
1924
1981
  output: ScheduledOutputSchema,
1925
1982
  transports: ["AGENT"],
@@ -1929,7 +1986,7 @@ function createAgentScheduleTools(service) {
1929
1986
  name: "schedule_at",
1930
1987
  description: "Schedule durable input at an absolute timestamp with an explicit offset.",
1931
1988
  identity: { serviceName: "agent-schedules", action: "at", method: "POST" },
1932
- input: z8.object({ conversationId: z8.string().min(1), input: z8.json(), at: z8.string().min(1) }).strict(),
1989
+ input: z9.object({ conversationId: z9.string().min(1), input: z9.json(), at: z9.string().min(1) }).strict(),
1933
1990
  output: ScheduledOutputSchema,
1934
1991
  transports: ["AGENT"],
1935
1992
  handler: ({ input }) => schedule(input)
@@ -1938,11 +1995,11 @@ function createAgentScheduleTools(service) {
1938
1995
  name: "schedule_every",
1939
1996
  description: "Schedule recurring durable input in an explicit conversation timezone.",
1940
1997
  identity: { serviceName: "agent-schedules", action: "every", method: "POST" },
1941
- input: z8.object({
1942
- conversationId: z8.string().min(1),
1943
- input: z8.json(),
1944
- everyMs: z8.int().positive(),
1945
- timeZone: z8.string().min(1)
1998
+ input: z9.object({
1999
+ conversationId: z9.string().min(1),
2000
+ input: z9.json(),
2001
+ everyMs: z9.int().positive(),
2002
+ timeZone: z9.string().min(1)
1946
2003
  }).strict(),
1947
2004
  output: ScheduledOutputSchema,
1948
2005
  transports: ["AGENT"],
@@ -1953,7 +2010,7 @@ function createAgentScheduleTools(service) {
1953
2010
  description: "List durable schedules for one conversation.",
1954
2011
  identity: { serviceName: "agent-schedules", action: "list", method: "GET" },
1955
2012
  input: ConversationInputSchema,
1956
- output: z8.object({ schedules: z8.array(z8.json()) }).strict(),
2013
+ output: z9.object({ schedules: z9.array(z9.json()) }).strict(),
1957
2014
  transports: ["AGENT"],
1958
2015
  handler: ({ input }) => ({
1959
2016
  schedules: [...service.listSchedules(input.conversationId)]
@@ -1963,8 +2020,8 @@ function createAgentScheduleTools(service) {
1963
2020
  name: "schedule_cancel",
1964
2021
  description: "Cancel one durable deferred-input schedule.",
1965
2022
  identity: { serviceName: "agent-schedules", action: "cancel", method: "POST" },
1966
- input: z8.object({ conversationId: z8.string().min(1), scheduleId: z8.string().min(1) }).strict(),
1967
- output: z8.object({ cancelled: z8.boolean() }).strict(),
2023
+ input: z9.object({ conversationId: z9.string().min(1), scheduleId: z9.string().min(1) }).strict(),
2024
+ output: z9.object({ cancelled: z9.boolean() }).strict(),
1968
2025
  transports: ["AGENT"],
1969
2026
  handler: async ({ input }) => ({
1970
2027
  cancelled: await service.cancelSchedule(input.conversationId, input.scheduleId)
@@ -1973,12 +2030,14 @@ function createAgentScheduleTools(service) {
1973
2030
  ];
1974
2031
  }
1975
2032
  // src/agent-runtime/schedules.ts
2033
+ import { z as z11 } from "zod";
2034
+
2035
+ // src/agent-runtime/schedule-dispatch.ts
1976
2036
  import { randomUUID as randomUUID3 } from "node:crypto";
1977
- import { z as z10 } from "zod";
1978
2037
 
1979
2038
  // src/agent-runtime/schedule-records.ts
1980
2039
  import { randomUUID as randomUUID2 } from "node:crypto";
1981
- import { z as z9 } from "zod";
2040
+ import { z as z10 } from "zod";
1982
2041
  function newSchedule(request, observedAt) {
1983
2042
  const modes = [
1984
2043
  request.at !== undefined,
@@ -2003,7 +2062,7 @@ function newSchedule(request, observedAt) {
2003
2062
  if (delay !== undefined && (!Number.isSafeInteger(delay) || delay < 1)) {
2004
2063
  throw new TypeError("Schedule delay must be a positive safe integer");
2005
2064
  }
2006
- const nextAt = request.at ? new Date(z9.iso.datetime({ offset: true }).parse(request.at)).toISOString() : new Date(observedAt.getTime() + (delay ?? 0)).toISOString();
2065
+ const nextAt = request.at ? new Date(z10.iso.datetime({ offset: true }).parse(request.at)).toISOString() : new Date(observedAt.getTime() + (delay ?? 0)).toISOString();
2007
2066
  const schedule = AgentScheduleSchema.parse({
2008
2067
  id: randomUUID2(),
2009
2068
  conversationId: request.conversationId,
@@ -2011,7 +2070,7 @@ function newSchedule(request, observedAt) {
2011
2070
  nextAt,
2012
2071
  ...intervalMs !== undefined && { intervalMs },
2013
2072
  ...request.timeZone && { timeZone: request.timeZone },
2014
- input: z9.json().parse(request.input),
2073
+ input: z10.json().parse(request.input),
2015
2074
  state: "scheduled",
2016
2075
  occurrence: 0,
2017
2076
  createdAt: observedAt.toISOString(),
@@ -2035,50 +2094,67 @@ async function insertSchedule(sqlite, schedule) {
2035
2094
  });
2036
2095
  });
2037
2096
  }
2038
- async function recordDispatchFailure(sqlite, { schedule, occurrence, at, owner }, error) {
2097
+ function holdsClaim(raw, firing, at) {
2098
+ if (raw === null || raw === undefined)
2099
+ return false;
2100
+ const row = z10.object({
2101
+ state: z10.string(),
2102
+ claim_owner: z10.string().nullable(),
2103
+ claim_until: z10.string().nullable(),
2104
+ occurrence: z10.int(),
2105
+ next_at: z10.string()
2106
+ }).parse(raw);
2107
+ return row.state === "scheduled" && row.claim_owner === firing.owner && row.claim_until !== null && row.claim_until > at && row.occurrence === firing.schedule.occurrence && row.next_at === firing.schedule.nextAt;
2108
+ }
2109
+ async function recordDispatchFailure(sqlite, firing, reason, terminal = false) {
2110
+ const { schedule, occurrence } = firing;
2039
2111
  await sqlite.transaction(async (scope) => {
2112
+ const at = firing.now().toISOString();
2113
+ const raw = scope.database.prepare(`
2114
+ SELECT state, claim_owner, claim_until, occurrence, next_at
2115
+ FROM stitchkit_agent_runtime_schedules WHERE id = ?
2116
+ `).get(schedule.id);
2117
+ if (!holdsClaim(raw, firing, at))
2118
+ return;
2119
+ const attempts = Math.min((schedule.attempts ?? 0) + 1, Number.MAX_SAFE_INTEGER);
2120
+ const retryAt = terminal ? null : new Date(new Date(at).getTime() + Math.min(60000, 1000 * 2 ** Math.min(attempts - 1, 6))).toISOString();
2040
2121
  scope.database.prepare(`
2041
- UPDATE stitchkit_agent_runtime_schedules
2042
- SET claim_owner = NULL, claim_until = NULL, updated_at = ?
2043
- WHERE id = ? AND claim_owner = ?
2044
- `).run(at, schedule.id, owner);
2122
+ UPDATE stitchkit_agent_runtime_schedules
2123
+ SET state = ?, retry_at = ?, attempts = ?, last_error = ?,
2124
+ claim_owner = NULL, claim_until = NULL, updated_at = ? WHERE id = ?
2125
+ `).run(terminal ? "failed" : "scheduled", retryAt, attempts, reason, at, schedule.id);
2045
2126
  await scope.appendEvent({
2046
2127
  conversationId: schedule.conversationId,
2047
2128
  kind: "schedule/failed",
2048
2129
  occurredAt: at,
2049
- payload: {
2050
- id: schedule.id,
2051
- occurrence,
2052
- message: error instanceof Error ? error.message : String(error)
2053
- }
2130
+ payload: { id: schedule.id, occurrence, message: reason, attempts, retryAt, terminal }
2054
2131
  });
2055
2132
  });
2056
2133
  }
2057
- async function settleFiring(sqlite, { schedule, occurrence, at, owner }, observedAt, lateByMs) {
2058
- let nextAt = schedule.nextAt;
2059
- if (schedule.kind === "every" && schedule.intervalMs) {
2060
- let next = new Date(schedule.nextAt).getTime() + schedule.intervalMs;
2061
- while (next <= observedAt.getTime())
2062
- next += schedule.intervalMs;
2063
- nextAt = new Date(next).toISOString();
2064
- }
2134
+ async function settleFiring(sqlite, firing, lateByMs) {
2135
+ const { schedule, occurrence } = firing;
2065
2136
  const state = schedule.kind === "every" ? "scheduled" : "completed";
2066
2137
  await sqlite.transaction(async (scope) => {
2067
- const held = scope.database.prepare(`
2068
- SELECT state, claim_owner FROM stitchkit_agent_runtime_schedules WHERE id = ?
2069
- `).get(schedule.id);
2070
- const settled = held !== null && held !== undefined && (() => {
2071
- const row = FinalizeRowSchema.parse(held);
2072
- return row.state === "scheduled" && row.claim_owner === owner;
2073
- })();
2074
- if (settled) {
2075
- scope.database.prepare(`
2076
- UPDATE stitchkit_agent_runtime_schedules
2077
- SET state = ?, occurrence = ?, next_at = ?, updated_at = ?,
2078
- claim_owner = NULL, claim_until = NULL
2079
- WHERE id = ?
2080
- `).run(state, occurrence, nextAt, at, schedule.id);
2138
+ const observedAt = firing.now();
2139
+ const at = observedAt.toISOString();
2140
+ let nextAt = schedule.nextAt;
2141
+ if (schedule.kind === "every" && schedule.intervalMs) {
2142
+ const due = new Date(schedule.nextAt).getTime();
2143
+ const slots = Math.max(1, Math.floor((observedAt.getTime() - due) / schedule.intervalMs) + 1);
2144
+ nextAt = new Date(due + slots * schedule.intervalMs).toISOString();
2081
2145
  }
2146
+ const raw = scope.database.prepare(`
2147
+ SELECT state, claim_owner, claim_until, occurrence, next_at
2148
+ FROM stitchkit_agent_runtime_schedules WHERE id = ?
2149
+ `).get(schedule.id);
2150
+ if (!holdsClaim(raw, firing, at))
2151
+ return;
2152
+ scope.database.prepare(`
2153
+ UPDATE stitchkit_agent_runtime_schedules
2154
+ SET state = ?, occurrence = ?, next_at = ?, updated_at = ?,
2155
+ claim_owner = NULL, claim_until = NULL, retry_at = NULL, attempts = 0, last_error = NULL
2156
+ WHERE id = ?
2157
+ `).run(state, occurrence, nextAt, at, schedule.id);
2082
2158
  if (lateByMs > 0) {
2083
2159
  await scope.appendEvent({
2084
2160
  conversationId: schedule.conversationId,
@@ -2091,168 +2167,165 @@ async function settleFiring(sqlite, { schedule, occurrence, at, owner }, observe
2091
2167
  conversationId: schedule.conversationId,
2092
2168
  kind: "schedule/fired",
2093
2169
  occurredAt: at,
2094
- payload: {
2095
- id: schedule.id,
2096
- occurrence,
2097
- lateByMs,
2098
- nextAt,
2099
- state: settled ? state : "cancelled"
2100
- }
2170
+ payload: { id: schedule.id, occurrence, lateByMs, nextAt, state }
2101
2171
  });
2102
2172
  });
2103
2173
  }
2104
- function claimSchedule(sqlite, owner, id, until, at) {
2174
+ function claimSchedule(sqlite, owner, schedule, now) {
2105
2175
  return sqlite.transaction(async (scope) => {
2176
+ const observedAt = now();
2177
+ const at = observedAt.toISOString();
2178
+ const until = new Date(observedAt.getTime() + 60000).toISOString();
2106
2179
  const raw = scope.database.prepare(`
2107
- SELECT state, claim_until FROM stitchkit_agent_runtime_schedules WHERE id = ?
2108
- `).get(id);
2180
+ SELECT occurrence, next_at, attempts FROM stitchkit_agent_runtime_schedules
2181
+ WHERE id = ? AND state = 'scheduled' AND eligible_at <= ?
2182
+ `).get(schedule.id, at);
2109
2183
  if (raw === null || raw === undefined)
2110
2184
  return false;
2111
- const row = ClaimRowSchema.parse(raw);
2112
- if (row.state !== "scheduled")
2185
+ const row = z10.object({ occurrence: z10.int(), next_at: z10.string(), attempts: z10.int() }).parse(raw);
2186
+ if (row.attempts !== (schedule.attempts ?? 0))
2113
2187
  return false;
2114
- if (row.claim_until !== null && row.claim_until >= at)
2188
+ if (row.occurrence !== schedule.occurrence || row.next_at !== schedule.nextAt)
2115
2189
  return false;
2116
2190
  scope.database.prepare(`
2117
- UPDATE stitchkit_agent_runtime_schedules
2118
- SET claim_owner = ?, claim_until = ?, updated_at = ?
2119
- WHERE id = ?
2120
- `).run(owner, until, at, id);
2191
+ UPDATE stitchkit_agent_runtime_schedules
2192
+ SET claim_owner = ?, claim_until = ?, updated_at = ? WHERE id = ?
2193
+ `).run(owner, until, at, schedule.id);
2121
2194
  return true;
2122
2195
  });
2123
2196
  }
2124
2197
 
2125
- // src/agent-runtime/schedules.ts
2126
- var AgentScheduleSchema = z10.object({
2127
- id: z10.string().min(1),
2128
- conversationId: z10.string().min(1),
2129
- kind: z10.enum(["at", "after", "every"]),
2130
- nextAt: z10.iso.datetime({ offset: true }),
2131
- intervalMs: z10.int().positive().optional(),
2132
- timeZone: z10.string().min(1).optional(),
2133
- input: z10.json(),
2134
- state: z10.enum(["scheduled", "cancelled", "completed"]),
2135
- occurrence: z10.int().nonnegative(),
2136
- createdAt: z10.iso.datetime({ offset: true }),
2137
- updatedAt: z10.iso.datetime({ offset: true })
2138
- }).strict();
2139
- var ScheduleRowSchema = z10.object({
2140
- id: z10.string(),
2141
- conversation_id: z10.string(),
2142
- kind: z10.enum(["at", "after", "every"]),
2143
- next_at: z10.string(),
2144
- interval_ms: z10.number().int().positive().nullable(),
2145
- time_zone: z10.string().nullable(),
2146
- input_payload: z10.string(),
2147
- state: z10.enum(["scheduled", "cancelled", "completed"]),
2148
- occurrence: z10.int().nonnegative(),
2149
- created_at: z10.string(),
2150
- updated_at: z10.string()
2151
- });
2152
- var ClaimRowSchema = z10.object({
2153
- state: z10.enum(["scheduled", "cancelled", "completed"]),
2154
- claim_until: z10.string().nullable()
2155
- });
2156
- var FinalizeRowSchema = z10.object({
2157
- state: z10.enum(["scheduled", "cancelled", "completed"]),
2158
- claim_owner: z10.string().nullable()
2159
- });
2160
- var StateRowSchema = z10.object({
2161
- state: z10.enum(["scheduled", "cancelled", "completed"])
2162
- });
2163
- function parseSchedule(raw) {
2164
- const row = ScheduleRowSchema.parse(raw);
2165
- return AgentScheduleSchema.parse({
2166
- id: row.id,
2167
- conversationId: row.conversation_id,
2168
- kind: row.kind,
2169
- nextAt: row.next_at,
2170
- ...row.interval_ms !== null && { intervalMs: row.interval_ms },
2171
- ...row.time_zone !== null && { timeZone: row.time_zone },
2172
- input: JSON.parse(row.input_payload),
2173
- state: row.state,
2174
- occurrence: row.occurrence,
2175
- createdAt: row.created_at,
2176
- updatedAt: row.updated_at
2177
- });
2198
+ // src/agent-runtime/schedule-dispatch.ts
2199
+ async function dispatchSchedule(context, schedule) {
2200
+ if (context.signal.aborted)
2201
+ return;
2202
+ const owner = randomUUID3();
2203
+ if (!await claimSchedule(context.sqlite, owner, schedule, context.now))
2204
+ return;
2205
+ const observedAt = context.now();
2206
+ const occurrence = schedule.occurrence + 1;
2207
+ const lateByMs = Math.max(0, observedAt.getTime() - new Date(schedule.nextAt).getTime());
2208
+ const controller = new AbortController;
2209
+ const aborted = Promise.withResolvers();
2210
+ const abort = () => {
2211
+ const error = new Error("Schedule dispatch aborted");
2212
+ controller.abort(error);
2213
+ aborted.reject(error);
2214
+ };
2215
+ context.signal.addEventListener("abort", abort, { once: true });
2216
+ const timer = context.setTimer(abort, 30000);
2217
+ try {
2218
+ if (context.signal.aborted)
2219
+ abort();
2220
+ const outcome = await Promise.race([
2221
+ aborted.promise,
2222
+ Promise.resolve().then(() => {
2223
+ controller.signal.throwIfAborted();
2224
+ return context.dispatch({
2225
+ conversationId: schedule.conversationId,
2226
+ idempotencyKey: `schedule:${schedule.id}:${occurrence}`,
2227
+ input: schedule.input,
2228
+ schedule: { id: schedule.id, occurrence, lateByMs },
2229
+ signal: controller.signal
2230
+ });
2231
+ })
2232
+ ]);
2233
+ const firing = { schedule, occurrence, now: context.now, owner };
2234
+ if (outcome === undefined) {
2235
+ await settleFiring(context.sqlite, firing, lateByMs);
2236
+ } else {
2237
+ const failure = AgentScheduleDeliveryOutcomeSchema.parse(outcome);
2238
+ await recordDispatchFailure(context.sqlite, firing, failure.reason, failure.status === "terminal");
2239
+ }
2240
+ } catch (error) {
2241
+ if (!context.signal.aborted) {
2242
+ await recordDispatchFailure(context.sqlite, { schedule, occurrence, now: context.now, owner }, error instanceof Error ? error.message : String(error));
2243
+ }
2244
+ } finally {
2245
+ context.clearTimer(timer);
2246
+ context.signal.removeEventListener("abort", abort);
2247
+ }
2178
2248
  }
2249
+
2250
+ // src/agent-runtime/schedules.ts
2179
2251
  function createAgentScheduleService(input) {
2180
- const onError = input.onError ?? ((error) => {
2181
- console.error("[stitchkit] agent schedule tick failed", error);
2182
- });
2183
2252
  const now = input.now ?? (() => new Date);
2184
2253
  const database = input.sqlite.database;
2185
2254
  const setTimer = input.setTimer ?? ((callback, delay) => setTimeout(callback, delay));
2186
2255
  const clearTimer = input.clearTimer ?? clearTimeout;
2256
+ const onError = input.onError ?? ((error) => {
2257
+ console.error("[stitchkit] agent schedule tick failed", error);
2258
+ });
2259
+ const lifecycle = new AbortController;
2187
2260
  let timer;
2188
- let closed = false;
2261
+ let ticking;
2262
+ let recoveryAt = 0;
2263
+ let storageFailures = 0;
2264
+ const reportStorageFailure = (error) => {
2265
+ storageFailures += 1;
2266
+ recoveryAt = now().getTime() + Math.min(60000, 1000 * 2 ** Math.min(storageFailures - 1, 6));
2267
+ onError(error);
2268
+ };
2189
2269
  const listSchedules = (conversationId) => database.prepare(`
2190
- SELECT id, conversation_id, kind, next_at, interval_ms, time_zone,
2191
- input_payload, state, occurrence, created_at, updated_at
2192
- FROM stitchkit_agent_runtime_schedules
2193
- WHERE conversation_id = ? ORDER BY created_at, id
2194
- `).all(conversationId).map(parseSchedule);
2270
+ SELECT * FROM stitchkit_agent_runtime_schedules
2271
+ WHERE conversation_id = ? ORDER BY created_at, id
2272
+ `).all(conversationId).map(parseSchedule);
2195
2273
  const arm = () => {
2196
- if (timer)
2274
+ if (timer !== undefined)
2197
2275
  clearTimer(timer);
2198
- if (closed)
2276
+ timer = undefined;
2277
+ if (lifecycle.signal.aborted || ticking)
2199
2278
  return;
2200
- try {
2201
- armNext();
2202
- } catch (error) {
2203
- onError(error);
2279
+ let dueAt;
2280
+ if (recoveryAt > now().getTime()) {
2281
+ dueAt = recoveryAt;
2282
+ } else {
2283
+ try {
2284
+ const raw = database.prepare(`
2285
+ SELECT eligible_at FROM stitchkit_agent_runtime_schedules
2286
+ WHERE state = 'scheduled' ORDER BY eligible_at, id LIMIT 1
2287
+ `).get();
2288
+ if (raw === null || raw === undefined)
2289
+ return;
2290
+ dueAt = new Date(z11.object({ eligible_at: z11.string() }).parse(raw).eligible_at).getTime();
2291
+ } catch (error) {
2292
+ reportStorageFailure(error);
2293
+ dueAt = recoveryAt;
2294
+ }
2204
2295
  }
2296
+ timer = setTimer(() => {
2297
+ timer = undefined;
2298
+ tick();
2299
+ }, Math.min(Math.max(0, dueAt - now().getTime()), 2147483647));
2205
2300
  };
2206
- const armNext = () => {
2207
- const raw = database.prepare(`
2208
- SELECT MAX(next_at, COALESCE(claim_until, next_at)) AS due_at
2209
- FROM stitchkit_agent_runtime_schedules
2210
- WHERE state = 'scheduled' ORDER BY due_at, id LIMIT 1
2211
- `).get();
2212
- if (raw === null || raw === undefined || typeof raw.due_at !== "string")
2213
- return;
2214
- const delay = Math.max(0, new Date(raw.due_at).getTime() - now().getTime());
2215
- timer = setTimer(() => void tick(), Math.min(delay, 2147483647));
2216
- };
2217
- const owner = randomUUID3();
2218
- const CLAIM_MS = 60000;
2219
- let ticking;
2220
- const claim = (id, until, at) => claimSchedule(input.sqlite, owner, id, until, at);
2221
2301
  const runTick = async () => {
2222
- if (closed)
2302
+ if (lifecycle.signal.aborted || now().getTime() < recoveryAt)
2223
2303
  return;
2224
- const observedAt = now();
2225
2304
  const due = database.prepare(`
2226
- SELECT id, conversation_id, kind, next_at, interval_ms, time_zone,
2227
- input_payload, state, occurrence, created_at, updated_at
2228
- FROM stitchkit_agent_runtime_schedules
2229
- WHERE state = 'scheduled' AND next_at <= ? ORDER BY next_at, id
2230
- `).all(observedAt.toISOString()).map(parseSchedule);
2231
- for (const schedule of due) {
2232
- const at = observedAt.toISOString();
2233
- if (!await claim(schedule.id, new Date(observedAt.getTime() + CLAIM_MS).toISOString(), at)) {
2234
- continue;
2235
- }
2236
- const occurrence = schedule.occurrence + 1;
2237
- const lateByMs = Math.max(0, observedAt.getTime() - new Date(schedule.nextAt).getTime());
2238
- try {
2239
- await input.dispatch({
2240
- conversationId: schedule.conversationId,
2241
- idempotencyKey: `schedule:${schedule.id}:${occurrence}`,
2242
- input: schedule.input,
2243
- schedule: { id: schedule.id, occurrence, lateByMs }
2244
- });
2245
- } catch (error) {
2246
- await recordDispatchFailure(input.sqlite, { schedule, occurrence, at, owner }, error);
2247
- continue;
2248
- }
2249
- await settleFiring(input.sqlite, { schedule, occurrence, at, owner }, observedAt, lateByMs);
2250
- }
2305
+ SELECT * FROM stitchkit_agent_runtime_schedules
2306
+ WHERE state = 'scheduled' AND eligible_at <= ? ORDER BY eligible_at, id LIMIT 32
2307
+ `).all(now().toISOString()).map(parseSchedule);
2308
+ const results = await Promise.allSettled(due.map((schedule) => dispatchSchedule({
2309
+ sqlite: input.sqlite,
2310
+ dispatch: input.dispatch,
2311
+ now,
2312
+ setTimer,
2313
+ clearTimer,
2314
+ signal: lifecycle.signal
2315
+ }, schedule)));
2316
+ const failed = results.find((result) => result.status === "rejected");
2317
+ if (failed?.status === "rejected")
2318
+ throw failed.reason;
2319
+ recoveryAt = 0;
2320
+ storageFailures = 0;
2251
2321
  };
2252
2322
  const tick = () => {
2253
2323
  if (ticking)
2254
2324
  return ticking;
2255
- ticking = runTick().catch(onError).finally(() => {
2325
+ if (timer !== undefined)
2326
+ clearTimer(timer);
2327
+ timer = undefined;
2328
+ ticking = runTick().catch(reportStorageFailure).finally(() => {
2256
2329
  ticking = undefined;
2257
2330
  arm();
2258
2331
  });
@@ -2268,17 +2341,17 @@ function createAgentScheduleService(input) {
2268
2341
  const observedAt = now().toISOString();
2269
2342
  const changed = await input.sqlite.transaction(async (scope) => {
2270
2343
  const raw = scope.database.prepare(`
2271
- SELECT state FROM stitchkit_agent_runtime_schedules
2272
- WHERE id = ? AND conversation_id = ?
2273
- `).get(id, conversationId);
2344
+ SELECT state FROM stitchkit_agent_runtime_schedules WHERE id = ? AND conversation_id = ?
2345
+ `).get(id, conversationId);
2274
2346
  if (raw === null || raw === undefined)
2275
2347
  return false;
2276
- if (StateRowSchema.parse(raw).state !== "scheduled")
2348
+ if (z11.object({ state: z11.string() }).parse(raw).state !== "scheduled")
2277
2349
  return false;
2278
2350
  scope.database.prepare(`
2279
- UPDATE stitchkit_agent_runtime_schedules SET state = 'cancelled', updated_at = ?
2280
- WHERE id = ? AND conversation_id = ?
2281
- `).run(observedAt, id, conversationId);
2351
+ UPDATE stitchkit_agent_runtime_schedules
2352
+ SET state = 'cancelled', updated_at = ?, claim_owner = NULL, claim_until = NULL
2353
+ WHERE id = ? AND conversation_id = ?
2354
+ `).run(observedAt, id, conversationId);
2282
2355
  await scope.appendEvent({
2283
2356
  conversationId,
2284
2357
  kind: "schedule/cancelled",
@@ -2298,24 +2371,25 @@ function createAgentScheduleService(input) {
2298
2371
  tick,
2299
2372
  start: arm,
2300
2373
  close() {
2301
- closed = true;
2302
- if (timer)
2374
+ lifecycle.abort();
2375
+ if (timer !== undefined)
2303
2376
  clearTimer(timer);
2377
+ timer = undefined;
2304
2378
  }
2305
2379
  };
2306
2380
  }
2307
2381
  // src/agent-runtime/spill.ts
2308
2382
  import { createHash, randomUUID as randomUUID4 } from "node:crypto";
2309
- import { z as z11 } from "zod";
2310
- var SpillRowSchema = z11.object({
2311
- payload: z11.instanceof(Uint8Array),
2312
- bytes: z11.int().nonnegative(),
2313
- authorization_payload: z11.string().nullable()
2383
+ import { z as z12 } from "zod";
2384
+ var SpillRowSchema = z12.object({
2385
+ payload: z12.instanceof(Uint8Array),
2386
+ bytes: z12.int().nonnegative(),
2387
+ authorization_payload: z12.string().nullable()
2314
2388
  });
2315
- var SpillMetadataRowSchema = z11.object({
2316
- reference_id: z11.string(),
2317
- bytes: z11.int().nonnegative(),
2318
- created_at: z11.string()
2389
+ var SpillMetadataRowSchema = z12.object({
2390
+ reference_id: z12.string(),
2391
+ bytes: z12.int().nonnegative(),
2392
+ created_at: z12.string()
2319
2393
  });
2320
2394
  function decodeAuthorization(value) {
2321
2395
  if (value === null)
@@ -2402,14 +2476,14 @@ function createSqliteAgentSpillStore(input) {
2402
2476
  return { authorization, write, read, search, cleanup };
2403
2477
  }
2404
2478
  // src/agent-runtime/state-tools.ts
2405
- import { z as z12 } from "zod";
2406
- var GoalSchema = z12.object({
2407
- objective: z12.string().min(1),
2408
- status: z12.enum(["active", "complete", "blocked"])
2479
+ import { z as z13 } from "zod";
2480
+ var GoalSchema = z13.object({
2481
+ objective: z13.string().min(1),
2482
+ status: z13.enum(["active", "complete", "blocked"])
2409
2483
  }).strict();
2410
- var TodoItemSchema = z12.object({
2411
- step: z12.string().min(1),
2412
- status: z12.enum(["pending", "in_progress", "completed"])
2484
+ var TodoItemSchema = z13.object({
2485
+ step: z13.string().min(1),
2486
+ status: z13.enum(["pending", "in_progress", "completed"])
2413
2487
  }).strict();
2414
2488
  function createAgentStateTools(input) {
2415
2489
  const writeGoal = async (value) => {
@@ -2426,7 +2500,7 @@ function createAgentStateTools(input) {
2426
2500
  name: "create_goal",
2427
2501
  description: "Create the durable objective for this conversation.",
2428
2502
  identity: { serviceName: "agent-state", action: "create-goal", method: "POST" },
2429
- input: z12.object({ objective: z12.string().min(1) }).strict(),
2503
+ input: z13.object({ objective: z13.string().min(1) }).strict(),
2430
2504
  output: GoalSchema,
2431
2505
  transports: ["AGENT"],
2432
2506
  handler: async ({ input: request }) => {
@@ -2440,9 +2514,9 @@ function createAgentStateTools(input) {
2440
2514
  name: "update_goal",
2441
2515
  description: "Update the durable objective or lifecycle status.",
2442
2516
  identity: { serviceName: "agent-state", action: "update-goal", method: "POST" },
2443
- input: z12.object({
2444
- objective: z12.string().min(1).optional(),
2445
- status: z12.enum(["active", "complete", "blocked"]).optional()
2517
+ input: z13.object({
2518
+ objective: z13.string().min(1).optional(),
2519
+ status: z13.enum(["active", "complete", "blocked"]).optional()
2446
2520
  }).strict().refine((request) => request.objective !== undefined || request.status !== undefined, {
2447
2521
  message: "At least one goal field must be supplied"
2448
2522
  }),
@@ -2460,8 +2534,8 @@ function createAgentStateTools(input) {
2460
2534
  name: "get_goal",
2461
2535
  description: "Read the durable objective for this conversation.",
2462
2536
  identity: { serviceName: "agent-state", action: "get-goal", method: "GET" },
2463
- input: z12.object({}).strict(),
2464
- output: z12.object({ goal: GoalSchema.optional() }).strict(),
2537
+ input: z13.object({}).strict(),
2538
+ output: z13.object({ goal: GoalSchema.optional() }).strict(),
2465
2539
  transports: ["AGENT"],
2466
2540
  handler: async () => {
2467
2541
  const current = await input.state.get(input.conversationId, "goal");
@@ -2472,8 +2546,8 @@ function createAgentStateTools(input) {
2472
2546
  name: "todo_write",
2473
2547
  description: "Replace the durable ordered work plan for this conversation.",
2474
2548
  identity: { serviceName: "agent-state", action: "write-todo", method: "POST" },
2475
- input: z12.object({ items: z12.array(TodoItemSchema) }).strict(),
2476
- output: z12.object({ items: z12.array(TodoItemSchema) }).strict(),
2549
+ input: z13.object({ items: z13.array(TodoItemSchema) }).strict(),
2550
+ output: z13.object({ items: z13.array(TodoItemSchema) }).strict(),
2477
2551
  transports: ["AGENT"],
2478
2552
  handler: async ({ input: request }) => {
2479
2553
  const stored = await input.state.set({
@@ -2482,7 +2556,7 @@ function createAgentStateTools(input) {
2482
2556
  value: request,
2483
2557
  actor: "agent"
2484
2558
  });
2485
- return z12.object({ items: z12.array(TodoItemSchema) }).strict().parse(stored.value);
2559
+ return z13.object({ items: z13.array(TodoItemSchema) }).strict().parse(stored.value);
2486
2560
  }
2487
2561
  })
2488
2562
  ];
@@ -2592,6 +2666,7 @@ export {
2592
2666
  AgentSnapshotSchema,
2593
2667
  AgentSeedReceiptSchema,
2594
2668
  AgentScheduleSchema,
2669
+ AgentScheduleDeliveryOutcomeSchema,
2595
2670
  AgentSandboxRestrictionSchema,
2596
2671
  AgentSandboxGradeSchema,
2597
2672
  AgentRuntimeHeadSchema,