stitchkit 0.96.1 → 0.97.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.
- package/CHANGELOG.md +22 -0
- package/dist/agent-runtime/schedule-contract.d.ts +52 -0
- package/dist/agent-runtime/schedule-contract.d.ts.map +1 -0
- package/dist/agent-runtime/schedule-dispatch.d.ts +26 -0
- package/dist/agent-runtime/schedule-dispatch.d.ts.map +1 -0
- package/dist/agent-runtime/schedule-records.d.ts +8 -14
- package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
- package/dist/agent-runtime/schedule-tools.d.ts +1 -1
- package/dist/agent-runtime/schedule-tools.d.ts.map +1 -1
- package/dist/agent-runtime/schedules.d.ts +7 -92
- package/dist/agent-runtime/schedules.d.ts.map +1 -1
- package/dist/agent-runtime/sqlite-schema.d.ts.map +1 -1
- package/dist/agent-runtime/store-migrations/v3-to-v4.d.ts +4 -0
- package/dist/agent-runtime/store-migrations/v3-to-v4.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +1 -1
- package/dist/entrypoints/agent-runtime/sqlite/node.js +1 -1
- package/dist/entrypoints/agent-runtime.d.ts +2 -1
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime.js +289 -214
- package/dist/{index-p9e0w8hs.js → index-1m2thnq0.js} +25 -1
- package/llms/agent-runtime.2.txt +224 -0
- package/llms/agent-runtime.3.txt +1 -1
- package/llms/agent-runtime.txt +18 -225
- package/llms/stitchkit.txt +3 -3
- package/llms/{upgrading-0.37.0-0.56.0.txt → upgrading-0.37.0-0.58.0.txt} +82 -3
- package/llms/{upgrading-0.57.0-0.69.0.txt → upgrading-0.59.0-0.71.0.txt} +108 -82
- package/llms/{upgrading-0.70.0-0.96.0.txt → upgrading-0.72.0-0.97.0.txt} +39 -108
- package/llms/upgrading.txt +3 -3
- package/llms-full.txt +58 -5
- package/llms.txt +4 -4
- package/package.json +1 -1
|
@@ -1902,10 +1902,67 @@ function defineAgentProtocol(config) {
|
|
|
1902
1902
|
}
|
|
1903
1903
|
};
|
|
1904
1904
|
}
|
|
1905
|
-
// src/agent-runtime/schedule-
|
|
1905
|
+
// src/agent-runtime/schedule-contract.ts
|
|
1906
1906
|
import { z as z8 } from "zod";
|
|
1907
|
-
var
|
|
1908
|
-
|
|
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:
|
|
1920
|
-
conversationId:
|
|
1921
|
-
input:
|
|
1922
|
-
afterMs:
|
|
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:
|
|
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:
|
|
1942
|
-
conversationId:
|
|
1943
|
-
input:
|
|
1944
|
-
everyMs:
|
|
1945
|
-
timeZone:
|
|
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:
|
|
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:
|
|
1967
|
-
output:
|
|
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
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
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,
|
|
2058
|
-
|
|
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
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
const
|
|
2072
|
-
|
|
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,
|
|
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
|
-
|
|
2108
|
-
|
|
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 =
|
|
2112
|
-
if (row.
|
|
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.
|
|
2188
|
+
if (row.occurrence !== schedule.occurrence || row.next_at !== schedule.nextAt)
|
|
2115
2189
|
return false;
|
|
2116
2190
|
scope.database.prepare(`
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
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/
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
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
|
|
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
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
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
|
-
|
|
2276
|
+
timer = undefined;
|
|
2277
|
+
if (lifecycle.signal.aborted || ticking)
|
|
2199
2278
|
return;
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
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 (
|
|
2302
|
+
if (lifecycle.signal.aborted || now().getTime() < recoveryAt)
|
|
2223
2303
|
return;
|
|
2224
|
-
const observedAt = now();
|
|
2225
2304
|
const due = database.prepare(`
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2272
|
-
|
|
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 (
|
|
2348
|
+
if (z11.object({ state: z11.string() }).parse(raw).state !== "scheduled")
|
|
2277
2349
|
return false;
|
|
2278
2350
|
scope.database.prepare(`
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
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
|
-
|
|
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
|
|
2310
|
-
var SpillRowSchema =
|
|
2311
|
-
payload:
|
|
2312
|
-
bytes:
|
|
2313
|
-
authorization_payload:
|
|
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 =
|
|
2316
|
-
reference_id:
|
|
2317
|
-
bytes:
|
|
2318
|
-
created_at:
|
|
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
|
|
2406
|
-
var GoalSchema =
|
|
2407
|
-
objective:
|
|
2408
|
-
status:
|
|
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 =
|
|
2411
|
-
step:
|
|
2412
|
-
status:
|
|
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:
|
|
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:
|
|
2444
|
-
objective:
|
|
2445
|
-
status:
|
|
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:
|
|
2464
|
-
output:
|
|
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:
|
|
2476
|
-
output:
|
|
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
|
|
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,
|