stitchkit 0.85.1 → 0.86.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 +178 -0
- package/README.md +3 -0
- package/dist/agent-runtime/agent-tool.d.ts +25 -0
- package/dist/agent-runtime/agent-tool.d.ts.map +1 -0
- package/dist/agent-runtime/child-tools.d.ts +69 -0
- package/dist/agent-runtime/child-tools.d.ts.map +1 -0
- package/dist/agent-runtime/children.d.ts +113 -0
- package/dist/agent-runtime/children.d.ts.map +1 -0
- package/dist/agent-runtime/coding-tool-contract.d.ts +29 -0
- package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-refusals.d.ts +2 -0
- package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tools.d.ts +1 -0
- package/dist/agent-runtime/coding-tools.d.ts.map +1 -1
- package/dist/agent-runtime/control-schema.d.ts +17 -0
- package/dist/agent-runtime/control-schema.d.ts.map +1 -1
- package/dist/agent-runtime/event-schema.d.ts +34 -0
- package/dist/agent-runtime/event-schema.d.ts.map +1 -1
- package/dist/agent-runtime/event-search-tools.d.ts +53 -0
- package/dist/agent-runtime/event-search-tools.d.ts.map +1 -0
- package/dist/agent-runtime/event-search.d.ts +23 -0
- package/dist/agent-runtime/event-search.d.ts.map +1 -0
- package/dist/agent-runtime/fault-bench.d.ts +45 -0
- package/dist/agent-runtime/fault-bench.d.ts.map +1 -0
- package/dist/agent-runtime/observability.d.ts +6 -0
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/projections.d.ts +69 -0
- package/dist/agent-runtime/projections.d.ts.map +1 -0
- package/dist/agent-runtime/provider-failure.d.ts +5 -1
- package/dist/agent-runtime/provider-failure.d.ts.map +1 -1
- package/dist/agent-runtime/provider-origin.d.ts +12 -0
- package/dist/agent-runtime/provider-origin.d.ts.map +1 -0
- package/dist/agent-runtime/retry-policy.d.ts +27 -0
- package/dist/agent-runtime/retry-policy.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution.d.ts +0 -9
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/run-mutation-queue.d.ts +21 -0
- package/dist/agent-runtime/run-mutation-queue.d.ts.map +1 -0
- package/dist/agent-runtime/run-operation-lifecycle.d.ts +3 -0
- package/dist/agent-runtime/run-operation-lifecycle.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +15 -0
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox.d.ts +66 -0
- package/dist/agent-runtime/sandbox.d.ts.map +1 -0
- package/dist/agent-runtime/schedule-tools.d.ts +35 -0
- package/dist/agent-runtime/schedule-tools.d.ts.map +1 -0
- package/dist/agent-runtime/schedules.d.ts +63 -0
- package/dist/agent-runtime/schedules.d.ts.map +1 -0
- package/dist/agent-runtime/schemas.d.ts +9 -0
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime/spill.d.ts +24 -0
- package/dist/agent-runtime/spill.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -1
- package/dist/agent-runtime/sqlite.d.ts +26 -0
- package/dist/agent-runtime/sqlite.d.ts.map +1 -1
- package/dist/agent-runtime/state-slots.d.ts +48 -0
- package/dist/agent-runtime/state-slots.d.ts.map +1 -0
- package/dist/agent-runtime/state-tools.d.ts +58 -0
- package/dist/agent-runtime/state-tools.d.ts.map +1 -0
- package/dist/agent-runtime/store-driver.d.ts +13 -0
- package/dist/agent-runtime/store-driver.d.ts.map +1 -1
- package/dist/agent-runtime/store-events.d.ts +829 -0
- package/dist/agent-runtime/store-events.d.ts.map +1 -0
- package/dist/agent-runtime/store-migrations/v1-to-v2.d.ts +6 -0
- package/dist/agent-runtime/store-migrations/v1-to-v2.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +45 -0
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-commit.d.ts +1 -1
- package/dist/agent-runtime-browser.js +5 -3
- package/dist/agent-runtime-coding-tools.js +144 -15
- package/dist/agent-runtime-harness.js +12 -10
- package/dist/agent-runtime-sqlite-bun.d.ts +1 -1
- package/dist/agent-runtime-sqlite-bun.d.ts.map +1 -1
- package/dist/agent-runtime-sqlite-bun.js +4 -3
- package/dist/agent-runtime-sqlite-node.d.ts +1 -1
- package/dist/agent-runtime-sqlite-node.d.ts.map +1 -1
- package/dist/agent-runtime-sqlite-node.js +4 -3
- package/dist/agent-runtime-testing.d.ts +3 -0
- package/dist/agent-runtime-testing.d.ts.map +1 -0
- package/dist/agent-runtime-testing.js +156 -0
- package/dist/agent-runtime.d.ts +16 -2
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +1591 -154
- package/dist/application.js +14 -14
- package/dist/cli.js +6 -6
- package/dist/{index-fwbkp2jn.js → index-0vfdh685.js} +2 -2
- package/dist/{index-5z8tezpx.js → index-0ydchz76.js} +3 -3
- package/dist/{index-3ds42w1w.js → index-1szetq6f.js} +33 -4
- package/dist/{index-jpp7jd1p.js → index-33xckd7h.js} +898 -425
- package/dist/{index-27487ay5.js → index-9hw9eq48.js} +4 -4
- package/dist/{index-66nxrgy0.js → index-b7qwhbzq.js} +428 -46
- package/dist/index-cnyk6te3.js +77 -0
- package/dist/index-gskm1k5a.js +50 -0
- package/dist/{index-fq492hg0.js → index-hber83mk.js} +254 -157
- package/dist/{index-4hk633vz.js → index-hkm6wysp.js} +9 -2
- package/dist/{index-dx9xehwt.js → index-kc6h6hg0.js} +1 -1
- package/dist/{index-x15ss2dx.js → index-p1b1y93x.js} +3 -0
- package/dist/{index-mgtxfe6b.js → index-r159gjwy.js} +8 -1
- package/dist/{index-s4ckmrac.js → index-rcgsnrr6.js} +3 -3
- package/dist/index-vfgb58nf.js +252 -0
- package/dist/{index-dggwnnjv.js → index-wn9cb82y.js} +1 -1
- package/dist/{index-ff0kcqvp.js → index-z2452tsw.js} +3 -3
- package/dist/testing/agent-store-conformance.d.ts.map +1 -1
- package/dist/testing.js +54 -80
- package/dist/tool-invoker.js +4 -4
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +16 -0
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +10 -8
- package/llms-full.txt +321 -13
- package/package.json +6 -1
package/dist/agent-runtime.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
messageOf
|
|
3
|
-
} from "./index-p13mwz16.js";
|
|
4
1
|
import {
|
|
5
2
|
ACTIVE_AGENT_RUN_STATES,
|
|
6
|
-
AcceptInputAndAssignRunSchema,
|
|
7
|
-
AcquireAgentRunSchema,
|
|
8
3
|
AgentAdmissionReceiptSchema,
|
|
9
4
|
AgentConversationMessagePageSchema,
|
|
10
5
|
AgentConversationPageSchema,
|
|
@@ -13,43 +8,57 @@ import {
|
|
|
13
8
|
AgentConversationPurgedError,
|
|
14
9
|
AgentConversationSummarySchema,
|
|
15
10
|
AgentHistoryMutationSchema,
|
|
16
|
-
AgentRecoverableDescriptorSchema,
|
|
17
|
-
AgentRecoverablePageSchema,
|
|
18
|
-
AgentRunViewSchema,
|
|
19
11
|
AgentRuntimeHeadSchema,
|
|
20
|
-
AgentStoreAppliedSchema,
|
|
21
|
-
AgentStoreConflictSchema,
|
|
22
|
-
AgentStoreDuplicateSchema,
|
|
23
|
-
AgentStoreMutationResultSchema,
|
|
24
|
-
AgentStoreNotFoundSchema,
|
|
25
12
|
AgentStoredRunSchema,
|
|
26
|
-
CheckpointRunAssistantSchema,
|
|
27
|
-
CommitRunTerminalSchema,
|
|
28
|
-
RecordRunOperationSchema,
|
|
29
|
-
RecoverAgentRunSchema,
|
|
30
|
-
ReplaceCompactedRangeSchema,
|
|
31
|
-
RequestRunInterruptSchema,
|
|
32
13
|
createAgentRuntimeStore,
|
|
33
14
|
createMemoryAgentRuntimeStore,
|
|
34
15
|
purgeAgentConversation
|
|
35
|
-
} from "./index-
|
|
16
|
+
} from "./index-hber83mk.js";
|
|
36
17
|
import {
|
|
37
18
|
AgentContextOverflowError,
|
|
19
|
+
AgentProviderStreamCutError,
|
|
38
20
|
AgentRuntimeConflictError,
|
|
39
21
|
AgentTokenCountSchema,
|
|
22
|
+
agentGoalStateSlot,
|
|
23
|
+
agentTodoStateSlot,
|
|
24
|
+
classifyProviderFailure,
|
|
40
25
|
composeAgentPrompt,
|
|
41
26
|
createAgentRuntime,
|
|
42
27
|
createAgentRuntimeEventSink,
|
|
43
28
|
createAgentSessionCoordinator,
|
|
29
|
+
createAgentStateSlotStore,
|
|
44
30
|
createAgentToolFenceLifecycle,
|
|
45
31
|
deferredToolCatalog,
|
|
32
|
+
defineStateSlot,
|
|
33
|
+
isToolResultFailure,
|
|
34
|
+
normalizeSdkUsage,
|
|
46
35
|
projectAgentHistory,
|
|
47
36
|
projectAgentHistoryDetailed,
|
|
37
|
+
recordAgentRetryDecision,
|
|
48
38
|
registerDeferredToolCatalog,
|
|
49
39
|
registerDeferredToolRepair,
|
|
40
|
+
renderAgentStateSlots,
|
|
50
41
|
repairedSearchCall,
|
|
51
42
|
selectAgentHistory
|
|
52
|
-
} from "./index-
|
|
43
|
+
} from "./index-33xckd7h.js";
|
|
44
|
+
import"./index-p13mwz16.js";
|
|
45
|
+
import {
|
|
46
|
+
AgentAdmissionEventSchema,
|
|
47
|
+
AgentAttemptResetEventSchema,
|
|
48
|
+
AgentCheckpointEventSchema,
|
|
49
|
+
AgentReasoningDeltaEventSchema,
|
|
50
|
+
AgentReasoningEndEventSchema,
|
|
51
|
+
AgentReasoningStartEventSchema,
|
|
52
|
+
AgentRunOperationEventSchema,
|
|
53
|
+
AgentRunStateEventSchema,
|
|
54
|
+
AgentRuntimeEventCursorSchema,
|
|
55
|
+
AgentRuntimeEventSchema,
|
|
56
|
+
AgentTerminalEventSchema,
|
|
57
|
+
AgentToolStatusEventSchema,
|
|
58
|
+
AgentTransientDeltaEventSchema,
|
|
59
|
+
advanceAgentRuntimeEventCursor,
|
|
60
|
+
agentDurableEventId
|
|
61
|
+
} from "./index-hkm6wysp.js";
|
|
53
62
|
import {
|
|
54
63
|
AgentModelCapabilitySchema,
|
|
55
64
|
AgentModelCatalogEntrySchema,
|
|
@@ -72,24 +81,34 @@ import {
|
|
|
72
81
|
isCompleteAgentHistoryTurn
|
|
73
82
|
} from "./index-y2s6h5bf.js";
|
|
74
83
|
import {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
AcceptInputAndAssignRunSchema,
|
|
85
|
+
AcquireAgentRunSchema,
|
|
86
|
+
AgentConversationArchiveSchema,
|
|
87
|
+
AgentRecoverableDescriptorSchema,
|
|
88
|
+
AgentRecoverablePageSchema,
|
|
89
|
+
AgentRunViewSchema,
|
|
90
|
+
AgentStoreAppliedSchema,
|
|
91
|
+
AgentStoreConflictSchema,
|
|
92
|
+
AgentStoreDuplicateSchema,
|
|
93
|
+
AgentStoreEventEnvelopeSchema,
|
|
94
|
+
AgentStoreEventKindSchema,
|
|
95
|
+
AgentStoreEventPageSchema,
|
|
96
|
+
AgentStoreMutationResultSchema,
|
|
97
|
+
AgentStoreNotFoundSchema,
|
|
98
|
+
AgentStoreTransitionSchema,
|
|
99
|
+
AppendAgentStoreEventSchema,
|
|
100
|
+
CheckpointRunAssistantSchema,
|
|
101
|
+
CommitRunTerminalSchema,
|
|
102
|
+
ReadAgentStoreEventsSchema,
|
|
103
|
+
RecordRunOperationSchema,
|
|
104
|
+
RecoverAgentRunSchema,
|
|
105
|
+
ReplaceCompactedRangeSchema,
|
|
106
|
+
RequestRunInterruptSchema,
|
|
107
|
+
canonicalAgentJson,
|
|
108
|
+
decodeAgentConversationArchive,
|
|
109
|
+
decodeAgentStoreEvent,
|
|
110
|
+
encodeAgentConversationArchive
|
|
111
|
+
} from "./index-vfgb58nf.js";
|
|
93
112
|
import {
|
|
94
113
|
AgentAssistantPlaceholderSchema,
|
|
95
114
|
AgentControlPartSchema,
|
|
@@ -125,21 +144,31 @@ import {
|
|
|
125
144
|
AgentUsageSchema,
|
|
126
145
|
AgentUsageValueSchema,
|
|
127
146
|
runStateForTerminalReason
|
|
128
|
-
} from "./index-
|
|
147
|
+
} from "./index-p1b1y93x.js";
|
|
148
|
+
import {
|
|
149
|
+
createBoundedSinkManager
|
|
150
|
+
} from "./index-wnfk50r0.js";
|
|
151
|
+
import {
|
|
152
|
+
AgentSandboxGradeSchema,
|
|
153
|
+
AgentSandboxRestrictionSchema,
|
|
154
|
+
missingSandboxRestrictions,
|
|
155
|
+
probeAgentProcessSandbox,
|
|
156
|
+
recordAgentSandboxProbe
|
|
157
|
+
} from "./index-gskm1k5a.js";
|
|
129
158
|
import {
|
|
130
159
|
buildToolManifest,
|
|
131
160
|
mountAgent
|
|
132
|
-
} from "./index-
|
|
161
|
+
} from "./index-1szetq6f.js";
|
|
133
162
|
import"./index-3xnq72rz.js";
|
|
134
|
-
import"./index-
|
|
163
|
+
import"./index-rcgsnrr6.js";
|
|
135
164
|
import {
|
|
136
165
|
defineRuntimeTool
|
|
137
|
-
} from "./index-
|
|
138
|
-
import"./index-
|
|
139
|
-
import"./index-
|
|
166
|
+
} from "./index-kc6h6hg0.js";
|
|
167
|
+
import"./index-0vfdh685.js";
|
|
168
|
+
import"./index-wn9cb82y.js";
|
|
140
169
|
import"./index-vsbzgd7b.js";
|
|
141
170
|
import"./index-f9mb610r.js";
|
|
142
|
-
import"./index-
|
|
171
|
+
import"./index-r159gjwy.js";
|
|
143
172
|
import"./index-cby4ar3v.js";
|
|
144
173
|
import"./index-7zbps32p.js";
|
|
145
174
|
import"./index-7evdsyez.js";
|
|
@@ -152,10 +181,501 @@ import {
|
|
|
152
181
|
} from "./index-zcgf3gqf.js";
|
|
153
182
|
import"./index-kzfs85xp.js";
|
|
154
183
|
import {
|
|
155
|
-
executableAgentRuntimeTools
|
|
156
|
-
isRecord
|
|
184
|
+
executableAgentRuntimeTools
|
|
157
185
|
} from "./index-77fekveh.js";
|
|
158
186
|
|
|
187
|
+
// src/agent-runtime/child-tools.ts
|
|
188
|
+
import { z as z2 } from "zod";
|
|
189
|
+
|
|
190
|
+
// src/agent-runtime/agent-tool.ts
|
|
191
|
+
function defineAgentTool(definition) {
|
|
192
|
+
return {
|
|
193
|
+
...definition,
|
|
194
|
+
handler: (context) => definition.handler(context)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/agent-runtime/children.ts
|
|
199
|
+
import { randomUUID } from "node:crypto";
|
|
200
|
+
import { z } from "zod";
|
|
201
|
+
var AgentChildBudgetSchema = z.object({
|
|
202
|
+
usd: z.number().positive().optional(),
|
|
203
|
+
tokens: z.int().positive().optional(),
|
|
204
|
+
milliseconds: z.int().positive().optional()
|
|
205
|
+
}).strict();
|
|
206
|
+
var AgentChildStateSchema = z.enum([
|
|
207
|
+
"spawned",
|
|
208
|
+
"running",
|
|
209
|
+
"finished",
|
|
210
|
+
"stopped",
|
|
211
|
+
"lost"
|
|
212
|
+
]);
|
|
213
|
+
var AgentChildRecordSchema = z.object({
|
|
214
|
+
parentConversationId: z.string().min(1),
|
|
215
|
+
childConversationId: z.string().min(1),
|
|
216
|
+
seedUptoSeq: z.int().nonnegative(),
|
|
217
|
+
state: AgentChildStateSchema,
|
|
218
|
+
budget: AgentChildBudgetSchema,
|
|
219
|
+
usage: z.object({
|
|
220
|
+
tokens: z.int().nonnegative(),
|
|
221
|
+
usd: z.number().nonnegative(),
|
|
222
|
+
milliseconds: z.int().nonnegative()
|
|
223
|
+
}).strict(),
|
|
224
|
+
resultReference: z.string().optional(),
|
|
225
|
+
createdAt: z.string(),
|
|
226
|
+
updatedAt: z.string()
|
|
227
|
+
}).strict();
|
|
228
|
+
var ChildRowSchema = z.object({
|
|
229
|
+
parent_conversation_id: z.string(),
|
|
230
|
+
child_conversation_id: z.string(),
|
|
231
|
+
seed_upto_seq: z.int().nonnegative(),
|
|
232
|
+
state: AgentChildStateSchema,
|
|
233
|
+
budget_payload: z.string(),
|
|
234
|
+
usage_payload: z.string(),
|
|
235
|
+
result_reference: z.string().nullable(),
|
|
236
|
+
created_at: z.string(),
|
|
237
|
+
updated_at: z.string()
|
|
238
|
+
});
|
|
239
|
+
function parseChild(raw) {
|
|
240
|
+
const row = ChildRowSchema.parse(raw);
|
|
241
|
+
return AgentChildRecordSchema.parse({
|
|
242
|
+
parentConversationId: row.parent_conversation_id,
|
|
243
|
+
childConversationId: row.child_conversation_id,
|
|
244
|
+
seedUptoSeq: row.seed_upto_seq,
|
|
245
|
+
state: row.state,
|
|
246
|
+
budget: JSON.parse(row.budget_payload),
|
|
247
|
+
usage: JSON.parse(row.usage_payload),
|
|
248
|
+
...row.result_reference && { resultReference: row.result_reference },
|
|
249
|
+
createdAt: row.created_at,
|
|
250
|
+
updatedAt: row.updated_at
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function usageNumbers(usage) {
|
|
254
|
+
const inputTokens = usage.inputTokens.value;
|
|
255
|
+
const outputTokens = usage.outputTokens.value;
|
|
256
|
+
return {
|
|
257
|
+
...inputTokens !== undefined && outputTokens !== undefined ? { tokens: inputTokens + outputTokens } : {},
|
|
258
|
+
...usage.cost?.currency === "USD" && usage.cost.value !== undefined ? { usd: usage.cost.value } : {}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function createSqliteAgentChildManager(input) {
|
|
262
|
+
const now = input.now ?? (() => new Date);
|
|
263
|
+
const database = input.sqlite.database;
|
|
264
|
+
const store = input.sqlite.store;
|
|
265
|
+
const handles = new Map;
|
|
266
|
+
const settlements = new Map;
|
|
267
|
+
const currentState = (childConversationId) => {
|
|
268
|
+
const raw = database.prepare("SELECT state FROM stitchkit_agent_runtime_children WHERE child_conversation_id = ?").get(childConversationId);
|
|
269
|
+
return raw === null || raw === undefined ? undefined : AgentChildStateSchema.parse(raw.state);
|
|
270
|
+
};
|
|
271
|
+
const isLive = (childConversationId) => {
|
|
272
|
+
const state = currentState(childConversationId);
|
|
273
|
+
return state === "spawned" || state === "running";
|
|
274
|
+
};
|
|
275
|
+
const settleable = (childConversationId) => {
|
|
276
|
+
const state = currentState(childConversationId);
|
|
277
|
+
return state === undefined || state === "spawned" || state === "running";
|
|
278
|
+
};
|
|
279
|
+
const STOP_BOUND_MS = 1e4;
|
|
280
|
+
const bounded = async (work, fallback) => {
|
|
281
|
+
let timer;
|
|
282
|
+
try {
|
|
283
|
+
return await Promise.race([
|
|
284
|
+
Promise.resolve(work),
|
|
285
|
+
new Promise((resolve) => {
|
|
286
|
+
timer = setTimeout(() => resolve(fallback), STOP_BOUND_MS);
|
|
287
|
+
})
|
|
288
|
+
]);
|
|
289
|
+
} finally {
|
|
290
|
+
if (timer)
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const listChildren = (parentConversationId) => database.prepare(`
|
|
295
|
+
SELECT parent_conversation_id, child_conversation_id, seed_upto_seq, state,
|
|
296
|
+
budget_payload, usage_payload, result_reference, created_at, updated_at
|
|
297
|
+
FROM stitchkit_agent_runtime_children
|
|
298
|
+
WHERE parent_conversation_id = ? ORDER BY created_at, child_conversation_id
|
|
299
|
+
`).all(parentConversationId).map(parseChild);
|
|
300
|
+
const updateState = async (record, state, resultReference, details = {}) => {
|
|
301
|
+
const updatedAt = now().toISOString();
|
|
302
|
+
const payload = {
|
|
303
|
+
childConversationId: record.childConversationId,
|
|
304
|
+
state,
|
|
305
|
+
...resultReference && { resultReference },
|
|
306
|
+
...details
|
|
307
|
+
};
|
|
308
|
+
const childPayload = {
|
|
309
|
+
parentConversationId: record.parentConversationId,
|
|
310
|
+
state,
|
|
311
|
+
...resultReference && { resultReference },
|
|
312
|
+
...details
|
|
313
|
+
};
|
|
314
|
+
const write = (audience) => input.sqlite.transaction(async (scope) => {
|
|
315
|
+
scope.database.prepare(`
|
|
316
|
+
UPDATE stitchkit_agent_runtime_children
|
|
317
|
+
SET state = ?, result_reference = ?, updated_at = ? WHERE child_conversation_id = ?
|
|
318
|
+
`).run(state, resultReference ?? null, updatedAt, record.childConversationId);
|
|
319
|
+
if (audience === "both") {
|
|
320
|
+
await scope.appendEvent({
|
|
321
|
+
conversationId: record.parentConversationId,
|
|
322
|
+
kind: "child/state",
|
|
323
|
+
occurredAt: updatedAt,
|
|
324
|
+
payload
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (audience !== "row") {
|
|
328
|
+
await scope.appendEvent({
|
|
329
|
+
conversationId: record.childConversationId,
|
|
330
|
+
kind: "child/state",
|
|
331
|
+
occurredAt: updatedAt,
|
|
332
|
+
payload: childPayload
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
try {
|
|
337
|
+
await write("both");
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (!(error instanceof AgentConversationPurgedError))
|
|
340
|
+
throw error;
|
|
341
|
+
try {
|
|
342
|
+
await write("child");
|
|
343
|
+
} catch (inner) {
|
|
344
|
+
if (!(inner instanceof AgentConversationPurgedError))
|
|
345
|
+
throw inner;
|
|
346
|
+
await write("row");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
const spawnChild = async (request) => {
|
|
351
|
+
const requestedBudget = AgentChildBudgetSchema.parse(request.budget);
|
|
352
|
+
const remaining = input.remainingBudget ? AgentChildBudgetSchema.parse(await input.remainingBudget(request.parentConversationId)) : undefined;
|
|
353
|
+
const bounded2 = (requested, available) => {
|
|
354
|
+
if (requested === undefined)
|
|
355
|
+
return available;
|
|
356
|
+
if (available === undefined)
|
|
357
|
+
return requested;
|
|
358
|
+
return Math.min(requested, available);
|
|
359
|
+
};
|
|
360
|
+
const usd = bounded2(requestedBudget.usd, remaining?.usd);
|
|
361
|
+
const tokens = bounded2(requestedBudget.tokens, remaining?.tokens);
|
|
362
|
+
const milliseconds = bounded2(requestedBudget.milliseconds, remaining?.milliseconds);
|
|
363
|
+
const budget = AgentChildBudgetSchema.parse({
|
|
364
|
+
...usd !== undefined && { usd },
|
|
365
|
+
...tokens !== undefined && { tokens },
|
|
366
|
+
...milliseconds !== undefined && { milliseconds }
|
|
367
|
+
});
|
|
368
|
+
const events = [];
|
|
369
|
+
let cursor = 1;
|
|
370
|
+
for (;; ) {
|
|
371
|
+
const page = await store.readEvents({
|
|
372
|
+
conversationId: request.parentConversationId,
|
|
373
|
+
fromSeq: cursor,
|
|
374
|
+
...request.seedUptoSeq !== undefined && { toSeq: request.seedUptoSeq },
|
|
375
|
+
limit: 1000
|
|
376
|
+
});
|
|
377
|
+
events.push(...page.items);
|
|
378
|
+
if (page.nextSeq === undefined)
|
|
379
|
+
break;
|
|
380
|
+
cursor = page.nextSeq;
|
|
381
|
+
}
|
|
382
|
+
const seedUptoSeq = events.at(-1)?.seq ?? 0;
|
|
383
|
+
const childConversationId = request.childConversationId ?? randomUUID();
|
|
384
|
+
const createdAt = now().toISOString();
|
|
385
|
+
const record = AgentChildRecordSchema.parse({
|
|
386
|
+
parentConversationId: request.parentConversationId,
|
|
387
|
+
childConversationId,
|
|
388
|
+
seedUptoSeq,
|
|
389
|
+
state: "spawned",
|
|
390
|
+
budget,
|
|
391
|
+
usage: { tokens: 0, usd: 0, milliseconds: 0 },
|
|
392
|
+
createdAt,
|
|
393
|
+
updatedAt: createdAt
|
|
394
|
+
});
|
|
395
|
+
const seedArchive = encodeAgentConversationArchive({
|
|
396
|
+
format: "stitchkit.agent-conversation",
|
|
397
|
+
formatVersion: 1,
|
|
398
|
+
conversationId: request.parentConversationId,
|
|
399
|
+
events,
|
|
400
|
+
projections: [],
|
|
401
|
+
spills: []
|
|
402
|
+
});
|
|
403
|
+
const handle = await input.spawn({
|
|
404
|
+
parentConversationId: record.parentConversationId,
|
|
405
|
+
childConversationId,
|
|
406
|
+
seedArchive,
|
|
407
|
+
childInput: request.childInput,
|
|
408
|
+
budget
|
|
409
|
+
});
|
|
410
|
+
await input.sqlite.transaction(async (scope) => {
|
|
411
|
+
scope.database.prepare(`
|
|
412
|
+
INSERT INTO stitchkit_agent_runtime_children (
|
|
413
|
+
parent_conversation_id, child_conversation_id, seed_upto_seq, state,
|
|
414
|
+
budget_payload, usage_payload, result_reference, created_at, updated_at
|
|
415
|
+
) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
416
|
+
`).run(record.parentConversationId, record.childConversationId, record.seedUptoSeq, record.state, JSON.stringify(record.budget), JSON.stringify(record.usage), createdAt, createdAt);
|
|
417
|
+
await scope.appendEvent({
|
|
418
|
+
conversationId: record.parentConversationId,
|
|
419
|
+
kind: "child/spawned",
|
|
420
|
+
occurredAt: createdAt,
|
|
421
|
+
payload: { childConversationId, seedUptoSeq, budget }
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
handles.set(childConversationId, handle);
|
|
425
|
+
await updateState(record, "running");
|
|
426
|
+
const settlement = handle.result.then(async (result) => {
|
|
427
|
+
handles.delete(childConversationId);
|
|
428
|
+
if (settleable(childConversationId)) {
|
|
429
|
+
await updateState(record, "finished", result.resultReference);
|
|
430
|
+
}
|
|
431
|
+
}, async () => {
|
|
432
|
+
handles.delete(childConversationId);
|
|
433
|
+
if (settleable(childConversationId))
|
|
434
|
+
await updateState(record, "stopped");
|
|
435
|
+
});
|
|
436
|
+
settlements.set(childConversationId, settlement);
|
|
437
|
+
return { ...record, state: "running", updatedAt: now().toISOString() };
|
|
438
|
+
};
|
|
439
|
+
const recordStepUsage = async (request) => {
|
|
440
|
+
const raw = database.prepare(`
|
|
441
|
+
SELECT parent_conversation_id, child_conversation_id, seed_upto_seq, state,
|
|
442
|
+
budget_payload, usage_payload, result_reference, created_at, updated_at
|
|
443
|
+
FROM stitchkit_agent_runtime_children WHERE child_conversation_id = ?
|
|
444
|
+
`).get(request.childConversationId);
|
|
445
|
+
if (raw === null || raw === undefined)
|
|
446
|
+
throw new TypeError("Unknown child conversation");
|
|
447
|
+
const record = parseChild(raw);
|
|
448
|
+
const measured = usageNumbers(request.usage);
|
|
449
|
+
if (record.budget.tokens !== undefined && measured.tokens === undefined) {
|
|
450
|
+
throw new TypeError("Cannot enforce child token budget without measured token usage");
|
|
451
|
+
}
|
|
452
|
+
if (record.budget.usd !== undefined && measured.usd === undefined) {
|
|
453
|
+
throw new TypeError("Cannot enforce child USD budget without USD usage");
|
|
454
|
+
}
|
|
455
|
+
const usage = {
|
|
456
|
+
tokens: record.usage.tokens + (measured.tokens ?? 0),
|
|
457
|
+
usd: record.usage.usd + (measured.usd ?? 0),
|
|
458
|
+
milliseconds: record.usage.milliseconds + request.elapsedMs
|
|
459
|
+
};
|
|
460
|
+
const updatedAt = now().toISOString();
|
|
461
|
+
const overrun = {
|
|
462
|
+
tokens: Math.max(0, usage.tokens - (record.budget.tokens ?? Number.POSITIVE_INFINITY)),
|
|
463
|
+
usd: Math.max(0, usage.usd - (record.budget.usd ?? Number.POSITIVE_INFINITY)),
|
|
464
|
+
milliseconds: Math.max(0, usage.milliseconds - (record.budget.milliseconds ?? Number.POSITIVE_INFINITY))
|
|
465
|
+
};
|
|
466
|
+
const stop = record.budget.tokens !== undefined && usage.tokens >= record.budget.tokens || record.budget.usd !== undefined && usage.usd >= record.budget.usd || record.budget.milliseconds !== undefined && usage.milliseconds >= record.budget.milliseconds;
|
|
467
|
+
const handle = handles.get(request.childConversationId);
|
|
468
|
+
let enforced = false;
|
|
469
|
+
if (stop && handle) {
|
|
470
|
+
enforced = await bounded(Promise.resolve().then(() => handle.stopPolicy("child-budget")).then(() => true, () => false), false);
|
|
471
|
+
}
|
|
472
|
+
const payload = {
|
|
473
|
+
childConversationId: record.childConversationId,
|
|
474
|
+
state: "running",
|
|
475
|
+
usage,
|
|
476
|
+
overrun,
|
|
477
|
+
...stop && { policyName: "child-budget", enforced }
|
|
478
|
+
};
|
|
479
|
+
await input.sqlite.transaction(async (scope) => {
|
|
480
|
+
scope.database.prepare("UPDATE stitchkit_agent_runtime_children SET usage_payload = ?, updated_at = ? WHERE child_conversation_id = ?").run(JSON.stringify(usage), updatedAt, request.childConversationId);
|
|
481
|
+
await scope.appendEvent({
|
|
482
|
+
conversationId: record.parentConversationId,
|
|
483
|
+
kind: "child/state",
|
|
484
|
+
occurredAt: updatedAt,
|
|
485
|
+
payload
|
|
486
|
+
});
|
|
487
|
+
await scope.appendEvent({
|
|
488
|
+
conversationId: record.childConversationId,
|
|
489
|
+
kind: "child/state",
|
|
490
|
+
occurredAt: updatedAt,
|
|
491
|
+
payload: { parentConversationId: record.parentConversationId, ...payload }
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
return { stop, enforced, ...stop && { policyName: "child-budget" }, overrun };
|
|
495
|
+
};
|
|
496
|
+
const stopChildren = async (parentConversationId) => {
|
|
497
|
+
const records = listChildren(parentConversationId).filter((record) => record.state === "spawned" || record.state === "running");
|
|
498
|
+
await Promise.all(records.map(async (record) => {
|
|
499
|
+
const startedAt = performance.now();
|
|
500
|
+
const handle = handles.get(record.childConversationId);
|
|
501
|
+
const reachable = handle ? handle.reachable ? await bounded(handle.reachable(), false) : true : false;
|
|
502
|
+
if (!reachable) {
|
|
503
|
+
if (isLive(record.childConversationId)) {
|
|
504
|
+
await updateState(record, "lost", undefined, {
|
|
505
|
+
stopDurationMs: Math.max(0, performance.now() - startedAt)
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
await bounded(Promise.resolve(handle?.stopPolicy("parent-stopped")).then(() => true), false);
|
|
511
|
+
if (!isLive(record.childConversationId))
|
|
512
|
+
return;
|
|
513
|
+
await updateState(record, "stopped", undefined, {
|
|
514
|
+
stopDurationMs: Math.max(0, performance.now() - startedAt)
|
|
515
|
+
});
|
|
516
|
+
}));
|
|
517
|
+
};
|
|
518
|
+
const activeChild = (parentConversationId, childConversationId) => {
|
|
519
|
+
const child = listChildren(parentConversationId).find((record) => record.childConversationId === childConversationId);
|
|
520
|
+
if (!child)
|
|
521
|
+
throw new TypeError("Unknown child conversation for this parent");
|
|
522
|
+
if (child.state !== "running" && child.state !== "spawned") {
|
|
523
|
+
throw new TypeError("Child conversation is not active");
|
|
524
|
+
}
|
|
525
|
+
const handle = handles.get(childConversationId);
|
|
526
|
+
if (!handle)
|
|
527
|
+
throw new TypeError("Child host handle is unavailable");
|
|
528
|
+
return { child, handle };
|
|
529
|
+
};
|
|
530
|
+
const sendMessage = async (parentConversationId, childConversationId, message) => {
|
|
531
|
+
const { handle } = activeChild(parentConversationId, childConversationId);
|
|
532
|
+
if (!handle.sendMessage)
|
|
533
|
+
throw new TypeError("Child host does not support messages");
|
|
534
|
+
await handle.sendMessage(message);
|
|
535
|
+
await store.appendEvent({
|
|
536
|
+
conversationId: parentConversationId,
|
|
537
|
+
kind: "child/state",
|
|
538
|
+
payload: { childConversationId, action: "message-sent" }
|
|
539
|
+
});
|
|
540
|
+
};
|
|
541
|
+
const interruptChild = async (parentConversationId, childConversationId) => {
|
|
542
|
+
const { child, handle } = activeChild(parentConversationId, childConversationId);
|
|
543
|
+
const startedAt = performance.now();
|
|
544
|
+
await handle.stopPolicy("parent-interrupt");
|
|
545
|
+
await updateState(child, "stopped", undefined, {
|
|
546
|
+
policyName: "parent-interrupt",
|
|
547
|
+
stopDurationMs: Math.max(0, performance.now() - startedAt)
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
const waitChild = async (childConversationId) => {
|
|
551
|
+
await settlements.get(childConversationId);
|
|
552
|
+
};
|
|
553
|
+
return {
|
|
554
|
+
spawnChild,
|
|
555
|
+
listChildren,
|
|
556
|
+
recordStepUsage,
|
|
557
|
+
stopChildren,
|
|
558
|
+
sendMessage,
|
|
559
|
+
interruptChild,
|
|
560
|
+
waitChild
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function agentChildBudgetStopPolicy(input) {
|
|
564
|
+
let stepsSeen = 0;
|
|
565
|
+
let lastBoundary = performance.now();
|
|
566
|
+
return {
|
|
567
|
+
name: "child-budget",
|
|
568
|
+
when: async ({ steps }) => {
|
|
569
|
+
const step = steps.at(-1);
|
|
570
|
+
if (!step || steps.length <= stepsSeen)
|
|
571
|
+
return false;
|
|
572
|
+
stepsSeen = steps.length;
|
|
573
|
+
const elapsedMs = Math.max(0, performance.now() - lastBoundary);
|
|
574
|
+
lastBoundary = performance.now();
|
|
575
|
+
const usage = normalizeSdkUsage(step.usage);
|
|
576
|
+
const cost = input.cost?.({ usage: step.usage });
|
|
577
|
+
const boundary = await input.manager.recordStepUsage({
|
|
578
|
+
childConversationId: input.childConversationId,
|
|
579
|
+
usage: { ...usage, ...cost && { cost } },
|
|
580
|
+
elapsedMs
|
|
581
|
+
});
|
|
582
|
+
return boundary.stop;
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/agent-runtime/child-tools.ts
|
|
588
|
+
function childTool(manager, fork) {
|
|
589
|
+
return defineAgentTool({
|
|
590
|
+
name: fork ? "subagent_fork" : "subagent",
|
|
591
|
+
description: fork ? "Run a child conversation seeded from the parent through one exact event sequence." : "Run a child conversation seeded from the current durable parent history.",
|
|
592
|
+
identity: {
|
|
593
|
+
serviceName: "agent-children",
|
|
594
|
+
action: fork ? "fork" : "spawn",
|
|
595
|
+
method: "POST"
|
|
596
|
+
},
|
|
597
|
+
input: z2.object({
|
|
598
|
+
parentConversationId: z2.string().min(1),
|
|
599
|
+
childInput: z2.json(),
|
|
600
|
+
budget: AgentChildBudgetSchema,
|
|
601
|
+
seedUptoSeq: z2.int().positive().optional()
|
|
602
|
+
}).strict(),
|
|
603
|
+
output: z2.object({
|
|
604
|
+
childConversationId: z2.string(),
|
|
605
|
+
state: z2.string(),
|
|
606
|
+
resultReference: z2.string().optional()
|
|
607
|
+
}),
|
|
608
|
+
transports: ["AGENT"],
|
|
609
|
+
handler: async ({ input }) => {
|
|
610
|
+
if (fork && input.seedUptoSeq === undefined) {
|
|
611
|
+
throw new TypeError("subagent_fork requires seedUptoSeq");
|
|
612
|
+
}
|
|
613
|
+
const child = await manager.spawnChild({
|
|
614
|
+
parentConversationId: input.parentConversationId,
|
|
615
|
+
childInput: input.childInput,
|
|
616
|
+
budget: input.budget,
|
|
617
|
+
...input.seedUptoSeq !== undefined && { seedUptoSeq: input.seedUptoSeq }
|
|
618
|
+
});
|
|
619
|
+
await manager.waitChild(child.childConversationId);
|
|
620
|
+
const finished = manager.listChildren(input.parentConversationId).find((record) => record.childConversationId === child.childConversationId);
|
|
621
|
+
if (!finished)
|
|
622
|
+
throw new Error("Child record disappeared after settlement");
|
|
623
|
+
return {
|
|
624
|
+
childConversationId: finished.childConversationId,
|
|
625
|
+
state: finished.state,
|
|
626
|
+
...finished.resultReference && { resultReference: finished.resultReference }
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
function createAgentChildTools(manager) {
|
|
632
|
+
return [
|
|
633
|
+
childTool(manager, false),
|
|
634
|
+
childTool(manager, true),
|
|
635
|
+
defineAgentTool({
|
|
636
|
+
name: "list_agents",
|
|
637
|
+
description: "List the durable child graph for one parent conversation.",
|
|
638
|
+
identity: { serviceName: "agent-children", action: "list", method: "GET" },
|
|
639
|
+
input: z2.object({ parentConversationId: z2.string().min(1) }).strict(),
|
|
640
|
+
output: z2.object({ children: z2.array(AgentChildRecordSchema) }),
|
|
641
|
+
transports: ["AGENT"],
|
|
642
|
+
handler: ({ input }) => ({
|
|
643
|
+
children: [...manager.listChildren(input.parentConversationId)]
|
|
644
|
+
})
|
|
645
|
+
}),
|
|
646
|
+
defineAgentTool({
|
|
647
|
+
name: "send_message",
|
|
648
|
+
description: "Send one follow-up input to an active child conversation.",
|
|
649
|
+
identity: { serviceName: "agent-children", action: "message", method: "POST" },
|
|
650
|
+
input: z2.object({
|
|
651
|
+
parentConversationId: z2.string().min(1),
|
|
652
|
+
childConversationId: z2.string().min(1),
|
|
653
|
+
input: z2.json()
|
|
654
|
+
}).strict(),
|
|
655
|
+
output: z2.object({ sent: z2.literal(true) }),
|
|
656
|
+
transports: ["AGENT"],
|
|
657
|
+
handler: async ({ input }) => {
|
|
658
|
+
await manager.sendMessage(input.parentConversationId, input.childConversationId, input.input);
|
|
659
|
+
return { sent: true };
|
|
660
|
+
}
|
|
661
|
+
}),
|
|
662
|
+
defineAgentTool({
|
|
663
|
+
name: "interrupt_agent",
|
|
664
|
+
description: "Stop one active child conversation at a durable policy boundary.",
|
|
665
|
+
identity: { serviceName: "agent-children", action: "interrupt", method: "POST" },
|
|
666
|
+
input: z2.object({
|
|
667
|
+
parentConversationId: z2.string().min(1),
|
|
668
|
+
childConversationId: z2.string().min(1)
|
|
669
|
+
}).strict(),
|
|
670
|
+
output: z2.object({ interrupted: z2.literal(true) }),
|
|
671
|
+
transports: ["AGENT"],
|
|
672
|
+
handler: async ({ input }) => {
|
|
673
|
+
await manager.interruptChild(input.parentConversationId, input.childConversationId);
|
|
674
|
+
return { interrupted: true };
|
|
675
|
+
}
|
|
676
|
+
})
|
|
677
|
+
];
|
|
678
|
+
}
|
|
159
679
|
// src/agent-runtime/compaction.ts
|
|
160
680
|
function groupProviderTurns(messages, evidencePolicy) {
|
|
161
681
|
const turns = [];
|
|
@@ -261,24 +781,24 @@ function structuredCompaction(config) {
|
|
|
261
781
|
};
|
|
262
782
|
}
|
|
263
783
|
// src/agent-runtime/deferred-tool-types.ts
|
|
264
|
-
import { z } from "zod";
|
|
265
|
-
var DeferredAgentToolSearchInputSchema =
|
|
266
|
-
query:
|
|
267
|
-
reason:
|
|
784
|
+
import { z as z3 } from "zod";
|
|
785
|
+
var DeferredAgentToolSearchInputSchema = z3.object({
|
|
786
|
+
query: z3.string().min(1).describe("An exact tool name, or words describing the tool. Matched as one phrase first, then word by word."),
|
|
787
|
+
reason: z3.enum(["inactive_call"]).optional()
|
|
268
788
|
});
|
|
269
|
-
var DeferredAgentToolMatchSchema =
|
|
270
|
-
name:
|
|
271
|
-
description:
|
|
789
|
+
var DeferredAgentToolMatchSchema = z3.object({
|
|
790
|
+
name: z3.string(),
|
|
791
|
+
description: z3.string()
|
|
272
792
|
});
|
|
273
|
-
var DeferredAgentToolReceiptSchema =
|
|
274
|
-
schemaVersion:
|
|
275
|
-
kind:
|
|
276
|
-
status:
|
|
277
|
-
runId:
|
|
278
|
-
surfaceKey:
|
|
279
|
-
selected:
|
|
280
|
-
matches:
|
|
281
|
-
truncated:
|
|
793
|
+
var DeferredAgentToolReceiptSchema = z3.object({
|
|
794
|
+
schemaVersion: z3.literal(1),
|
|
795
|
+
kind: z3.literal("stitchkit.deferred-tool-selection"),
|
|
796
|
+
status: z3.enum(["SELECTED", "SEARCH_REQUIRED", "NO_MATCH", "SELECTION_REFUSED"]),
|
|
797
|
+
runId: z3.string(),
|
|
798
|
+
surfaceKey: z3.string(),
|
|
799
|
+
selected: z3.array(z3.string()),
|
|
800
|
+
matches: z3.array(DeferredAgentToolMatchSchema),
|
|
801
|
+
truncated: z3.boolean()
|
|
282
802
|
}).superRefine((receipt, context) => {
|
|
283
803
|
const selected = receipt.status === "SELECTED" || receipt.status === "SEARCH_REQUIRED";
|
|
284
804
|
if (selected !== receipt.selected.length > 0) {
|
|
@@ -634,39 +1154,209 @@ function createDeferredAgentToolSurface(config) {
|
|
|
634
1154
|
}
|
|
635
1155
|
};
|
|
636
1156
|
}
|
|
1157
|
+
// src/agent-runtime/event-search.ts
|
|
1158
|
+
import { z as z4 } from "zod";
|
|
1159
|
+
var AgentEventSearchResultSchema = z4.object({
|
|
1160
|
+
conversationId: z4.string().min(1),
|
|
1161
|
+
seq: z4.int().positive(),
|
|
1162
|
+
occurredAt: z4.string(),
|
|
1163
|
+
snippet: z4.string()
|
|
1164
|
+
}).strict();
|
|
1165
|
+
var SearchRowSchema = z4.object({
|
|
1166
|
+
conversation_id: z4.string(),
|
|
1167
|
+
event_seq: z4.int().positive(),
|
|
1168
|
+
occurred_at: z4.string(),
|
|
1169
|
+
snippet: z4.string()
|
|
1170
|
+
});
|
|
1171
|
+
function literalFtsQuery(query) {
|
|
1172
|
+
return `"${query.replaceAll('"', '""')}"`;
|
|
1173
|
+
}
|
|
1174
|
+
function createSqliteAgentEventSearch(input) {
|
|
1175
|
+
return async (request) => {
|
|
1176
|
+
if (request.query.trim().length === 0)
|
|
1177
|
+
throw new TypeError("Event search query must not be empty");
|
|
1178
|
+
const limit = Math.min(request.limit ?? 20, 200);
|
|
1179
|
+
const target = request.conversationId ?? (input.authorizeConversation ? undefined : request.requestingConversationId);
|
|
1180
|
+
const values = [literalFtsQuery(request.query)];
|
|
1181
|
+
const targetClause = target ? "AND f.conversation_id = ?" : "";
|
|
1182
|
+
if (target)
|
|
1183
|
+
values.push(target);
|
|
1184
|
+
const sinceClause = request.since ? "AND f.occurred_at >= ?" : "";
|
|
1185
|
+
if (request.since)
|
|
1186
|
+
values.push(z4.iso.datetime({ offset: true }).parse(request.since));
|
|
1187
|
+
const page = limit * 4;
|
|
1188
|
+
const candidatesFrom = (offset2) => input.database.prepare(`
|
|
1189
|
+
SELECT f.conversation_id, f.event_seq, f.occurred_at,
|
|
1190
|
+
snippet(stitchkit_agent_runtime_events_fts, 3, '[', ']', ' … ', 24) AS snippet
|
|
1191
|
+
FROM stitchkit_agent_runtime_events_fts AS f
|
|
1192
|
+
WHERE stitchkit_agent_runtime_events_fts MATCH ? ${targetClause} ${sinceClause}
|
|
1193
|
+
ORDER BY bm25(stitchkit_agent_runtime_events_fts), f.occurred_at DESC, f.rowid
|
|
1194
|
+
LIMIT ? OFFSET ?
|
|
1195
|
+
`).all(...values, page, offset2).map((row) => SearchRowSchema.parse(row));
|
|
1196
|
+
const authorization = new Map;
|
|
1197
|
+
const results = [];
|
|
1198
|
+
let offset = 0;
|
|
1199
|
+
let candidates = candidatesFrom(offset);
|
|
1200
|
+
while (candidates.length > 0 && results.length < limit) {
|
|
1201
|
+
for (const row of candidates) {
|
|
1202
|
+
if (row.conversation_id !== request.requestingConversationId) {
|
|
1203
|
+
let allowed = authorization.get(row.conversation_id);
|
|
1204
|
+
if (allowed === undefined) {
|
|
1205
|
+
allowed = input.authorizeConversation ? await input.authorizeConversation({
|
|
1206
|
+
requestingConversationId: request.requestingConversationId,
|
|
1207
|
+
targetConversationId: row.conversation_id
|
|
1208
|
+
}) : false;
|
|
1209
|
+
authorization.set(row.conversation_id, allowed);
|
|
1210
|
+
}
|
|
1211
|
+
if (!allowed)
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
results.push(AgentEventSearchResultSchema.parse({
|
|
1215
|
+
conversationId: row.conversation_id,
|
|
1216
|
+
seq: row.event_seq,
|
|
1217
|
+
occurredAt: row.occurred_at,
|
|
1218
|
+
snippet: row.snippet
|
|
1219
|
+
}));
|
|
1220
|
+
if (results.length === limit)
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
offset += page;
|
|
1224
|
+
candidates = results.length < limit ? candidatesFrom(offset) : [];
|
|
1225
|
+
}
|
|
1226
|
+
return results;
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
// src/agent-runtime/event-search-tools.ts
|
|
1230
|
+
import { z as z5 } from "zod";
|
|
1231
|
+
function createAgentEventSearchTools(input) {
|
|
1232
|
+
const EventAddressSchema = z5.object({ conversationId: z5.string(), seq: z5.int().positive() }).strict();
|
|
1233
|
+
const EventOutputSchema = z5.object({
|
|
1234
|
+
conversationId: z5.string(),
|
|
1235
|
+
seq: z5.int().positive(),
|
|
1236
|
+
kind: z5.string(),
|
|
1237
|
+
occurredAt: z5.string(),
|
|
1238
|
+
payload: z5.json()
|
|
1239
|
+
}).strict();
|
|
1240
|
+
const readExact = async (address) => {
|
|
1241
|
+
if (address.conversationId !== input.conversationId) {
|
|
1242
|
+
const allowed = await input.authorizeConversation?.({
|
|
1243
|
+
requestingConversationId: input.conversationId,
|
|
1244
|
+
targetConversationId: address.conversationId
|
|
1245
|
+
});
|
|
1246
|
+
if (!allowed)
|
|
1247
|
+
throw new TypeError("Event address is outside the authorized conversation");
|
|
1248
|
+
}
|
|
1249
|
+
const page = await input.store.readEvents({
|
|
1250
|
+
conversationId: address.conversationId,
|
|
1251
|
+
fromSeq: address.seq,
|
|
1252
|
+
toSeq: address.seq,
|
|
1253
|
+
limit: 1
|
|
1254
|
+
});
|
|
1255
|
+
const event = page.items[0];
|
|
1256
|
+
if (!event || event.seq !== address.seq)
|
|
1257
|
+
throw new TypeError("Unknown event address");
|
|
1258
|
+
return {
|
|
1259
|
+
conversationId: event.conversationId,
|
|
1260
|
+
seq: event.seq,
|
|
1261
|
+
kind: event.kind,
|
|
1262
|
+
occurredAt: event.occurredAt,
|
|
1263
|
+
payload: event.payload
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
return [
|
|
1267
|
+
defineAgentTool({
|
|
1268
|
+
name: "session_search",
|
|
1269
|
+
description: "Search authorized durable conversation events and return exact addresses.",
|
|
1270
|
+
identity: { serviceName: "agent-events", action: "search", method: "POST" },
|
|
1271
|
+
input: z5.object({
|
|
1272
|
+
query: z5.string().min(1),
|
|
1273
|
+
conversationId: z5.string().min(1).optional(),
|
|
1274
|
+
since: z5.iso.datetime({ offset: true }).optional(),
|
|
1275
|
+
limit: z5.int().positive().max(200).optional()
|
|
1276
|
+
}).strict(),
|
|
1277
|
+
output: z5.object({ results: z5.array(AgentEventSearchResultSchema) }).strict(),
|
|
1278
|
+
transports: ["AGENT"],
|
|
1279
|
+
handler: async ({ input: request }) => ({
|
|
1280
|
+
results: [
|
|
1281
|
+
...await input.search({
|
|
1282
|
+
requestingConversationId: input.conversationId,
|
|
1283
|
+
...request
|
|
1284
|
+
})
|
|
1285
|
+
]
|
|
1286
|
+
})
|
|
1287
|
+
}),
|
|
1288
|
+
defineAgentTool({
|
|
1289
|
+
name: "session_trace",
|
|
1290
|
+
description: "Read a bounded contiguous slice of this conversation event trace.",
|
|
1291
|
+
identity: { serviceName: "agent-events", action: "trace", method: "GET" },
|
|
1292
|
+
input: z5.object({
|
|
1293
|
+
fromSeq: z5.int().positive().optional(),
|
|
1294
|
+
toSeq: z5.int().positive().optional(),
|
|
1295
|
+
limit: z5.int().positive().max(1000).default(100)
|
|
1296
|
+
}).strict(),
|
|
1297
|
+
output: z5.object({ events: z5.array(EventOutputSchema), nextSeq: z5.number().optional() }),
|
|
1298
|
+
transports: ["AGENT"],
|
|
1299
|
+
handler: async ({ input: request }) => {
|
|
1300
|
+
const page = await input.store.readEvents({
|
|
1301
|
+
conversationId: input.conversationId,
|
|
1302
|
+
...request
|
|
1303
|
+
});
|
|
1304
|
+
return {
|
|
1305
|
+
events: page.items.map((event) => ({
|
|
1306
|
+
conversationId: event.conversationId,
|
|
1307
|
+
seq: event.seq,
|
|
1308
|
+
kind: event.kind,
|
|
1309
|
+
occurredAt: event.occurredAt,
|
|
1310
|
+
payload: event.payload
|
|
1311
|
+
})),
|
|
1312
|
+
...page.nextSeq !== undefined && { nextSeq: page.nextSeq }
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
}),
|
|
1316
|
+
defineAgentTool({
|
|
1317
|
+
name: "session_event",
|
|
1318
|
+
description: "Read one durable event by exact conversation and sequence address.",
|
|
1319
|
+
identity: { serviceName: "agent-events", action: "read", method: "GET" },
|
|
1320
|
+
input: EventAddressSchema,
|
|
1321
|
+
output: EventOutputSchema,
|
|
1322
|
+
transports: ["AGENT"],
|
|
1323
|
+
handler: ({ input: request }) => readExact(request)
|
|
1324
|
+
})
|
|
1325
|
+
];
|
|
1326
|
+
}
|
|
637
1327
|
// src/agent-runtime/observability.ts
|
|
638
|
-
import { z as
|
|
639
|
-
var AgentRunEventBaseSchema =
|
|
640
|
-
schemaVersion:
|
|
1328
|
+
import { z as z6 } from "zod";
|
|
1329
|
+
var AgentRunEventBaseSchema = z6.object({
|
|
1330
|
+
schemaVersion: z6.literal(1),
|
|
641
1331
|
eventId: AgentRecordIdSchema,
|
|
642
1332
|
conversationId: AgentRecordIdSchema,
|
|
643
1333
|
runId: AgentRecordIdSchema,
|
|
644
|
-
traceId:
|
|
645
|
-
spanId:
|
|
646
|
-
parentSpanId:
|
|
1334
|
+
traceId: z6.string().min(1),
|
|
1335
|
+
spanId: z6.string().min(1),
|
|
1336
|
+
parentSpanId: z6.string().min(1).optional(),
|
|
647
1337
|
state: AgentRunStateSchema,
|
|
648
|
-
modelId:
|
|
649
|
-
durationMs:
|
|
1338
|
+
modelId: z6.string().min(1).optional(),
|
|
1339
|
+
durationMs: z6.number().nonnegative().optional(),
|
|
650
1340
|
emittedAt: AgentTimestampSchema
|
|
651
1341
|
});
|
|
652
1342
|
var AgentRunStartedEventSchema = AgentRunEventBaseSchema.extend({
|
|
653
|
-
type:
|
|
654
|
-
queueWaitMs:
|
|
1343
|
+
type: z6.literal("run-started"),
|
|
1344
|
+
queueWaitMs: z6.number().nonnegative().optional()
|
|
655
1345
|
});
|
|
656
1346
|
var AgentStepFinishedEventSchema = AgentRunEventBaseSchema.extend({
|
|
657
|
-
type:
|
|
658
|
-
step:
|
|
659
|
-
ttftMs:
|
|
1347
|
+
type: z6.literal("step-finished"),
|
|
1348
|
+
step: z6.int().nonnegative(),
|
|
1349
|
+
ttftMs: z6.number().nonnegative().optional(),
|
|
660
1350
|
usage: AgentUsageSchema
|
|
661
1351
|
});
|
|
662
1352
|
var AgentRunTerminalEventSchema = AgentRunEventBaseSchema.extend({
|
|
663
|
-
type:
|
|
1353
|
+
type: z6.literal("run-terminal"),
|
|
664
1354
|
terminalReason: AgentTerminalReasonSchema,
|
|
665
|
-
ttftMs:
|
|
666
|
-
internalCause:
|
|
1355
|
+
ttftMs: z6.number().nonnegative().optional(),
|
|
1356
|
+
internalCause: z6.unknown().optional(),
|
|
667
1357
|
usage: AgentUsageSchema
|
|
668
1358
|
});
|
|
669
|
-
var AgentRunEventSchema =
|
|
1359
|
+
var AgentRunEventSchema = z6.discriminatedUnion("type", [
|
|
670
1360
|
AgentRunStartedEventSchema,
|
|
671
1361
|
AgentStepFinishedEventSchema,
|
|
672
1362
|
AgentRunTerminalEventSchema
|
|
@@ -714,6 +1404,247 @@ function createAgentObservability(config) {
|
|
|
714
1404
|
close: (bound) => manager.close(bound)
|
|
715
1405
|
};
|
|
716
1406
|
}
|
|
1407
|
+
// src/agent-runtime/projections.ts
|
|
1408
|
+
import { z as z7 } from "zod";
|
|
1409
|
+
function defineAgentProjection(definition) {
|
|
1410
|
+
if (definition.name.length === 0)
|
|
1411
|
+
throw new TypeError("Projection name must not be empty");
|
|
1412
|
+
if (!Number.isInteger(definition.version) || definition.version < 1) {
|
|
1413
|
+
throw new TypeError("Projection version must be a positive integer");
|
|
1414
|
+
}
|
|
1415
|
+
definition.schema.parse(definition.initial());
|
|
1416
|
+
return definition;
|
|
1417
|
+
}
|
|
1418
|
+
function createAgentProjectionRegistry(definitions) {
|
|
1419
|
+
const registry = new Map;
|
|
1420
|
+
for (const definition of definitions) {
|
|
1421
|
+
const previous = registry.get(definition.name);
|
|
1422
|
+
if (previous)
|
|
1423
|
+
throw new TypeError(`Duplicate agent projection: ${definition.name}`);
|
|
1424
|
+
registry.set(definition.name, definition);
|
|
1425
|
+
}
|
|
1426
|
+
return registry;
|
|
1427
|
+
}
|
|
1428
|
+
var ProjectionRowSchema = z7.object({
|
|
1429
|
+
conversation_id: z7.string().optional(),
|
|
1430
|
+
version: z7.int().positive(),
|
|
1431
|
+
upto_seq: z7.int().nonnegative(),
|
|
1432
|
+
payload: z7.string(),
|
|
1433
|
+
updated_at: z7.string()
|
|
1434
|
+
});
|
|
1435
|
+
function projectionPayload(value) {
|
|
1436
|
+
return JSON.parse(value);
|
|
1437
|
+
}
|
|
1438
|
+
function createSqliteAgentProjectionStore(input) {
|
|
1439
|
+
const database = input.sqlite.database;
|
|
1440
|
+
const store = input.sqlite.store;
|
|
1441
|
+
const load = (conversationId, definition) => {
|
|
1442
|
+
const raw = database.prepare(`
|
|
1443
|
+
SELECT version, upto_seq, payload, updated_at
|
|
1444
|
+
FROM stitchkit_agent_runtime_projections
|
|
1445
|
+
WHERE conversation_id = ? AND name = ?
|
|
1446
|
+
`).get(conversationId, definition.name);
|
|
1447
|
+
if (raw === null || raw === undefined)
|
|
1448
|
+
return;
|
|
1449
|
+
const row = ProjectionRowSchema.parse(raw);
|
|
1450
|
+
if (row.version !== definition.version)
|
|
1451
|
+
return;
|
|
1452
|
+
return {
|
|
1453
|
+
name: definition.name,
|
|
1454
|
+
version: row.version,
|
|
1455
|
+
uptoSeq: row.upto_seq,
|
|
1456
|
+
updatedAt: row.updated_at,
|
|
1457
|
+
value: definition.schema.parse(projectionPayload(row.payload))
|
|
1458
|
+
};
|
|
1459
|
+
};
|
|
1460
|
+
const advance = async (conversationId, definition) => {
|
|
1461
|
+
const current = load(conversationId, definition);
|
|
1462
|
+
let value = current?.value ?? definition.initial();
|
|
1463
|
+
let uptoSeq = current?.uptoSeq ?? 0;
|
|
1464
|
+
let cursor = uptoSeq + 1;
|
|
1465
|
+
let updatedAt = current?.updatedAt ?? new Date(0).toISOString();
|
|
1466
|
+
for (;; ) {
|
|
1467
|
+
const page = await store.readEvents({
|
|
1468
|
+
conversationId,
|
|
1469
|
+
fromSeq: cursor,
|
|
1470
|
+
limit: 1000
|
|
1471
|
+
});
|
|
1472
|
+
for (const event of page.items) {
|
|
1473
|
+
value = definition.schema.parse(definition.fold(value, event));
|
|
1474
|
+
uptoSeq = event.seq;
|
|
1475
|
+
updatedAt = event.occurredAt;
|
|
1476
|
+
}
|
|
1477
|
+
if (page.nextSeq === undefined)
|
|
1478
|
+
break;
|
|
1479
|
+
cursor = page.nextSeq;
|
|
1480
|
+
}
|
|
1481
|
+
await input.sqlite.transaction(async (scope) => {
|
|
1482
|
+
scope.database.prepare(`
|
|
1483
|
+
INSERT INTO stitchkit_agent_runtime_projections
|
|
1484
|
+
(conversation_id, name, version, upto_seq, payload, updated_at)
|
|
1485
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1486
|
+
ON CONFLICT (conversation_id, name) DO UPDATE SET
|
|
1487
|
+
version = excluded.version,
|
|
1488
|
+
upto_seq = excluded.upto_seq,
|
|
1489
|
+
payload = excluded.payload,
|
|
1490
|
+
updated_at = excluded.updated_at
|
|
1491
|
+
`).run(conversationId, definition.name, definition.version, uptoSeq, JSON.stringify(value), updatedAt);
|
|
1492
|
+
});
|
|
1493
|
+
return { name: definition.name, version: definition.version, uptoSeq, updatedAt, value };
|
|
1494
|
+
};
|
|
1495
|
+
const list = (definition) => database.prepare(`
|
|
1496
|
+
SELECT conversation_id, version, upto_seq, payload, updated_at
|
|
1497
|
+
FROM stitchkit_agent_runtime_projections
|
|
1498
|
+
WHERE name = ? AND version = ? ORDER BY updated_at DESC, conversation_id
|
|
1499
|
+
`).all(definition.name, definition.version).map((raw) => {
|
|
1500
|
+
const row = ProjectionRowSchema.parse(raw);
|
|
1501
|
+
if (!row.conversation_id)
|
|
1502
|
+
throw new TypeError("Projection list row has no conversation id");
|
|
1503
|
+
return {
|
|
1504
|
+
conversationId: row.conversation_id,
|
|
1505
|
+
projection: {
|
|
1506
|
+
name: definition.name,
|
|
1507
|
+
version: row.version,
|
|
1508
|
+
uptoSeq: row.upto_seq,
|
|
1509
|
+
updatedAt: row.updated_at,
|
|
1510
|
+
value: definition.schema.parse(projectionPayload(row.payload))
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
});
|
|
1514
|
+
return { load, advance, list };
|
|
1515
|
+
}
|
|
1516
|
+
var ConversationCardSchema = z7.object({
|
|
1517
|
+
eventCount: z7.int().nonnegative(),
|
|
1518
|
+
lastKind: z7.string().optional(),
|
|
1519
|
+
lastOccurredAt: z7.string().optional()
|
|
1520
|
+
});
|
|
1521
|
+
var agentConversationCardProjection = defineAgentProjection({
|
|
1522
|
+
name: "conversation-card",
|
|
1523
|
+
version: 1,
|
|
1524
|
+
schema: ConversationCardSchema,
|
|
1525
|
+
initial: () => ({ eventCount: 0 }),
|
|
1526
|
+
fold: (state, event) => ({
|
|
1527
|
+
eventCount: state.eventCount + 1,
|
|
1528
|
+
lastKind: event.kind,
|
|
1529
|
+
lastOccurredAt: event.occurredAt
|
|
1530
|
+
})
|
|
1531
|
+
});
|
|
1532
|
+
var StateSlotsProjectionSchema = z7.record(z7.string(), z7.json());
|
|
1533
|
+
var agentStateSlotsProjection = defineAgentProjection({
|
|
1534
|
+
name: "state-slots",
|
|
1535
|
+
version: 1,
|
|
1536
|
+
schema: StateSlotsProjectionSchema,
|
|
1537
|
+
initial: () => ({}),
|
|
1538
|
+
fold: (state, event) => {
|
|
1539
|
+
if (event.kind !== "state/set")
|
|
1540
|
+
return state;
|
|
1541
|
+
const payload = z7.object({ name: z7.string().min(1), value: z7.json() }).passthrough().parse(event.payload);
|
|
1542
|
+
return { ...state, [payload.name]: payload.value };
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
var ScheduleSummarySchema = z7.object({
|
|
1546
|
+
scheduled: z7.int().nonnegative(),
|
|
1547
|
+
fired: z7.int().nonnegative(),
|
|
1548
|
+
cancelled: z7.int().nonnegative(),
|
|
1549
|
+
late: z7.int().nonnegative()
|
|
1550
|
+
});
|
|
1551
|
+
var agentScheduleSummaryProjection = defineAgentProjection({
|
|
1552
|
+
name: "schedule-summary",
|
|
1553
|
+
version: 1,
|
|
1554
|
+
schema: ScheduleSummarySchema,
|
|
1555
|
+
initial: () => ({ scheduled: 0, fired: 0, cancelled: 0, late: 0 }),
|
|
1556
|
+
fold: (state, event) => {
|
|
1557
|
+
if (event.kind === "schedule/set")
|
|
1558
|
+
return { ...state, scheduled: state.scheduled + 1 };
|
|
1559
|
+
if (event.kind === "schedule/fired")
|
|
1560
|
+
return { ...state, fired: state.fired + 1 };
|
|
1561
|
+
if (event.kind === "schedule/cancelled")
|
|
1562
|
+
return { ...state, cancelled: state.cancelled + 1 };
|
|
1563
|
+
if (event.kind === "schedule/late")
|
|
1564
|
+
return { ...state, late: state.late + 1 };
|
|
1565
|
+
return state;
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
var RuntimeSummarySchema = z7.object({
|
|
1569
|
+
eventCount: z7.int().nonnegative(),
|
|
1570
|
+
lastSeq: z7.int().nonnegative(),
|
|
1571
|
+
lastKind: z7.string().optional(),
|
|
1572
|
+
lastOccurredAt: z7.string().optional(),
|
|
1573
|
+
lastModelId: z7.string().optional()
|
|
1574
|
+
}).strict();
|
|
1575
|
+
var agentSummaryProjection = defineAgentProjection({
|
|
1576
|
+
name: "summary",
|
|
1577
|
+
version: 1,
|
|
1578
|
+
schema: RuntimeSummarySchema,
|
|
1579
|
+
initial: () => ({ eventCount: 0, lastSeq: 0 }),
|
|
1580
|
+
fold: (state, event) => {
|
|
1581
|
+
const request = event.kind === "provider/request" ? z7.object({ modelId: z7.string().optional() }).passthrough().safeParse(event.payload) : undefined;
|
|
1582
|
+
return {
|
|
1583
|
+
eventCount: state.eventCount + 1,
|
|
1584
|
+
lastSeq: event.seq,
|
|
1585
|
+
lastKind: event.kind,
|
|
1586
|
+
lastOccurredAt: event.occurredAt,
|
|
1587
|
+
...request?.success && request.data.modelId ? { lastModelId: request.data.modelId } : state.lastModelId ? { lastModelId: state.lastModelId } : {}
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
var RuntimeUsageSchema = z7.object({
|
|
1592
|
+
inputTokens: z7.number().nonnegative(),
|
|
1593
|
+
outputTokens: z7.number().nonnegative(),
|
|
1594
|
+
cost: z7.number().nonnegative(),
|
|
1595
|
+
sources: z7.array(z7.string())
|
|
1596
|
+
}).strict();
|
|
1597
|
+
var agentUsageProjection = defineAgentProjection({
|
|
1598
|
+
name: "usage",
|
|
1599
|
+
version: 1,
|
|
1600
|
+
schema: RuntimeUsageSchema,
|
|
1601
|
+
initial: () => ({ inputTokens: 0, outputTokens: 0, cost: 0, sources: [] }),
|
|
1602
|
+
fold: (state, event) => {
|
|
1603
|
+
const parsed = z7.object({
|
|
1604
|
+
inputTokens: z7.number().nonnegative().optional(),
|
|
1605
|
+
outputTokens: z7.number().nonnegative().optional(),
|
|
1606
|
+
cost: z7.number().nonnegative().optional(),
|
|
1607
|
+
source: z7.string().min(1).optional()
|
|
1608
|
+
}).passthrough().safeParse(event.payload);
|
|
1609
|
+
if (!parsed.success)
|
|
1610
|
+
return state;
|
|
1611
|
+
const source = parsed.data.source;
|
|
1612
|
+
return {
|
|
1613
|
+
inputTokens: state.inputTokens + (parsed.data.inputTokens ?? 0),
|
|
1614
|
+
outputTokens: state.outputTokens + (parsed.data.outputTokens ?? 0),
|
|
1615
|
+
cost: state.cost + (parsed.data.cost ?? 0),
|
|
1616
|
+
sources: source && !state.sources.includes(source) ? [...state.sources, source] : state.sources
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
var RuntimeOutlineSchema = z7.object({
|
|
1621
|
+
requests: z7.array(z7.object({
|
|
1622
|
+
seq: z7.int().positive(),
|
|
1623
|
+
occurredAt: z7.string(),
|
|
1624
|
+
runId: z7.string().optional()
|
|
1625
|
+
}).strict())
|
|
1626
|
+
}).strict();
|
|
1627
|
+
var agentOutlineProjection = defineAgentProjection({
|
|
1628
|
+
name: "outline",
|
|
1629
|
+
version: 1,
|
|
1630
|
+
schema: RuntimeOutlineSchema,
|
|
1631
|
+
initial: () => ({ requests: [] }),
|
|
1632
|
+
fold: (state, event) => {
|
|
1633
|
+
if (event.kind !== "provider/request")
|
|
1634
|
+
return state;
|
|
1635
|
+
const payload = z7.object({ runId: z7.string().optional() }).passthrough().parse(event.payload);
|
|
1636
|
+
return {
|
|
1637
|
+
requests: [
|
|
1638
|
+
...state.requests,
|
|
1639
|
+
{
|
|
1640
|
+
seq: event.seq,
|
|
1641
|
+
occurredAt: event.occurredAt,
|
|
1642
|
+
...payload.runId && { runId: payload.runId }
|
|
1643
|
+
}
|
|
1644
|
+
]
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
717
1648
|
// src/agent-runtime/protocol.ts
|
|
718
1649
|
function hasAgentTerminalOutput(input) {
|
|
719
1650
|
for (const part of input.message.parts) {
|
|
@@ -741,84 +1672,543 @@ function defineAgentProtocol(config) {
|
|
|
741
1672
|
}
|
|
742
1673
|
};
|
|
743
1674
|
}
|
|
744
|
-
// src/agent-runtime/
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
[
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1675
|
+
// src/agent-runtime/schedule-tools.ts
|
|
1676
|
+
import { z as z8 } from "zod";
|
|
1677
|
+
var ScheduledOutputSchema = z8.object({ scheduleId: z8.string(), nextAt: z8.string() }).strict();
|
|
1678
|
+
var ConversationInputSchema = z8.object({ conversationId: z8.string().min(1) }).strict();
|
|
1679
|
+
function createAgentScheduleTools(service) {
|
|
1680
|
+
const schedule = async (request) => {
|
|
1681
|
+
const result = await service.scheduleInput(request);
|
|
1682
|
+
return { scheduleId: result.id, nextAt: result.nextAt };
|
|
1683
|
+
};
|
|
1684
|
+
return [
|
|
1685
|
+
defineAgentTool({
|
|
1686
|
+
name: "schedule_after",
|
|
1687
|
+
description: "Schedule durable input after a relative delay.",
|
|
1688
|
+
identity: { serviceName: "agent-schedules", action: "after", method: "POST" },
|
|
1689
|
+
input: z8.object({
|
|
1690
|
+
conversationId: z8.string().min(1),
|
|
1691
|
+
input: z8.json(),
|
|
1692
|
+
afterMs: z8.int().positive()
|
|
1693
|
+
}).strict(),
|
|
1694
|
+
output: ScheduledOutputSchema,
|
|
1695
|
+
transports: ["AGENT"],
|
|
1696
|
+
handler: ({ input }) => schedule(input)
|
|
1697
|
+
}),
|
|
1698
|
+
defineAgentTool({
|
|
1699
|
+
name: "schedule_at",
|
|
1700
|
+
description: "Schedule durable input at an absolute timestamp with an explicit offset.",
|
|
1701
|
+
identity: { serviceName: "agent-schedules", action: "at", method: "POST" },
|
|
1702
|
+
input: z8.object({ conversationId: z8.string().min(1), input: z8.json(), at: z8.string().min(1) }).strict(),
|
|
1703
|
+
output: ScheduledOutputSchema,
|
|
1704
|
+
transports: ["AGENT"],
|
|
1705
|
+
handler: ({ input }) => schedule(input)
|
|
1706
|
+
}),
|
|
1707
|
+
defineAgentTool({
|
|
1708
|
+
name: "schedule_every",
|
|
1709
|
+
description: "Schedule recurring durable input in an explicit conversation timezone.",
|
|
1710
|
+
identity: { serviceName: "agent-schedules", action: "every", method: "POST" },
|
|
1711
|
+
input: z8.object({
|
|
1712
|
+
conversationId: z8.string().min(1),
|
|
1713
|
+
input: z8.json(),
|
|
1714
|
+
everyMs: z8.int().positive(),
|
|
1715
|
+
timeZone: z8.string().min(1)
|
|
1716
|
+
}).strict(),
|
|
1717
|
+
output: ScheduledOutputSchema,
|
|
1718
|
+
transports: ["AGENT"],
|
|
1719
|
+
handler: ({ input }) => schedule(input)
|
|
1720
|
+
}),
|
|
1721
|
+
defineAgentTool({
|
|
1722
|
+
name: "schedule_list",
|
|
1723
|
+
description: "List durable schedules for one conversation.",
|
|
1724
|
+
identity: { serviceName: "agent-schedules", action: "list", method: "GET" },
|
|
1725
|
+
input: ConversationInputSchema,
|
|
1726
|
+
output: z8.object({ schedules: z8.array(z8.json()) }).strict(),
|
|
1727
|
+
transports: ["AGENT"],
|
|
1728
|
+
handler: ({ input }) => ({
|
|
1729
|
+
schedules: [...service.listSchedules(input.conversationId)]
|
|
1730
|
+
})
|
|
1731
|
+
}),
|
|
1732
|
+
defineAgentTool({
|
|
1733
|
+
name: "schedule_cancel",
|
|
1734
|
+
description: "Cancel one durable deferred-input schedule.",
|
|
1735
|
+
identity: { serviceName: "agent-schedules", action: "cancel", method: "POST" },
|
|
1736
|
+
input: z8.object({ conversationId: z8.string().min(1), scheduleId: z8.string().min(1) }).strict(),
|
|
1737
|
+
output: z8.object({ cancelled: z8.boolean() }).strict(),
|
|
1738
|
+
transports: ["AGENT"],
|
|
1739
|
+
handler: async ({ input }) => ({
|
|
1740
|
+
cancelled: await service.cancelSchedule(input.conversationId, input.scheduleId)
|
|
1741
|
+
})
|
|
1742
|
+
})
|
|
1743
|
+
];
|
|
1744
|
+
}
|
|
1745
|
+
// src/agent-runtime/schedules.ts
|
|
1746
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1747
|
+
import { z as z9 } from "zod";
|
|
1748
|
+
var AgentScheduleSchema = z9.object({
|
|
1749
|
+
id: z9.string().min(1),
|
|
1750
|
+
conversationId: z9.string().min(1),
|
|
1751
|
+
kind: z9.enum(["at", "after", "every"]),
|
|
1752
|
+
nextAt: z9.iso.datetime({ offset: true }),
|
|
1753
|
+
intervalMs: z9.int().positive().optional(),
|
|
1754
|
+
timeZone: z9.string().min(1).optional(),
|
|
1755
|
+
input: z9.json(),
|
|
1756
|
+
state: z9.enum(["scheduled", "cancelled", "completed"]),
|
|
1757
|
+
occurrence: z9.int().nonnegative(),
|
|
1758
|
+
createdAt: z9.iso.datetime({ offset: true }),
|
|
1759
|
+
updatedAt: z9.iso.datetime({ offset: true })
|
|
1760
|
+
}).strict();
|
|
1761
|
+
var ScheduleRowSchema = z9.object({
|
|
1762
|
+
id: z9.string(),
|
|
1763
|
+
conversation_id: z9.string(),
|
|
1764
|
+
kind: z9.enum(["at", "after", "every"]),
|
|
1765
|
+
next_at: z9.string(),
|
|
1766
|
+
interval_ms: z9.number().int().positive().nullable(),
|
|
1767
|
+
time_zone: z9.string().nullable(),
|
|
1768
|
+
input_payload: z9.string(),
|
|
1769
|
+
state: z9.enum(["scheduled", "cancelled", "completed"]),
|
|
1770
|
+
occurrence: z9.int().nonnegative(),
|
|
1771
|
+
created_at: z9.string(),
|
|
1772
|
+
updated_at: z9.string()
|
|
1773
|
+
});
|
|
1774
|
+
function parseSchedule(raw) {
|
|
1775
|
+
const row = ScheduleRowSchema.parse(raw);
|
|
1776
|
+
return AgentScheduleSchema.parse({
|
|
1777
|
+
id: row.id,
|
|
1778
|
+
conversationId: row.conversation_id,
|
|
1779
|
+
kind: row.kind,
|
|
1780
|
+
nextAt: row.next_at,
|
|
1781
|
+
...row.interval_ms !== null && { intervalMs: row.interval_ms },
|
|
1782
|
+
...row.time_zone !== null && { timeZone: row.time_zone },
|
|
1783
|
+
input: JSON.parse(row.input_payload),
|
|
1784
|
+
state: row.state,
|
|
1785
|
+
occurrence: row.occurrence,
|
|
1786
|
+
createdAt: row.created_at,
|
|
1787
|
+
updatedAt: row.updated_at
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
function createAgentScheduleService(input) {
|
|
1791
|
+
const onError = input.onError ?? ((error) => {
|
|
1792
|
+
console.error("[stitchkit] agent schedule tick failed", error);
|
|
1793
|
+
});
|
|
1794
|
+
const now = input.now ?? (() => new Date);
|
|
1795
|
+
const database = input.sqlite.database;
|
|
1796
|
+
const setTimer = input.setTimer ?? ((callback, delay) => setTimeout(callback, delay));
|
|
1797
|
+
const clearTimer = input.clearTimer ?? clearTimeout;
|
|
1798
|
+
let timer;
|
|
1799
|
+
let closed = false;
|
|
1800
|
+
const listSchedules = (conversationId) => database.prepare(`
|
|
1801
|
+
SELECT id, conversation_id, kind, next_at, interval_ms, time_zone,
|
|
1802
|
+
input_payload, state, occurrence, created_at, updated_at
|
|
1803
|
+
FROM stitchkit_agent_runtime_schedules
|
|
1804
|
+
WHERE conversation_id = ? ORDER BY created_at, id
|
|
1805
|
+
`).all(conversationId).map(parseSchedule);
|
|
1806
|
+
const arm = () => {
|
|
1807
|
+
if (timer)
|
|
1808
|
+
clearTimer(timer);
|
|
1809
|
+
if (closed)
|
|
1810
|
+
return;
|
|
1811
|
+
try {
|
|
1812
|
+
armNext();
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
onError(error);
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
const armNext = () => {
|
|
1818
|
+
const raw = database.prepare(`
|
|
1819
|
+
SELECT MAX(next_at, COALESCE(claim_until, next_at)) AS due_at
|
|
1820
|
+
FROM stitchkit_agent_runtime_schedules
|
|
1821
|
+
WHERE state = 'scheduled' ORDER BY due_at, id LIMIT 1
|
|
1822
|
+
`).get();
|
|
1823
|
+
if (raw === null || raw === undefined || typeof raw.due_at !== "string")
|
|
1824
|
+
return;
|
|
1825
|
+
const delay = Math.max(0, new Date(raw.due_at).getTime() - now().getTime());
|
|
1826
|
+
timer = setTimer(() => void tick(), Math.min(delay, 2147483647));
|
|
1827
|
+
};
|
|
1828
|
+
const owner = randomUUID2();
|
|
1829
|
+
const CLAIM_MS = 60000;
|
|
1830
|
+
let ticking;
|
|
1831
|
+
const claim = (id, until, at) => input.sqlite.transaction(async (scope) => scope.database.prepare(`
|
|
1832
|
+
UPDATE stitchkit_agent_runtime_schedules
|
|
1833
|
+
SET claim_owner = ?, claim_until = ?, updated_at = ?
|
|
1834
|
+
WHERE id = ? AND state = 'scheduled' AND (claim_until IS NULL OR claim_until < ?)
|
|
1835
|
+
`).run(owner, until, at, id, at).changes === 1);
|
|
1836
|
+
const runTick = async () => {
|
|
1837
|
+
if (closed)
|
|
1838
|
+
return;
|
|
1839
|
+
const observedAt = now();
|
|
1840
|
+
const due = database.prepare(`
|
|
1841
|
+
SELECT id, conversation_id, kind, next_at, interval_ms, time_zone,
|
|
1842
|
+
input_payload, state, occurrence, created_at, updated_at
|
|
1843
|
+
FROM stitchkit_agent_runtime_schedules
|
|
1844
|
+
WHERE state = 'scheduled' AND next_at <= ? ORDER BY next_at, id
|
|
1845
|
+
`).all(observedAt.toISOString()).map(parseSchedule);
|
|
1846
|
+
for (const schedule of due) {
|
|
1847
|
+
const at = observedAt.toISOString();
|
|
1848
|
+
if (!await claim(schedule.id, new Date(observedAt.getTime() + CLAIM_MS).toISOString(), at)) {
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
const occurrence = schedule.occurrence + 1;
|
|
1852
|
+
const lateByMs = Math.max(0, observedAt.getTime() - new Date(schedule.nextAt).getTime());
|
|
1853
|
+
try {
|
|
1854
|
+
await input.dispatch({
|
|
1855
|
+
conversationId: schedule.conversationId,
|
|
1856
|
+
idempotencyKey: `schedule:${schedule.id}:${occurrence}`,
|
|
1857
|
+
input: schedule.input,
|
|
1858
|
+
schedule: { id: schedule.id, occurrence, lateByMs }
|
|
1859
|
+
});
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
await input.sqlite.transaction(async (scope) => {
|
|
1862
|
+
scope.database.prepare(`
|
|
1863
|
+
UPDATE stitchkit_agent_runtime_schedules
|
|
1864
|
+
SET claim_owner = NULL, claim_until = NULL, updated_at = ?
|
|
1865
|
+
WHERE id = ? AND claim_owner = ?
|
|
1866
|
+
`).run(at, schedule.id, owner);
|
|
1867
|
+
await scope.appendEvent({
|
|
1868
|
+
conversationId: schedule.conversationId,
|
|
1869
|
+
kind: "schedule/failed",
|
|
1870
|
+
occurredAt: at,
|
|
1871
|
+
payload: {
|
|
1872
|
+
id: schedule.id,
|
|
1873
|
+
occurrence,
|
|
1874
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
});
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
let nextAt = schedule.nextAt;
|
|
1881
|
+
if (schedule.kind === "every" && schedule.intervalMs) {
|
|
1882
|
+
let next = new Date(schedule.nextAt).getTime() + schedule.intervalMs;
|
|
1883
|
+
while (next <= observedAt.getTime())
|
|
1884
|
+
next += schedule.intervalMs;
|
|
1885
|
+
nextAt = new Date(next).toISOString();
|
|
1886
|
+
}
|
|
1887
|
+
const state = schedule.kind === "every" ? "scheduled" : "completed";
|
|
1888
|
+
await input.sqlite.transaction(async (scope) => {
|
|
1889
|
+
const settled = scope.database.prepare(`
|
|
1890
|
+
UPDATE stitchkit_agent_runtime_schedules
|
|
1891
|
+
SET state = ?, occurrence = ?, next_at = ?, updated_at = ?,
|
|
1892
|
+
claim_owner = NULL, claim_until = NULL
|
|
1893
|
+
WHERE id = ? AND state = 'scheduled' AND claim_owner = ?
|
|
1894
|
+
`).run(state, occurrence, nextAt, at, schedule.id, owner).changes === 1;
|
|
1895
|
+
if (lateByMs > 0) {
|
|
1896
|
+
await scope.appendEvent({
|
|
1897
|
+
conversationId: schedule.conversationId,
|
|
1898
|
+
kind: "schedule/late",
|
|
1899
|
+
occurredAt: at,
|
|
1900
|
+
payload: { id: schedule.id, occurrence, lateByMs }
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
await scope.appendEvent({
|
|
1904
|
+
conversationId: schedule.conversationId,
|
|
1905
|
+
kind: "schedule/fired",
|
|
1906
|
+
occurredAt: at,
|
|
1907
|
+
payload: {
|
|
1908
|
+
id: schedule.id,
|
|
1909
|
+
occurrence,
|
|
1910
|
+
lateByMs,
|
|
1911
|
+
nextAt,
|
|
1912
|
+
state: settled ? state : "cancelled"
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
const tick = () => {
|
|
1919
|
+
if (ticking)
|
|
1920
|
+
return ticking;
|
|
1921
|
+
ticking = runTick().catch(onError).finally(() => {
|
|
1922
|
+
ticking = undefined;
|
|
1923
|
+
arm();
|
|
1924
|
+
});
|
|
1925
|
+
return ticking;
|
|
1926
|
+
};
|
|
1927
|
+
const scheduleInput = async (request) => {
|
|
1928
|
+
const modes = [
|
|
1929
|
+
request.at !== undefined,
|
|
1930
|
+
request.afterMs !== undefined,
|
|
1931
|
+
request.everyMs !== undefined
|
|
1932
|
+
];
|
|
1933
|
+
if (modes.filter(Boolean).length !== 1)
|
|
1934
|
+
throw new TypeError("Declare exactly one schedule mode");
|
|
1935
|
+
if (request.everyMs !== undefined && !request.timeZone) {
|
|
1936
|
+
throw new TypeError("Repeating schedules require an explicit timeZone");
|
|
1937
|
+
}
|
|
1938
|
+
if (request.timeZone) {
|
|
1939
|
+
try {
|
|
1940
|
+
new Intl.DateTimeFormat("en", { timeZone: request.timeZone }).format(new Date(0));
|
|
1941
|
+
} catch {
|
|
1942
|
+
throw new TypeError(`Unknown schedule timeZone: ${request.timeZone}`);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
const observedAt = now();
|
|
1946
|
+
const kind = request.at !== undefined ? "at" : request.afterMs !== undefined ? "after" : "every";
|
|
1947
|
+
const intervalMs = request.everyMs;
|
|
1948
|
+
const delay = request.afterMs ?? request.everyMs;
|
|
1949
|
+
if (delay !== undefined && (!Number.isSafeInteger(delay) || delay < 1)) {
|
|
1950
|
+
throw new TypeError("Schedule delay must be a positive safe integer");
|
|
1951
|
+
}
|
|
1952
|
+
const nextAt = request.at ? new Date(z9.iso.datetime({ offset: true }).parse(request.at)).toISOString() : new Date(observedAt.getTime() + (delay ?? 0)).toISOString();
|
|
1953
|
+
const schedule = AgentScheduleSchema.parse({
|
|
1954
|
+
id: randomUUID2(),
|
|
1955
|
+
conversationId: request.conversationId,
|
|
1956
|
+
kind,
|
|
1957
|
+
nextAt,
|
|
1958
|
+
...intervalMs !== undefined && { intervalMs },
|
|
1959
|
+
...request.timeZone && { timeZone: request.timeZone },
|
|
1960
|
+
input: z9.json().parse(request.input),
|
|
1961
|
+
state: "scheduled",
|
|
1962
|
+
occurrence: 0,
|
|
1963
|
+
createdAt: observedAt.toISOString(),
|
|
1964
|
+
updatedAt: observedAt.toISOString()
|
|
1965
|
+
});
|
|
1966
|
+
await input.sqlite.transaction(async (scope) => {
|
|
1967
|
+
scope.database.prepare(`
|
|
1968
|
+
INSERT INTO stitchkit_agent_runtime_schedules (
|
|
1969
|
+
id, conversation_id, kind, next_at, interval_ms, time_zone,
|
|
1970
|
+
input_payload, state, occurrence, created_at, updated_at
|
|
1971
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'scheduled', 0, ?, ?)
|
|
1972
|
+
`).run(schedule.id, schedule.conversationId, schedule.kind, schedule.nextAt, schedule.intervalMs ?? null, schedule.timeZone ?? null, JSON.stringify(schedule.input), schedule.createdAt, schedule.updatedAt);
|
|
1973
|
+
await scope.appendEvent({
|
|
1974
|
+
conversationId: schedule.conversationId,
|
|
1975
|
+
kind: "schedule/set",
|
|
1976
|
+
occurredAt: schedule.createdAt,
|
|
1977
|
+
payload: { id: schedule.id, kind: schedule.kind, nextAt: schedule.nextAt }
|
|
1978
|
+
});
|
|
1979
|
+
});
|
|
1980
|
+
arm();
|
|
1981
|
+
return schedule;
|
|
1982
|
+
};
|
|
1983
|
+
const cancelSchedule = async (conversationId, id) => {
|
|
1984
|
+
const observedAt = now().toISOString();
|
|
1985
|
+
const changed = await input.sqlite.transaction(async (scope) => {
|
|
1986
|
+
const count = scope.database.prepare(`
|
|
1987
|
+
UPDATE stitchkit_agent_runtime_schedules SET state = 'cancelled', updated_at = ?
|
|
1988
|
+
WHERE id = ? AND conversation_id = ? AND state = 'scheduled'
|
|
1989
|
+
`).run(observedAt, id, conversationId).changes;
|
|
1990
|
+
if (count > 0) {
|
|
1991
|
+
await scope.appendEvent({
|
|
1992
|
+
conversationId,
|
|
1993
|
+
kind: "schedule/cancelled",
|
|
1994
|
+
occurredAt: observedAt,
|
|
1995
|
+
payload: { id }
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
return count;
|
|
1999
|
+
});
|
|
2000
|
+
if (changed > 0)
|
|
2001
|
+
arm();
|
|
2002
|
+
return changed > 0;
|
|
2003
|
+
};
|
|
2004
|
+
return {
|
|
2005
|
+
scheduleInput,
|
|
2006
|
+
cancelSchedule,
|
|
2007
|
+
listSchedules,
|
|
2008
|
+
tick,
|
|
2009
|
+
start: arm,
|
|
2010
|
+
close() {
|
|
2011
|
+
closed = true;
|
|
2012
|
+
if (timer)
|
|
2013
|
+
clearTimer(timer);
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
// src/agent-runtime/spill.ts
|
|
2018
|
+
import { createHash, randomUUID as randomUUID3 } from "node:crypto";
|
|
2019
|
+
import { z as z10 } from "zod";
|
|
2020
|
+
var SpillRowSchema = z10.object({
|
|
2021
|
+
payload: z10.instanceof(Uint8Array),
|
|
2022
|
+
bytes: z10.int().nonnegative(),
|
|
2023
|
+
authorization_payload: z10.string().nullable()
|
|
2024
|
+
});
|
|
2025
|
+
var SpillMetadataRowSchema = z10.object({
|
|
2026
|
+
reference_id: z10.string(),
|
|
2027
|
+
bytes: z10.int().nonnegative(),
|
|
2028
|
+
created_at: z10.string()
|
|
2029
|
+
});
|
|
2030
|
+
function decodeAuthorization(value) {
|
|
2031
|
+
if (value === null)
|
|
770
2032
|
return;
|
|
771
|
-
|
|
772
|
-
const candidate = value[key];
|
|
773
|
-
if (typeof candidate === "number" && candidate >= 400 && candidate < 600)
|
|
774
|
-
return candidate;
|
|
775
|
-
}
|
|
776
|
-
const nested = value.cause ?? value.response ?? value.error;
|
|
777
|
-
return nested === value ? undefined : statusOf(nested);
|
|
2033
|
+
return JSON.parse(value);
|
|
778
2034
|
}
|
|
779
|
-
function
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
|
|
2035
|
+
function createSqliteAgentSpillStore(input) {
|
|
2036
|
+
const now = input.now ?? (() => new Date);
|
|
2037
|
+
const database = input.sqlite.database;
|
|
2038
|
+
const authorization = (reference) => {
|
|
2039
|
+
const raw = database.prepare(`
|
|
2040
|
+
SELECT payload, bytes, authorization_payload
|
|
2041
|
+
FROM stitchkit_agent_runtime_spills
|
|
2042
|
+
WHERE reference_id = ? AND conversation_id = ?
|
|
2043
|
+
`).get(reference, input.conversationId);
|
|
2044
|
+
if (raw === null || raw === undefined)
|
|
2045
|
+
throw new TypeError("Unknown spill reference");
|
|
2046
|
+
return decodeAuthorization(SpillRowSchema.parse(raw).authorization_payload);
|
|
2047
|
+
};
|
|
2048
|
+
const write = async (request) => {
|
|
2049
|
+
const reference = randomUUID3();
|
|
2050
|
+
const createdAt = now();
|
|
2051
|
+
const expiresAt = input.retentionMs ? new Date(createdAt.getTime() + input.retentionMs).toISOString() : null;
|
|
2052
|
+
const sha256 = createHash("sha256").update(request.data).digest("hex");
|
|
2053
|
+
await input.sqlite.transaction(async (scope) => {
|
|
2054
|
+
scope.database.prepare(`
|
|
2055
|
+
INSERT INTO stitchkit_agent_runtime_spills (
|
|
2056
|
+
reference_id, conversation_id, run_id, tool_call_id, media_type,
|
|
2057
|
+
bytes, sha256, created_at, expires_at, payload, authorization_payload
|
|
2058
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2059
|
+
`).run(reference, input.conversationId, input.runId ?? null, input.toolCallId ?? null, request.mediaType, request.data.byteLength, sha256, createdAt.toISOString(), expiresAt, request.data, request.authorization ? JSON.stringify(request.authorization) : null);
|
|
2060
|
+
await scope.appendEvent({
|
|
2061
|
+
conversationId: input.conversationId,
|
|
2062
|
+
kind: "spill/created",
|
|
2063
|
+
occurredAt: createdAt.toISOString(),
|
|
2064
|
+
payload: { reference, bytes: request.data.byteLength, sha256 }
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
return { reference };
|
|
2068
|
+
};
|
|
2069
|
+
const read = async (request) => {
|
|
2070
|
+
const raw = database.prepare(`
|
|
2071
|
+
SELECT payload, bytes, authorization_payload
|
|
2072
|
+
FROM stitchkit_agent_runtime_spills
|
|
2073
|
+
WHERE reference_id = ? AND conversation_id = ?
|
|
2074
|
+
`).get(request.reference, input.conversationId);
|
|
2075
|
+
if (raw === null || raw === undefined)
|
|
2076
|
+
throw new TypeError("Unknown spill reference");
|
|
2077
|
+
const row = SpillRowSchema.parse(raw);
|
|
783
2078
|
return {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
2079
|
+
data: row.payload.slice(request.offset, request.offset + request.maxBytes),
|
|
2080
|
+
totalBytes: row.bytes,
|
|
2081
|
+
...decodeAuthorization(row.authorization_payload) && {
|
|
2082
|
+
authorization: decodeAuthorization(row.authorization_payload)
|
|
2083
|
+
}
|
|
788
2084
|
};
|
|
789
|
-
}
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
2085
|
+
};
|
|
2086
|
+
const search = (request) => {
|
|
2087
|
+
const raw = database.prepare(`
|
|
2088
|
+
SELECT payload, bytes, authorization_payload
|
|
2089
|
+
FROM stitchkit_agent_runtime_spills
|
|
2090
|
+
WHERE reference_id = ? AND conversation_id = ?
|
|
2091
|
+
`).get(request.reference, input.conversationId);
|
|
2092
|
+
if (raw === null || raw === undefined)
|
|
2093
|
+
throw new TypeError("Unknown spill reference");
|
|
2094
|
+
const row = SpillRowSchema.parse(raw);
|
|
2095
|
+
const matches = [];
|
|
2096
|
+
new TextDecoder("utf-8", { fatal: true }).decode(row.payload).split(`
|
|
2097
|
+
`).forEach((text, index) => {
|
|
2098
|
+
if (matches.length < request.maxMatches && text.includes(request.query)) {
|
|
2099
|
+
matches.push({ line: index + 1, text });
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
return matches;
|
|
2103
|
+
};
|
|
2104
|
+
const cleanup = async () => {
|
|
2105
|
+
const expired = database.prepare(`
|
|
2106
|
+
SELECT reference_id, bytes, created_at
|
|
2107
|
+
FROM stitchkit_agent_runtime_spills
|
|
2108
|
+
WHERE conversation_id = ? AND expires_at IS NOT NULL AND expires_at <= ?
|
|
2109
|
+
ORDER BY created_at, reference_id
|
|
2110
|
+
`).all(input.conversationId, now().toISOString()).map((row) => SpillMetadataRowSchema.parse(row));
|
|
2111
|
+
let bytes = 0;
|
|
2112
|
+
for (const spill of expired) {
|
|
2113
|
+
await input.sqlite.transaction(async (scope) => {
|
|
2114
|
+
scope.database.prepare("DELETE FROM stitchkit_agent_runtime_spills WHERE reference_id = ?").run(spill.reference_id);
|
|
2115
|
+
await scope.appendEvent({
|
|
2116
|
+
conversationId: input.conversationId,
|
|
2117
|
+
kind: "spill/deleted",
|
|
2118
|
+
payload: { reference: spill.reference_id, bytes: spill.bytes }
|
|
2119
|
+
});
|
|
2120
|
+
});
|
|
2121
|
+
bytes += spill.bytes;
|
|
799
2122
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
2123
|
+
return { deleted: expired.length, bytes };
|
|
2124
|
+
};
|
|
2125
|
+
return { authorization, write, read, search, cleanup };
|
|
2126
|
+
}
|
|
2127
|
+
// src/agent-runtime/state-tools.ts
|
|
2128
|
+
import { z as z11 } from "zod";
|
|
2129
|
+
var GoalSchema = z11.object({
|
|
2130
|
+
objective: z11.string().min(1),
|
|
2131
|
+
status: z11.enum(["active", "complete", "blocked"])
|
|
2132
|
+
}).strict();
|
|
2133
|
+
var TodoItemSchema = z11.object({
|
|
2134
|
+
step: z11.string().min(1),
|
|
2135
|
+
status: z11.enum(["pending", "in_progress", "completed"])
|
|
2136
|
+
}).strict();
|
|
2137
|
+
function createAgentStateTools(input) {
|
|
2138
|
+
const writeGoal = async (value) => {
|
|
2139
|
+
const stored = await input.state.set({
|
|
2140
|
+
conversationId: input.conversationId,
|
|
2141
|
+
name: "goal",
|
|
2142
|
+
value,
|
|
2143
|
+
actor: "agent"
|
|
2144
|
+
});
|
|
2145
|
+
return GoalSchema.parse(stored.value);
|
|
2146
|
+
};
|
|
2147
|
+
return [
|
|
2148
|
+
defineAgentTool({
|
|
2149
|
+
name: "create_goal",
|
|
2150
|
+
description: "Create the durable objective for this conversation.",
|
|
2151
|
+
identity: { serviceName: "agent-state", action: "create-goal", method: "POST" },
|
|
2152
|
+
input: z11.object({ objective: z11.string().min(1) }).strict(),
|
|
2153
|
+
output: GoalSchema,
|
|
2154
|
+
transports: ["AGENT"],
|
|
2155
|
+
handler: async ({ input: request }) => {
|
|
2156
|
+
if (await input.state.get(input.conversationId, "goal")) {
|
|
2157
|
+
throw new TypeError("A durable goal already exists for this conversation");
|
|
2158
|
+
}
|
|
2159
|
+
return writeGoal({ objective: request.objective, status: "active" });
|
|
2160
|
+
}
|
|
2161
|
+
}),
|
|
2162
|
+
defineAgentTool({
|
|
2163
|
+
name: "update_goal",
|
|
2164
|
+
description: "Update the durable objective or lifecycle status.",
|
|
2165
|
+
identity: { serviceName: "agent-state", action: "update-goal", method: "POST" },
|
|
2166
|
+
input: z11.object({
|
|
2167
|
+
objective: z11.string().min(1).optional(),
|
|
2168
|
+
status: z11.enum(["active", "complete", "blocked"]).optional()
|
|
2169
|
+
}).strict().refine((request) => request.objective !== undefined || request.status !== undefined, {
|
|
2170
|
+
message: "At least one goal field must be supplied"
|
|
2171
|
+
}),
|
|
2172
|
+
output: GoalSchema,
|
|
2173
|
+
transports: ["AGENT"],
|
|
2174
|
+
handler: async ({ input: request }) => {
|
|
2175
|
+
const current = await input.state.get(input.conversationId, "goal");
|
|
2176
|
+
if (!current)
|
|
2177
|
+
throw new TypeError("No durable goal exists for this conversation");
|
|
2178
|
+
const goal = GoalSchema.parse(current.value);
|
|
2179
|
+
return writeGoal({ ...goal, ...request });
|
|
2180
|
+
}
|
|
2181
|
+
}),
|
|
2182
|
+
defineAgentTool({
|
|
2183
|
+
name: "get_goal",
|
|
2184
|
+
description: "Read the durable objective for this conversation.",
|
|
2185
|
+
identity: { serviceName: "agent-state", action: "get-goal", method: "GET" },
|
|
2186
|
+
input: z11.object({}).strict(),
|
|
2187
|
+
output: z11.object({ goal: GoalSchema.optional() }).strict(),
|
|
2188
|
+
transports: ["AGENT"],
|
|
2189
|
+
handler: async () => {
|
|
2190
|
+
const current = await input.state.get(input.conversationId, "goal");
|
|
2191
|
+
return { ...current && { goal: GoalSchema.parse(current.value) } };
|
|
2192
|
+
}
|
|
2193
|
+
}),
|
|
2194
|
+
defineAgentTool({
|
|
2195
|
+
name: "todo_write",
|
|
2196
|
+
description: "Replace the durable ordered work plan for this conversation.",
|
|
2197
|
+
identity: { serviceName: "agent-state", action: "write-todo", method: "POST" },
|
|
2198
|
+
input: z11.object({ items: z11.array(TodoItemSchema) }).strict(),
|
|
2199
|
+
output: z11.object({ items: z11.array(TodoItemSchema) }).strict(),
|
|
2200
|
+
transports: ["AGENT"],
|
|
2201
|
+
handler: async ({ input: request }) => {
|
|
2202
|
+
const stored = await input.state.set({
|
|
2203
|
+
conversationId: input.conversationId,
|
|
2204
|
+
name: "todo",
|
|
2205
|
+
value: request,
|
|
2206
|
+
actor: "agent"
|
|
2207
|
+
});
|
|
2208
|
+
return z11.object({ items: z11.array(TodoItemSchema) }).strict().parse(stored.value);
|
|
2209
|
+
}
|
|
2210
|
+
})
|
|
2211
|
+
];
|
|
822
2212
|
}
|
|
823
2213
|
export {
|
|
824
2214
|
validateAgentModelSnapshot,
|
|
@@ -827,36 +2217,69 @@ export {
|
|
|
827
2217
|
selectAgentHistory,
|
|
828
2218
|
searchAgentModelCatalog,
|
|
829
2219
|
runStateForTerminalReason,
|
|
2220
|
+
renderAgentStateSlots,
|
|
2221
|
+
recordAgentSandboxProbe,
|
|
2222
|
+
recordAgentRetryDecision,
|
|
830
2223
|
purgeAgentConversation,
|
|
831
2224
|
projectAgentHistoryDetailed,
|
|
832
2225
|
projectAgentHistory,
|
|
2226
|
+
probeAgentProcessSandbox,
|
|
2227
|
+
missingSandboxRestrictions,
|
|
833
2228
|
isToolResultFailure,
|
|
834
2229
|
isAssistantHistoryEvidence,
|
|
835
2230
|
hasAgentTerminalOutput,
|
|
2231
|
+
encodeAgentConversationArchive,
|
|
2232
|
+
defineStateSlot,
|
|
836
2233
|
defineModelRegistry,
|
|
837
2234
|
defineAgentProtocol,
|
|
2235
|
+
defineAgentProjection,
|
|
2236
|
+
decodeAgentStoreEvent,
|
|
2237
|
+
decodeAgentConversationArchive,
|
|
2238
|
+
createSqliteAgentSpillStore,
|
|
2239
|
+
createSqliteAgentProjectionStore,
|
|
2240
|
+
createSqliteAgentEventSearch,
|
|
2241
|
+
createSqliteAgentChildManager,
|
|
838
2242
|
createMemoryAgentRuntimeStore,
|
|
839
2243
|
createMemoryAgentModelSelectionStore,
|
|
840
2244
|
createDeferredAgentToolSurface,
|
|
841
2245
|
createAgentToolFenceLifecycle,
|
|
2246
|
+
createAgentStateTools,
|
|
2247
|
+
createAgentStateSlotStore,
|
|
842
2248
|
createAgentSessionCoordinator,
|
|
2249
|
+
createAgentScheduleTools,
|
|
2250
|
+
createAgentScheduleService,
|
|
843
2251
|
createAgentRuntimeStore,
|
|
844
2252
|
createAgentRuntimeEventSink,
|
|
845
2253
|
createAgentRuntime,
|
|
2254
|
+
createAgentProjectionRegistry,
|
|
846
2255
|
createAgentObservability,
|
|
2256
|
+
createAgentEventSearchTools,
|
|
2257
|
+
createAgentChildTools,
|
|
847
2258
|
composeAgentPrompt,
|
|
848
2259
|
classifyProviderFailure,
|
|
2260
|
+
canonicalAgentJson,
|
|
2261
|
+
agentUsageProjection,
|
|
2262
|
+
agentTodoStateSlot,
|
|
2263
|
+
agentSummaryProjection,
|
|
2264
|
+
agentStateSlotsProjection,
|
|
2265
|
+
agentScheduleSummaryProjection,
|
|
2266
|
+
agentOutlineProjection,
|
|
2267
|
+
agentGoalStateSlot,
|
|
849
2268
|
agentDurableEventId,
|
|
2269
|
+
agentConversationCardProjection,
|
|
2270
|
+
agentChildBudgetStopPolicy,
|
|
850
2271
|
advanceAgentRuntimeEventCursor,
|
|
851
2272
|
RequestRunInterruptSchema,
|
|
852
2273
|
ReplaceCompactedRangeSchema,
|
|
853
2274
|
RecoverAgentRunSchema,
|
|
854
2275
|
RecordRunOperationSchema,
|
|
2276
|
+
ReadAgentStoreEventsSchema,
|
|
855
2277
|
DeferredAgentToolSearchInputSchema,
|
|
856
2278
|
DeferredAgentToolReceiptSchema,
|
|
857
2279
|
DeferredAgentToolMatchSchema,
|
|
858
2280
|
CommitRunTerminalSchema,
|
|
859
2281
|
CheckpointRunAssistantSchema,
|
|
2282
|
+
AppendAgentStoreEventSchema,
|
|
860
2283
|
AgentUsageValueSchema,
|
|
861
2284
|
AgentUsageSchema,
|
|
862
2285
|
AgentTransientDeltaEventSchema,
|
|
@@ -871,14 +2294,21 @@ export {
|
|
|
871
2294
|
AgentTerminalReasonSchema,
|
|
872
2295
|
AgentTerminalEventSchema,
|
|
873
2296
|
AgentStoredRunSchema,
|
|
2297
|
+
AgentStoreTransitionSchema,
|
|
874
2298
|
AgentStoreNotFoundSchema,
|
|
875
2299
|
AgentStoreMutationResultSchema,
|
|
2300
|
+
AgentStoreEventPageSchema,
|
|
2301
|
+
AgentStoreEventKindSchema,
|
|
2302
|
+
AgentStoreEventEnvelopeSchema,
|
|
876
2303
|
AgentStoreDuplicateSchema,
|
|
877
2304
|
AgentStoreConflictSchema,
|
|
878
2305
|
AgentStoreAppliedSchema,
|
|
879
2306
|
AgentStepFinishedEventSchema,
|
|
880
2307
|
AgentSourcePartSchema,
|
|
881
2308
|
AgentSnapshotSchema,
|
|
2309
|
+
AgentScheduleSchema,
|
|
2310
|
+
AgentSandboxRestrictionSchema,
|
|
2311
|
+
AgentSandboxGradeSchema,
|
|
882
2312
|
AgentRuntimeHeadSchema,
|
|
883
2313
|
AgentRuntimeEventSchema,
|
|
884
2314
|
AgentRuntimeEventCursorSchema,
|
|
@@ -904,6 +2334,7 @@ export {
|
|
|
904
2334
|
AgentReasoningPartSchema,
|
|
905
2335
|
AgentReasoningEndEventSchema,
|
|
906
2336
|
AgentReasoningDeltaEventSchema,
|
|
2337
|
+
AgentProviderStreamCutError,
|
|
907
2338
|
AgentProviderEnvelopeSchema,
|
|
908
2339
|
AgentProvenanceSchema,
|
|
909
2340
|
AgentOpaquePartSchema,
|
|
@@ -925,6 +2356,7 @@ export {
|
|
|
925
2356
|
AgentJsonObjectSchema,
|
|
926
2357
|
AgentHistoryMutationSchema,
|
|
927
2358
|
AgentFilePartSchema,
|
|
2359
|
+
AgentEventSearchResultSchema,
|
|
928
2360
|
AgentCostValueSchema,
|
|
929
2361
|
AgentConversationSummarySchema,
|
|
930
2362
|
AgentConversationPurgedError,
|
|
@@ -932,9 +2364,14 @@ export {
|
|
|
932
2364
|
AgentConversationPurgeInputSchema,
|
|
933
2365
|
AgentConversationPageSchema,
|
|
934
2366
|
AgentConversationMessagePageSchema,
|
|
2367
|
+
AgentConversationArchiveSchema,
|
|
935
2368
|
AgentControlPartSchema,
|
|
936
2369
|
AgentContextOverflowError,
|
|
2370
|
+
AgentChildStateSchema,
|
|
2371
|
+
AgentChildRecordSchema,
|
|
2372
|
+
AgentChildBudgetSchema,
|
|
937
2373
|
AgentCheckpointEventSchema,
|
|
2374
|
+
AgentAttemptResetEventSchema,
|
|
938
2375
|
AgentAssistantPlaceholderSchema,
|
|
939
2376
|
AgentAdmissionReceiptSchema,
|
|
940
2377
|
AgentAdmissionEventSchema,
|