@smithers-orchestrator/cli 0.16.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/LICENSE +21 -0
- package/package.json +55 -0
- package/src/AgentAvailability.ts +13 -0
- package/src/AgentAvailabilityStatus.ts +5 -0
- package/src/AggregateNodeDetailParams.ts +5 -0
- package/src/AskOptions.ts +12 -0
- package/src/ChatAttemptMeta.ts +7 -0
- package/src/ChatAttemptRow.ts +12 -0
- package/src/ChatOutputEvent.ts +6 -0
- package/src/DiffBundleLike.ts +6 -0
- package/src/DiscoveredWorkflow.ts +9 -0
- package/src/EnrichedNodeDetail.ts +60 -0
- package/src/EventCategory.ts +18 -0
- package/src/FindDbWaitOptions.ts +4 -0
- package/src/FormatEventLineOptions.ts +4 -0
- package/src/HijackCandidate.ts +11 -0
- package/src/HijackLaunchSpec.ts +6 -0
- package/src/InitWorkflowPackOptions.ts +4 -0
- package/src/InitWorkflowPackResult.ts +6 -0
- package/src/NativeHijackEngine.ts +8 -0
- package/src/NodeDetailAttempt.ts +22 -0
- package/src/NodeDetailTokenUsage.ts +11 -0
- package/src/NodeDetailToolCall.ts +12 -0
- package/src/ParsedNodeOutputEvent.ts +9 -0
- package/src/RenderNodeDetailOptions.ts +4 -0
- package/src/RunAutoResumeSkipReason.ts +4 -0
- package/src/RunDiffCommandInput.ts +13 -0
- package/src/RunDiffCommandResult.ts +3 -0
- package/src/RunOutputCommandInput.ts +12 -0
- package/src/RunOutputCommandResult.ts +3 -0
- package/src/RunRewindCommandInput.ts +14 -0
- package/src/RunRewindCommandResult.ts +3 -0
- package/src/RunTreeCommandInput.ts +14 -0
- package/src/RunTreeCommandResult.ts +3 -0
- package/src/SmithersEventType.ts +3 -0
- package/src/SupervisorOptions.ts +33 -0
- package/src/SupervisorPollSummary.ts +6 -0
- package/src/TreeRenderOptions.ts +5 -0
- package/src/WatchLoopOptions.ts +9 -0
- package/src/WatchLoopResult.ts +8 -0
- package/src/WatchRenderContext.ts +4 -0
- package/src/WhyBlocker.ts +17 -0
- package/src/WhyBlockerKind.ts +9 -0
- package/src/WhyDiagnosis.ts +10 -0
- package/src/WorkflowCta.ts +4 -0
- package/src/WorkflowSourceType.ts +1 -0
- package/src/agent-detection.js +257 -0
- package/src/ask.js +491 -0
- package/src/chat.js +226 -0
- package/src/diff.js +221 -0
- package/src/event-categories.js +141 -0
- package/src/find-db.js +93 -0
- package/src/format.js +272 -0
- package/src/hijack-session.js +207 -0
- package/src/hijack.js +226 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +4868 -0
- package/src/mcp/SemanticMcpServerOptions.ts +4 -0
- package/src/mcp/SemanticToolCallResult.ts +14 -0
- package/src/mcp/SemanticToolContext.ts +6 -0
- package/src/mcp/SemanticToolDefinition.ts +13 -0
- package/src/mcp/SemanticToolError.ts +6 -0
- package/src/mcp/semantic-server.js +41 -0
- package/src/mcp/semantic-tools.js +1242 -0
- package/src/node-detail.js +682 -0
- package/src/output.js +111 -0
- package/src/resume-detached.js +37 -0
- package/src/rewind.js +88 -0
- package/src/scheduler.js +112 -0
- package/src/smithersRuntime.js +63 -0
- package/src/supervisor.js +418 -0
- package/src/tree.js +307 -0
- package/src/tui/app.jsx +139 -0
- package/src/tui/app.tsx +5 -0
- package/src/tui/components/AskModal.jsx +109 -0
- package/src/tui/components/AskModal.tsx +3 -0
- package/src/tui/components/AttentionPane.jsx +112 -0
- package/src/tui/components/AttentionPane.tsx +6 -0
- package/src/tui/components/ChatPane.jsx +57 -0
- package/src/tui/components/ChatPane.tsx +7 -0
- package/src/tui/components/CronList.jsx +87 -0
- package/src/tui/components/CronList.tsx +5 -0
- package/src/tui/components/DetailsPane.jsx +96 -0
- package/src/tui/components/DetailsPane.tsx +7 -0
- package/src/tui/components/FramesPane.jsx +147 -0
- package/src/tui/components/FramesPane.tsx +8 -0
- package/src/tui/components/LogsPane.jsx +46 -0
- package/src/tui/components/LogsPane.tsx +6 -0
- package/src/tui/components/MetricsPane.jsx +108 -0
- package/src/tui/components/MetricsPane.tsx +5 -0
- package/src/tui/components/NodeDetailView.jsx +284 -0
- package/src/tui/components/NodeDetailView.tsx +7 -0
- package/src/tui/components/NodeInspector.jsx +51 -0
- package/src/tui/components/NodeInspector.tsx +7 -0
- package/src/tui/components/RunDetailView.jsx +190 -0
- package/src/tui/components/RunDetailView.tsx +7 -0
- package/src/tui/components/RunsList.jsx +184 -0
- package/src/tui/components/RunsList.tsx +7 -0
- package/src/tui/components/SqliteBrowser.jsx +131 -0
- package/src/tui/components/SqliteBrowser.tsx +5 -0
- package/src/tui/components/WorkflowLauncher.jsx +63 -0
- package/src/tui/components/WorkflowLauncher.tsx +3 -0
- package/src/util/CliErrorMapping.ts +7 -0
- package/src/util/CliExitCode.ts +10 -0
- package/src/util/errorMessage.js +212 -0
- package/src/util/exitCodes.js +18 -0
- package/src/watch.js +128 -0
- package/src/why-diagnosis.js +1000 -0
- package/src/workflow-pack.js +2151 -0
- package/src/workflows.js +122 -0
|
@@ -0,0 +1,1242 @@
|
|
|
1
|
+
import { basename, extname, resolve } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ensureSmithersTables } from "@smithers-orchestrator/db/ensure";
|
|
6
|
+
import { SmithersDb } from "@smithers-orchestrator/db/adapter";
|
|
7
|
+
import { findAndOpenDb } from "../find-db.js";
|
|
8
|
+
import { aggregateNodeDetailEffect, } from "../node-detail.js";
|
|
9
|
+
import { diagnoseRunEffect, } from "../why-diagnosis.js";
|
|
10
|
+
import { chatAttemptKey, parseChatAttemptMeta, parseNodeOutputEvent, selectChatAttempts, } from "../chat.js";
|
|
11
|
+
import { WATCH_MIN_INTERVAL_MS } from "../watch.js";
|
|
12
|
+
import { discoverWorkflows, resolveWorkflow } from "../workflows.js";
|
|
13
|
+
import { mdxPlugin } from "smithers-orchestrator/mdx-plugin";
|
|
14
|
+
import { approveNode, denyNode } from "@smithers-orchestrator/engine/approvals";
|
|
15
|
+
import { runWorkflow } from "@smithers-orchestrator/engine";
|
|
16
|
+
import { revertToAttempt } from "@smithers-orchestrator/time-travel/revert";
|
|
17
|
+
import { runPromise } from "../smithersRuntime.js";
|
|
18
|
+
import { SmithersError } from "@smithers-orchestrator/errors";
|
|
19
|
+
import { toSmithersError } from "@smithers-orchestrator/errors/toSmithersError";
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{ content: Array<{ type: "text"; text: string; }>; structuredContent: { ok: boolean; data?: unknown; error?: z.infer<typeof toolErrorSchema>; }; isError?: boolean; }} SemanticToolCallResult
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {{ cwd: () => string; openDb: typeof findAndOpenDb; }} SemanticToolContext
|
|
25
|
+
*/
|
|
26
|
+
/** @typedef {import("./SemanticToolDefinition.ts").SemanticToolDefinition} SemanticToolDefinition */
|
|
27
|
+
|
|
28
|
+
export const SEMANTIC_TOOL_NAMES = [
|
|
29
|
+
"list_workflows",
|
|
30
|
+
"run_workflow",
|
|
31
|
+
"list_runs",
|
|
32
|
+
"get_run",
|
|
33
|
+
"watch_run",
|
|
34
|
+
"explain_run",
|
|
35
|
+
"list_pending_approvals",
|
|
36
|
+
"resolve_approval",
|
|
37
|
+
"get_node_detail",
|
|
38
|
+
"revert_attempt",
|
|
39
|
+
"list_artifacts",
|
|
40
|
+
"get_chat_transcript",
|
|
41
|
+
"get_run_events",
|
|
42
|
+
];
|
|
43
|
+
const workflowSummarySchema = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
displayName: z.string(),
|
|
46
|
+
entryFile: z.string(),
|
|
47
|
+
sourceType: z.enum(["seeded", "user", "generated"]),
|
|
48
|
+
});
|
|
49
|
+
const timerSchema = z.object({
|
|
50
|
+
nodeId: z.string(),
|
|
51
|
+
iteration: z.number().int(),
|
|
52
|
+
firesAtMs: z.number(),
|
|
53
|
+
remainingMs: z.number(),
|
|
54
|
+
timerType: z.enum(["duration", "absolute"]),
|
|
55
|
+
});
|
|
56
|
+
const runSummarySchema = z.object({
|
|
57
|
+
runId: z.string(),
|
|
58
|
+
workflowName: z.string().nullable(),
|
|
59
|
+
workflowPath: z.string().nullable(),
|
|
60
|
+
parentRunId: z.string().nullable(),
|
|
61
|
+
status: z.string(),
|
|
62
|
+
createdAtMs: z.number(),
|
|
63
|
+
startedAtMs: z.number().nullable(),
|
|
64
|
+
finishedAtMs: z.number().nullable(),
|
|
65
|
+
heartbeatAtMs: z.number().nullable(),
|
|
66
|
+
activeNodeId: z.string().nullable(),
|
|
67
|
+
activeNodeLabel: z.string().nullable(),
|
|
68
|
+
pendingApprovalCount: z.number().int(),
|
|
69
|
+
waitingTimers: z.array(timerSchema),
|
|
70
|
+
countsByState: z.record(z.string(), z.number().int()),
|
|
71
|
+
});
|
|
72
|
+
const runStepSchema = z.object({
|
|
73
|
+
nodeId: z.string(),
|
|
74
|
+
iteration: z.number().int(),
|
|
75
|
+
state: z.string(),
|
|
76
|
+
lastAttempt: z.number().int().nullable(),
|
|
77
|
+
updatedAtMs: z.number().nullable(),
|
|
78
|
+
outputTable: z.string().nullable(),
|
|
79
|
+
label: z.string().nullable(),
|
|
80
|
+
});
|
|
81
|
+
const pendingApprovalSchema = z.object({
|
|
82
|
+
runId: z.string(),
|
|
83
|
+
nodeId: z.string(),
|
|
84
|
+
iteration: z.number().int(),
|
|
85
|
+
status: z.string(),
|
|
86
|
+
requestedAtMs: z.number().nullable().optional(),
|
|
87
|
+
decidedAtMs: z.number().nullable().optional(),
|
|
88
|
+
note: z.string().nullable().optional(),
|
|
89
|
+
decidedBy: z.string().nullable().optional(),
|
|
90
|
+
request: z.unknown().nullable().optional(),
|
|
91
|
+
decision: z.unknown().nullable().optional(),
|
|
92
|
+
autoApproved: z.boolean().optional(),
|
|
93
|
+
workflowName: z.string().nullable().optional(),
|
|
94
|
+
runStatus: z.string().nullable().optional(),
|
|
95
|
+
nodeLabel: z.string().nullable().optional(),
|
|
96
|
+
});
|
|
97
|
+
const runLoopSchema = z.object({
|
|
98
|
+
loopId: z.string(),
|
|
99
|
+
iteration: z.number().int(),
|
|
100
|
+
maxIterations: z.number().int().nullable(),
|
|
101
|
+
});
|
|
102
|
+
const runDetailSchema = runSummarySchema.extend({
|
|
103
|
+
steps: z.array(runStepSchema),
|
|
104
|
+
approvals: z.array(pendingApprovalSchema),
|
|
105
|
+
loops: z.array(runLoopSchema),
|
|
106
|
+
continuedFromRunIds: z.array(z.string()),
|
|
107
|
+
activeDescendantRunId: z.string().nullable(),
|
|
108
|
+
config: z.unknown().nullable(),
|
|
109
|
+
error: z.unknown().nullable(),
|
|
110
|
+
});
|
|
111
|
+
const runWatchSnapshotSchema = z.object({
|
|
112
|
+
observedAtMs: z.number(),
|
|
113
|
+
run: runSummarySchema,
|
|
114
|
+
});
|
|
115
|
+
const nodeDetailSchema = z.object({
|
|
116
|
+
node: z.object({
|
|
117
|
+
runId: z.string(),
|
|
118
|
+
nodeId: z.string(),
|
|
119
|
+
iteration: z.number().int(),
|
|
120
|
+
state: z.string(),
|
|
121
|
+
lastAttempt: z.number().int().nullable(),
|
|
122
|
+
updatedAtMs: z.number().nullable(),
|
|
123
|
+
outputTable: z.string().nullable(),
|
|
124
|
+
label: z.string().nullable(),
|
|
125
|
+
}),
|
|
126
|
+
status: z.string(),
|
|
127
|
+
durationMs: z.number().nullable(),
|
|
128
|
+
attemptsSummary: z.object({
|
|
129
|
+
total: z.number().int(),
|
|
130
|
+
failed: z.number().int(),
|
|
131
|
+
cancelled: z.number().int(),
|
|
132
|
+
succeeded: z.number().int(),
|
|
133
|
+
waiting: z.number().int(),
|
|
134
|
+
}),
|
|
135
|
+
attempts: z.array(z.unknown()),
|
|
136
|
+
toolCalls: z.array(z.unknown()),
|
|
137
|
+
tokenUsage: z.unknown(),
|
|
138
|
+
scorers: z.array(z.unknown()),
|
|
139
|
+
output: z.object({
|
|
140
|
+
validated: z.unknown().nullable(),
|
|
141
|
+
raw: z.unknown().nullable(),
|
|
142
|
+
source: z.enum(["cache", "output-table", "none"]),
|
|
143
|
+
cacheKey: z.string().nullable(),
|
|
144
|
+
}),
|
|
145
|
+
limits: z.object({
|
|
146
|
+
toolPayloadBytesHuman: z.number().int(),
|
|
147
|
+
validatedOutputBytesHuman: z.number().int(),
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
const diagnosisSchema = z.object({
|
|
151
|
+
runId: z.string(),
|
|
152
|
+
status: z.string(),
|
|
153
|
+
summary: z.string(),
|
|
154
|
+
generatedAtMs: z.number(),
|
|
155
|
+
blockers: z.array(z.object({
|
|
156
|
+
kind: z.string(),
|
|
157
|
+
nodeId: z.string(),
|
|
158
|
+
iteration: z.number().nullable(),
|
|
159
|
+
reason: z.string(),
|
|
160
|
+
waitingSince: z.number(),
|
|
161
|
+
unblocker: z.string(),
|
|
162
|
+
context: z.string().optional(),
|
|
163
|
+
signalName: z.string().nullable().optional(),
|
|
164
|
+
dependencyNodeId: z.string().nullable().optional(),
|
|
165
|
+
firesAtMs: z.number().nullable().optional(),
|
|
166
|
+
remainingMs: z.number().nullable().optional(),
|
|
167
|
+
attempt: z.number().nullable().optional(),
|
|
168
|
+
maxAttempts: z.number().nullable().optional(),
|
|
169
|
+
})),
|
|
170
|
+
currentNodeId: z.string().nullable(),
|
|
171
|
+
});
|
|
172
|
+
const eventSchema = z.object({
|
|
173
|
+
runId: z.string(),
|
|
174
|
+
seq: z.number().int(),
|
|
175
|
+
timestampMs: z.number(),
|
|
176
|
+
type: z.string(),
|
|
177
|
+
payload: z.unknown().nullable(),
|
|
178
|
+
});
|
|
179
|
+
const artifactSchema = z.object({
|
|
180
|
+
artifactId: z.string(),
|
|
181
|
+
kind: z.literal("node-output"),
|
|
182
|
+
runId: z.string(),
|
|
183
|
+
nodeId: z.string(),
|
|
184
|
+
iteration: z.number().int(),
|
|
185
|
+
label: z.string().nullable(),
|
|
186
|
+
state: z.string(),
|
|
187
|
+
outputTable: z.string().nullable(),
|
|
188
|
+
source: z.enum(["cache", "output-table", "none"]),
|
|
189
|
+
cacheKey: z.string().nullable(),
|
|
190
|
+
value: z.unknown().nullable(),
|
|
191
|
+
rawValue: z.unknown().nullable().optional(),
|
|
192
|
+
});
|
|
193
|
+
const chatAttemptSchema = z.object({
|
|
194
|
+
attemptKey: z.string(),
|
|
195
|
+
nodeId: z.string(),
|
|
196
|
+
iteration: z.number().int(),
|
|
197
|
+
attempt: z.number().int(),
|
|
198
|
+
state: z.string(),
|
|
199
|
+
startedAtMs: z.number(),
|
|
200
|
+
finishedAtMs: z.number().nullable(),
|
|
201
|
+
cached: z.boolean(),
|
|
202
|
+
meta: z.unknown().nullable(),
|
|
203
|
+
});
|
|
204
|
+
const chatMessageSchema = z.object({
|
|
205
|
+
id: z.string(),
|
|
206
|
+
attemptKey: z.string(),
|
|
207
|
+
nodeId: z.string(),
|
|
208
|
+
iteration: z.number().int(),
|
|
209
|
+
attempt: z.number().int(),
|
|
210
|
+
role: z.enum(["user", "assistant", "stderr"]),
|
|
211
|
+
stream: z.enum(["stdout", "stderr"]).nullable(),
|
|
212
|
+
timestampMs: z.number(),
|
|
213
|
+
text: z.string(),
|
|
214
|
+
source: z.enum(["prompt", "event", "responseText"]),
|
|
215
|
+
});
|
|
216
|
+
const toolErrorSchema = z.object({
|
|
217
|
+
code: z.string(),
|
|
218
|
+
message: z.string(),
|
|
219
|
+
details: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
220
|
+
docsUrl: z.string().nullable().optional(),
|
|
221
|
+
});
|
|
222
|
+
/**
|
|
223
|
+
* @template Data
|
|
224
|
+
* @param {Data} data
|
|
225
|
+
*/
|
|
226
|
+
function resultSchema(data) {
|
|
227
|
+
return z.object({
|
|
228
|
+
ok: z.boolean(),
|
|
229
|
+
data: data.optional(),
|
|
230
|
+
error: toolErrorSchema.optional(),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const listWorkflowsInputSchema = z.object({});
|
|
234
|
+
const listWorkflowsDataSchema = z.object({
|
|
235
|
+
workflows: z.array(workflowSummarySchema),
|
|
236
|
+
});
|
|
237
|
+
const runWorkflowInputSchema = z.object({
|
|
238
|
+
workflowId: z.string().describe("Discovered workflow ID from .smithers/workflows"),
|
|
239
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
240
|
+
prompt: z.string().optional(),
|
|
241
|
+
runId: z.string().optional(),
|
|
242
|
+
resume: z.boolean().default(false),
|
|
243
|
+
force: z.boolean().default(false),
|
|
244
|
+
waitForTerminal: z.boolean().default(false),
|
|
245
|
+
waitForStartMs: z.number().int().min(0).default(1_000),
|
|
246
|
+
maxConcurrency: z.number().int().min(1).optional(),
|
|
247
|
+
rootDir: z.string().optional(),
|
|
248
|
+
logDir: z.string().optional(),
|
|
249
|
+
allowNetwork: z.boolean().default(false),
|
|
250
|
+
maxOutputBytes: z.number().int().min(1).optional(),
|
|
251
|
+
toolTimeoutMs: z.number().int().min(1).optional(),
|
|
252
|
+
hot: z.boolean().default(false),
|
|
253
|
+
}).superRefine((value, ctx) => {
|
|
254
|
+
if (value.resume && !value.runId) {
|
|
255
|
+
ctx.addIssue({
|
|
256
|
+
code: z.ZodIssueCode.custom,
|
|
257
|
+
message: "runId is required when resume=true",
|
|
258
|
+
path: ["runId"],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const runWorkflowDataSchema = z.object({
|
|
263
|
+
workflow: workflowSummarySchema,
|
|
264
|
+
runId: z.string(),
|
|
265
|
+
launchMode: z.enum(["background", "waited"]),
|
|
266
|
+
requestedResume: z.boolean(),
|
|
267
|
+
status: z.string(),
|
|
268
|
+
observedRun: runSummarySchema.nullable(),
|
|
269
|
+
result: z.object({
|
|
270
|
+
runId: z.string(),
|
|
271
|
+
status: z.string(),
|
|
272
|
+
output: z.unknown().optional(),
|
|
273
|
+
error: z.unknown().optional(),
|
|
274
|
+
}).nullable(),
|
|
275
|
+
});
|
|
276
|
+
const listRunsInputSchema = z.object({
|
|
277
|
+
limit: z.number().int().min(1).max(200).default(20),
|
|
278
|
+
status: z.string().optional(),
|
|
279
|
+
});
|
|
280
|
+
const listRunsDataSchema = z.object({
|
|
281
|
+
runs: z.array(runSummarySchema),
|
|
282
|
+
});
|
|
283
|
+
const getRunInputSchema = z.object({
|
|
284
|
+
runId: z.string(),
|
|
285
|
+
});
|
|
286
|
+
const getRunDataSchema = z.object({
|
|
287
|
+
run: runDetailSchema,
|
|
288
|
+
});
|
|
289
|
+
const watchRunInputSchema = z.object({
|
|
290
|
+
runId: z.string(),
|
|
291
|
+
intervalMs: z.number().int().min(1).default(1_000),
|
|
292
|
+
timeoutMs: z.number().int().min(0).default(30_000),
|
|
293
|
+
});
|
|
294
|
+
const watchRunDataSchema = z.object({
|
|
295
|
+
runId: z.string(),
|
|
296
|
+
intervalMs: z.number().int(),
|
|
297
|
+
pollCount: z.number().int(),
|
|
298
|
+
reachedTerminal: z.boolean(),
|
|
299
|
+
timedOut: z.boolean(),
|
|
300
|
+
finalRun: runSummarySchema,
|
|
301
|
+
snapshots: z.array(runWatchSnapshotSchema),
|
|
302
|
+
});
|
|
303
|
+
const explainRunInputSchema = z.object({
|
|
304
|
+
runId: z.string(),
|
|
305
|
+
});
|
|
306
|
+
const explainRunDataSchema = z.object({
|
|
307
|
+
diagnosis: diagnosisSchema,
|
|
308
|
+
});
|
|
309
|
+
const listPendingApprovalsInputSchema = z.object({
|
|
310
|
+
runId: z.string().optional(),
|
|
311
|
+
workflowName: z.string().optional(),
|
|
312
|
+
nodeId: z.string().optional(),
|
|
313
|
+
});
|
|
314
|
+
const listPendingApprovalsDataSchema = z.object({
|
|
315
|
+
approvals: z.array(pendingApprovalSchema),
|
|
316
|
+
});
|
|
317
|
+
const resolveApprovalInputSchema = z.object({
|
|
318
|
+
action: z.enum(["approve", "deny"]),
|
|
319
|
+
runId: z.string().optional(),
|
|
320
|
+
workflowName: z.string().optional(),
|
|
321
|
+
nodeId: z.string().optional(),
|
|
322
|
+
iteration: z.number().int().min(0).optional(),
|
|
323
|
+
note: z.string().optional(),
|
|
324
|
+
decidedBy: z.string().optional(),
|
|
325
|
+
decision: z.unknown().optional(),
|
|
326
|
+
});
|
|
327
|
+
const resolveApprovalDataSchema = z.object({
|
|
328
|
+
action: z.enum(["approve", "deny"]),
|
|
329
|
+
approval: pendingApprovalSchema,
|
|
330
|
+
run: runSummarySchema.nullable(),
|
|
331
|
+
});
|
|
332
|
+
const getNodeDetailInputSchema = z.object({
|
|
333
|
+
runId: z.string(),
|
|
334
|
+
nodeId: z.string(),
|
|
335
|
+
iteration: z.number().int().min(0).optional(),
|
|
336
|
+
});
|
|
337
|
+
const getNodeDetailDataSchema = z.object({
|
|
338
|
+
detail: nodeDetailSchema,
|
|
339
|
+
});
|
|
340
|
+
const revertAttemptInputSchema = z.object({
|
|
341
|
+
runId: z.string(),
|
|
342
|
+
nodeId: z.string(),
|
|
343
|
+
iteration: z.number().int().min(0).default(0),
|
|
344
|
+
attempt: z.number().int().min(1),
|
|
345
|
+
});
|
|
346
|
+
const revertAttemptDataSchema = z.object({
|
|
347
|
+
runId: z.string(),
|
|
348
|
+
nodeId: z.string(),
|
|
349
|
+
iteration: z.number().int(),
|
|
350
|
+
attempt: z.number().int(),
|
|
351
|
+
success: z.boolean(),
|
|
352
|
+
error: z.string().optional(),
|
|
353
|
+
jjPointer: z.string().optional(),
|
|
354
|
+
run: runSummarySchema.nullable(),
|
|
355
|
+
});
|
|
356
|
+
const listArtifactsInputSchema = z.object({
|
|
357
|
+
runId: z.string(),
|
|
358
|
+
nodeId: z.string().optional(),
|
|
359
|
+
includeRaw: z.boolean().default(false),
|
|
360
|
+
});
|
|
361
|
+
const listArtifactsDataSchema = z.object({
|
|
362
|
+
artifacts: z.array(artifactSchema),
|
|
363
|
+
});
|
|
364
|
+
const getChatTranscriptInputSchema = z.object({
|
|
365
|
+
runId: z.string(),
|
|
366
|
+
all: z.boolean().default(false),
|
|
367
|
+
includeStderr: z.boolean().default(true),
|
|
368
|
+
tail: z.number().int().min(1).optional(),
|
|
369
|
+
});
|
|
370
|
+
const getChatTranscriptDataSchema = z.object({
|
|
371
|
+
runId: z.string(),
|
|
372
|
+
attempts: z.array(chatAttemptSchema),
|
|
373
|
+
messages: z.array(chatMessageSchema),
|
|
374
|
+
});
|
|
375
|
+
const getRunEventsInputSchema = z.object({
|
|
376
|
+
runId: z.string(),
|
|
377
|
+
afterSeq: z.number().int().optional(),
|
|
378
|
+
limit: z.number().int().min(1).max(10_000).default(200),
|
|
379
|
+
nodeId: z.string().optional(),
|
|
380
|
+
types: z.array(z.string()).optional(),
|
|
381
|
+
sinceTimestampMs: z.number().int().optional(),
|
|
382
|
+
});
|
|
383
|
+
const getRunEventsDataSchema = z.object({
|
|
384
|
+
runId: z.string(),
|
|
385
|
+
events: z.array(eventSchema),
|
|
386
|
+
});
|
|
387
|
+
/**
|
|
388
|
+
* @param {number} ms
|
|
389
|
+
*/
|
|
390
|
+
function sleep(ms) {
|
|
391
|
+
return new Promise((resolvePromise) => {
|
|
392
|
+
setTimeout(resolvePromise, ms);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* @param {string | null | undefined} raw
|
|
397
|
+
* @returns {unknown | null}
|
|
398
|
+
*/
|
|
399
|
+
function parseJsonValue(raw) {
|
|
400
|
+
if (!raw)
|
|
401
|
+
return null;
|
|
402
|
+
try {
|
|
403
|
+
return JSON.parse(raw);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
return raw;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* @param {Pick<RunRow, "workflowName" | "workflowPath">} run
|
|
411
|
+
*/
|
|
412
|
+
function resolveWorkflowName(run) {
|
|
413
|
+
const fromPath = run.workflowPath
|
|
414
|
+
? basename(run.workflowPath, extname(run.workflowPath))
|
|
415
|
+
: null;
|
|
416
|
+
if (run.workflowName && run.workflowName !== "workflow") {
|
|
417
|
+
return run.workflowName;
|
|
418
|
+
}
|
|
419
|
+
return fromPath ?? run.workflowName ?? null;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* @param {string | null} [metaJson]
|
|
423
|
+
*/
|
|
424
|
+
function parseWaitingTimerInfo(metaJson) {
|
|
425
|
+
if (!metaJson)
|
|
426
|
+
return null;
|
|
427
|
+
try {
|
|
428
|
+
const parsed = JSON.parse(metaJson);
|
|
429
|
+
const timer = parsed?.timer;
|
|
430
|
+
if (!timer || typeof timer !== "object")
|
|
431
|
+
return null;
|
|
432
|
+
const firesAtMs = Number(timer.firesAtMs);
|
|
433
|
+
if (!Number.isFinite(firesAtMs))
|
|
434
|
+
return null;
|
|
435
|
+
return {
|
|
436
|
+
firesAtMs: Math.floor(firesAtMs),
|
|
437
|
+
timerType: timer.timerType === "absolute" ? "absolute" : "duration",
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* @param {SmithersDb} adapter
|
|
446
|
+
* @param {string} runId
|
|
447
|
+
*/
|
|
448
|
+
async function listWaitingTimers(adapter, runId) {
|
|
449
|
+
const nodes = await adapter.listNodes(runId);
|
|
450
|
+
const waits = [];
|
|
451
|
+
for (const node of nodes) {
|
|
452
|
+
if (node.state !== "waiting-timer")
|
|
453
|
+
continue;
|
|
454
|
+
const attempts = await adapter.listAttempts(runId, node.nodeId, node.iteration ?? 0);
|
|
455
|
+
const waitingAttempt = attempts.find((attempt) => attempt.state === "waiting-timer") ??
|
|
456
|
+
attempts[0];
|
|
457
|
+
const parsed = parseWaitingTimerInfo(waitingAttempt?.metaJson);
|
|
458
|
+
if (!parsed)
|
|
459
|
+
continue;
|
|
460
|
+
waits.push({
|
|
461
|
+
nodeId: node.nodeId,
|
|
462
|
+
iteration: node.iteration ?? 0,
|
|
463
|
+
firesAtMs: parsed.firesAtMs,
|
|
464
|
+
remainingMs: parsed.firesAtMs - Date.now(),
|
|
465
|
+
timerType: parsed.timerType,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
waits.sort((left, right) => left.firesAtMs - right.firesAtMs);
|
|
469
|
+
return waits;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* @param {SmithersDb} adapter
|
|
473
|
+
* @param {RunRow} run
|
|
474
|
+
*/
|
|
475
|
+
async function buildRunSummary(adapter, run) {
|
|
476
|
+
const [nodes, approvals, waitingTimers, countsByStateRows] = await Promise.all([
|
|
477
|
+
adapter.listNodes(run.runId),
|
|
478
|
+
adapter.listPendingApprovals(run.runId),
|
|
479
|
+
run.status === "waiting-timer"
|
|
480
|
+
? listWaitingTimers(adapter, run.runId)
|
|
481
|
+
: Promise.resolve([]),
|
|
482
|
+
adapter.countNodesByState(run.runId),
|
|
483
|
+
]);
|
|
484
|
+
const activeNode = nodes
|
|
485
|
+
.filter((node) => node.state === "in-progress")
|
|
486
|
+
.sort((left, right) => {
|
|
487
|
+
const leftUpdated = Number(left.updatedAtMs ?? 0);
|
|
488
|
+
const rightUpdated = Number(right.updatedAtMs ?? 0);
|
|
489
|
+
return rightUpdated - leftUpdated;
|
|
490
|
+
})[0];
|
|
491
|
+
const countsByState = Object.fromEntries(countsByStateRows.map((row) => [
|
|
492
|
+
String(row.state),
|
|
493
|
+
Number(row.count ?? 0),
|
|
494
|
+
]));
|
|
495
|
+
return {
|
|
496
|
+
runId: run.runId,
|
|
497
|
+
workflowName: resolveWorkflowName(run),
|
|
498
|
+
workflowPath: run.workflowPath ?? null,
|
|
499
|
+
parentRunId: run.parentRunId ?? null,
|
|
500
|
+
status: run.status,
|
|
501
|
+
createdAtMs: run.createdAtMs,
|
|
502
|
+
startedAtMs: run.startedAtMs ?? null,
|
|
503
|
+
finishedAtMs: run.finishedAtMs ?? null,
|
|
504
|
+
heartbeatAtMs: run.heartbeatAtMs ?? null,
|
|
505
|
+
activeNodeId: activeNode?.nodeId ?? null,
|
|
506
|
+
activeNodeLabel: activeNode?.label ?? activeNode?.nodeId ?? null,
|
|
507
|
+
pendingApprovalCount: approvals.length,
|
|
508
|
+
waitingTimers,
|
|
509
|
+
countsByState,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* @param {SmithersDb} adapter
|
|
514
|
+
* @param {string} runId
|
|
515
|
+
*/
|
|
516
|
+
async function buildRunDetail(adapter, runId) {
|
|
517
|
+
const run = await requireRun(adapter, runId);
|
|
518
|
+
const [summary, nodes, approvals, loops, ancestry] = await Promise.all([
|
|
519
|
+
buildRunSummary(adapter, run),
|
|
520
|
+
adapter.listNodes(runId),
|
|
521
|
+
adapter.listPendingApprovals(runId),
|
|
522
|
+
adapter.listRalph(runId),
|
|
523
|
+
adapter.listRunAncestry(runId, 1_000),
|
|
524
|
+
]);
|
|
525
|
+
let activeDescendantRunId = null;
|
|
526
|
+
const seen = new Set([runId]);
|
|
527
|
+
let cursor = runId;
|
|
528
|
+
while (true) {
|
|
529
|
+
const child = await adapter.getLatestChildRun(cursor);
|
|
530
|
+
if (!child || !child.runId || seen.has(child.runId))
|
|
531
|
+
break;
|
|
532
|
+
activeDescendantRunId = child.runId;
|
|
533
|
+
seen.add(child.runId);
|
|
534
|
+
cursor = child.runId;
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
...summary,
|
|
538
|
+
steps: nodes
|
|
539
|
+
.map((node) => ({
|
|
540
|
+
nodeId: node.nodeId,
|
|
541
|
+
iteration: node.iteration ?? 0,
|
|
542
|
+
state: node.state,
|
|
543
|
+
lastAttempt: node.lastAttempt ?? null,
|
|
544
|
+
updatedAtMs: node.updatedAtMs ?? null,
|
|
545
|
+
outputTable: node.outputTable ?? null,
|
|
546
|
+
label: node.label ?? null,
|
|
547
|
+
}))
|
|
548
|
+
.sort((left, right) => {
|
|
549
|
+
if (left.nodeId !== right.nodeId) {
|
|
550
|
+
return left.nodeId.localeCompare(right.nodeId);
|
|
551
|
+
}
|
|
552
|
+
return left.iteration - right.iteration;
|
|
553
|
+
}),
|
|
554
|
+
approvals: approvals.map((approval) => ({
|
|
555
|
+
runId: approval.runId,
|
|
556
|
+
nodeId: approval.nodeId,
|
|
557
|
+
iteration: approval.iteration ?? 0,
|
|
558
|
+
status: approval.status,
|
|
559
|
+
requestedAtMs: approval.requestedAtMs ?? null,
|
|
560
|
+
decidedAtMs: approval.decidedAtMs ?? null,
|
|
561
|
+
note: approval.note ?? null,
|
|
562
|
+
decidedBy: approval.decidedBy ?? null,
|
|
563
|
+
request: parseJsonValue(approval.requestJson),
|
|
564
|
+
decision: parseJsonValue(approval.decisionJson),
|
|
565
|
+
autoApproved: Boolean(approval.autoApproved),
|
|
566
|
+
})),
|
|
567
|
+
loops: loops.map((loop) => ({
|
|
568
|
+
loopId: loop.ralphId,
|
|
569
|
+
iteration: loop.iteration,
|
|
570
|
+
maxIterations: loop.maxIterations ?? null,
|
|
571
|
+
})),
|
|
572
|
+
continuedFromRunIds: ancestry.slice(1).map((row) => row.runId),
|
|
573
|
+
activeDescendantRunId,
|
|
574
|
+
config: parseJsonValue(run.configJson),
|
|
575
|
+
error: parseJsonValue(run.errorJson),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* @param {SmithersDb} adapter
|
|
580
|
+
* @param {string} runId
|
|
581
|
+
*/
|
|
582
|
+
async function requireRun(adapter, runId) {
|
|
583
|
+
const run = await adapter.getRun(runId);
|
|
584
|
+
if (!run) {
|
|
585
|
+
throw new SmithersError("RUN_NOT_FOUND", `Run not found: ${runId}`, {
|
|
586
|
+
runId,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
return run;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* @param {SmithersDb} adapter
|
|
593
|
+
* @param {string} runId
|
|
594
|
+
*/
|
|
595
|
+
async function listAllEvents(adapter, runId) {
|
|
596
|
+
const events = [];
|
|
597
|
+
let lastSeq = -1;
|
|
598
|
+
while (true) {
|
|
599
|
+
const batch = await adapter.listEvents(runId, lastSeq, 1_000);
|
|
600
|
+
if (batch.length === 0)
|
|
601
|
+
break;
|
|
602
|
+
events.push(...batch);
|
|
603
|
+
lastSeq = batch[batch.length - 1].seq;
|
|
604
|
+
if (batch.length < 1_000)
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
return events;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* @param {string} workflowId
|
|
611
|
+
* @param {string} cwd
|
|
612
|
+
*/
|
|
613
|
+
async function loadWorkflowById(workflowId, cwd) {
|
|
614
|
+
const discovered = resolveWorkflow(workflowId, cwd);
|
|
615
|
+
mdxPlugin();
|
|
616
|
+
const moduleUrl = pathToFileURL(resolve(discovered.entryFile)).href;
|
|
617
|
+
const mod = await import(moduleUrl);
|
|
618
|
+
if (!mod.default) {
|
|
619
|
+
throw new SmithersError("WORKFLOW_MISSING_DEFAULT", `Workflow ${workflowId} must export default`, { workflowId, entryFile: discovered.entryFile });
|
|
620
|
+
}
|
|
621
|
+
const workflow = mod.default;
|
|
622
|
+
ensureSmithersTables(workflow.db);
|
|
623
|
+
return {
|
|
624
|
+
workflow,
|
|
625
|
+
summary: {
|
|
626
|
+
id: discovered.id,
|
|
627
|
+
displayName: discovered.displayName,
|
|
628
|
+
entryFile: discovered.entryFile,
|
|
629
|
+
sourceType: discovered.sourceType,
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* @param {SmithersDb} adapter
|
|
635
|
+
* @param {string} runId
|
|
636
|
+
* @param {number} waitForStartMs
|
|
637
|
+
*/
|
|
638
|
+
async function waitForObservedRun(adapter, runId, waitForStartMs) {
|
|
639
|
+
const deadline = Date.now() + Math.max(0, waitForStartMs);
|
|
640
|
+
while (true) {
|
|
641
|
+
const run = await adapter.getRun(runId);
|
|
642
|
+
if (run) {
|
|
643
|
+
return buildRunSummary(adapter, run);
|
|
644
|
+
}
|
|
645
|
+
if (Date.now() >= deadline) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
await sleep(25);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* @param {unknown} error
|
|
653
|
+
*/
|
|
654
|
+
function toToolError(error) {
|
|
655
|
+
const smithersError = toSmithersError(error);
|
|
656
|
+
return {
|
|
657
|
+
code: smithersError.code,
|
|
658
|
+
message: smithersError.summary,
|
|
659
|
+
details: smithersError.details ?? null,
|
|
660
|
+
docsUrl: smithersError.docsUrl ?? null,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* @template Data
|
|
665
|
+
* @param {Data} data
|
|
666
|
+
*/
|
|
667
|
+
function toolSuccess(data) {
|
|
668
|
+
const payload = { ok: true, data };
|
|
669
|
+
return {
|
|
670
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
671
|
+
structuredContent: payload,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* @param {unknown} error
|
|
676
|
+
* @returns {SemanticToolCallResult}
|
|
677
|
+
*/
|
|
678
|
+
function toolFailure(error) {
|
|
679
|
+
const payload = { ok: false, error: toToolError(error) };
|
|
680
|
+
return {
|
|
681
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
682
|
+
structuredContent: payload,
|
|
683
|
+
isError: true,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* @template T
|
|
688
|
+
* @param {SemanticToolContext} context
|
|
689
|
+
* @param {(adapter: SmithersDb) => Promise<T>} run
|
|
690
|
+
*/
|
|
691
|
+
async function withDb(context, run) {
|
|
692
|
+
const { adapter, cleanup } = await context.openDb(context.cwd());
|
|
693
|
+
try {
|
|
694
|
+
return await run(adapter);
|
|
695
|
+
}
|
|
696
|
+
finally {
|
|
697
|
+
cleanup();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* @param {any} row
|
|
702
|
+
*/
|
|
703
|
+
function parsePendingApproval(row) {
|
|
704
|
+
return {
|
|
705
|
+
runId: row.runId,
|
|
706
|
+
nodeId: row.nodeId,
|
|
707
|
+
iteration: row.iteration ?? 0,
|
|
708
|
+
status: row.status,
|
|
709
|
+
requestedAtMs: row.requestedAtMs ?? null,
|
|
710
|
+
decidedAtMs: row.decidedAtMs ?? null,
|
|
711
|
+
note: row.note ?? null,
|
|
712
|
+
decidedBy: row.decidedBy ?? null,
|
|
713
|
+
request: parseJsonValue(row.requestJson),
|
|
714
|
+
decision: parseJsonValue(row.decisionJson),
|
|
715
|
+
autoApproved: typeof row.autoApproved === "boolean" ? row.autoApproved : undefined,
|
|
716
|
+
workflowName: row.workflowName ?? null,
|
|
717
|
+
runStatus: row.runStatus ?? null,
|
|
718
|
+
nodeLabel: row.nodeLabel ?? null,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* @param {any[]} approvals
|
|
723
|
+
* @param {{ runId?: string; workflowName?: string; nodeId?: string; iteration?: number; }} filters
|
|
724
|
+
*/
|
|
725
|
+
function filterPendingApprovals(approvals, filters) {
|
|
726
|
+
return approvals.filter((approval) => {
|
|
727
|
+
if (filters.runId && approval.runId !== filters.runId)
|
|
728
|
+
return false;
|
|
729
|
+
if (filters.workflowName && approval.workflowName !== filters.workflowName)
|
|
730
|
+
return false;
|
|
731
|
+
if (filters.nodeId && approval.nodeId !== filters.nodeId)
|
|
732
|
+
return false;
|
|
733
|
+
if (typeof filters.iteration === "number" &&
|
|
734
|
+
Number(approval.iteration ?? 0) !== filters.iteration) {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
return true;
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* @template Data
|
|
742
|
+
* @param {string} toolName
|
|
743
|
+
* @param {() => Promise<Data>} handler
|
|
744
|
+
* @returns {Promise<SemanticToolCallResult>}
|
|
745
|
+
*/
|
|
746
|
+
async function executeSemanticTool(toolName, handler) {
|
|
747
|
+
try {
|
|
748
|
+
const data = await runPromise(Effect.tryPromise(() => handler()).pipe(Effect.annotateLogs({
|
|
749
|
+
toolName,
|
|
750
|
+
surface: "semantic",
|
|
751
|
+
}), Effect.withLogSpan("mcp:semantic")));
|
|
752
|
+
return toolSuccess(data);
|
|
753
|
+
}
|
|
754
|
+
catch (error) {
|
|
755
|
+
return toolFailure(error);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* @param {Partial<SemanticToolContext>} [options]
|
|
760
|
+
* @returns {SemanticToolDefinition[]}
|
|
761
|
+
*/
|
|
762
|
+
export function createSemanticToolDefinitions(options = {}) {
|
|
763
|
+
const context = {
|
|
764
|
+
cwd: options.cwd ?? (() => process.cwd()),
|
|
765
|
+
openDb: options.openDb ?? findAndOpenDb,
|
|
766
|
+
};
|
|
767
|
+
return [
|
|
768
|
+
{
|
|
769
|
+
name: "list_workflows",
|
|
770
|
+
description: "List discovered local Smithers workflows.",
|
|
771
|
+
inputSchema: listWorkflowsInputSchema,
|
|
772
|
+
outputSchema: resultSchema(listWorkflowsDataSchema),
|
|
773
|
+
annotations: { readOnlyHint: true },
|
|
774
|
+
handler: () => executeSemanticTool("list_workflows", async () => ({
|
|
775
|
+
workflows: discoverWorkflows(context.cwd()),
|
|
776
|
+
})),
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
name: "run_workflow",
|
|
780
|
+
description: "Start a discovered workflow directly through the engine. Defaults to background launch; set waitForTerminal=true to block until completion.",
|
|
781
|
+
inputSchema: runWorkflowInputSchema,
|
|
782
|
+
outputSchema: resultSchema(runWorkflowDataSchema),
|
|
783
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
784
|
+
handler: (input) => executeSemanticTool("run_workflow", async () => {
|
|
785
|
+
const runId = input.runId ?? crypto.randomUUID();
|
|
786
|
+
const { workflow, summary } = await loadWorkflowById(input.workflowId, context.cwd());
|
|
787
|
+
const adapter = workflow.db
|
|
788
|
+
? new SmithersDb(workflow.db)
|
|
789
|
+
: null;
|
|
790
|
+
const workflowInput = input.input ??
|
|
791
|
+
(typeof input.prompt === "string" ? { prompt: input.prompt } : {});
|
|
792
|
+
const launchState = {
|
|
793
|
+
settled: false,
|
|
794
|
+
result: null,
|
|
795
|
+
error: null,
|
|
796
|
+
};
|
|
797
|
+
const launchPromise = Effect.runPromise(runWorkflow(workflow, {
|
|
798
|
+
input: workflowInput,
|
|
799
|
+
runId,
|
|
800
|
+
resume: input.resume,
|
|
801
|
+
force: input.force,
|
|
802
|
+
workflowPath: summary.entryFile,
|
|
803
|
+
maxConcurrency: input.maxConcurrency,
|
|
804
|
+
rootDir: input.rootDir,
|
|
805
|
+
logDir: input.logDir,
|
|
806
|
+
allowNetwork: input.allowNetwork,
|
|
807
|
+
maxOutputBytes: input.maxOutputBytes,
|
|
808
|
+
toolTimeoutMs: input.toolTimeoutMs,
|
|
809
|
+
hot: input.hot,
|
|
810
|
+
})).then((result) => {
|
|
811
|
+
launchState.settled = true;
|
|
812
|
+
launchState.result = result;
|
|
813
|
+
return result;
|
|
814
|
+
}, (error) => {
|
|
815
|
+
launchState.settled = true;
|
|
816
|
+
launchState.error = error;
|
|
817
|
+
throw error;
|
|
818
|
+
});
|
|
819
|
+
if (input.waitForTerminal) {
|
|
820
|
+
const result = await launchPromise;
|
|
821
|
+
const observedRun = adapter != null ? await adapter.getRun(result.runId) : null;
|
|
822
|
+
return {
|
|
823
|
+
workflow: summary,
|
|
824
|
+
runId: result.runId,
|
|
825
|
+
launchMode: "waited",
|
|
826
|
+
requestedResume: input.resume,
|
|
827
|
+
status: result.status,
|
|
828
|
+
observedRun: observedRun != null
|
|
829
|
+
? await buildRunSummary(adapter, observedRun)
|
|
830
|
+
: null,
|
|
831
|
+
result,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
void launchPromise.catch((error) => {
|
|
835
|
+
const rendered = toToolError(error);
|
|
836
|
+
console.error(`[smithers:mcp] run_workflow background failure ${runId}: ${rendered.code} ${rendered.message}`);
|
|
837
|
+
});
|
|
838
|
+
const observedRun = adapter != null
|
|
839
|
+
? await waitForObservedRun(adapter, runId, input.waitForStartMs)
|
|
840
|
+
: null;
|
|
841
|
+
if (observedRun == null && launchState.settled) {
|
|
842
|
+
if (launchState.error) {
|
|
843
|
+
throw launchState.error;
|
|
844
|
+
}
|
|
845
|
+
if (launchState.result) {
|
|
846
|
+
const finalRun = adapter != null
|
|
847
|
+
? await adapter.getRun(launchState.result.runId)
|
|
848
|
+
: null;
|
|
849
|
+
return {
|
|
850
|
+
workflow: summary,
|
|
851
|
+
runId: launchState.result.runId,
|
|
852
|
+
launchMode: "background",
|
|
853
|
+
requestedResume: input.resume,
|
|
854
|
+
status: launchState.result.status,
|
|
855
|
+
observedRun: finalRun != null
|
|
856
|
+
? await buildRunSummary(adapter, finalRun)
|
|
857
|
+
: null,
|
|
858
|
+
result: launchState.result,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
workflow: summary,
|
|
864
|
+
runId,
|
|
865
|
+
launchMode: "background",
|
|
866
|
+
requestedResume: input.resume,
|
|
867
|
+
status: observedRun?.status ?? "running",
|
|
868
|
+
observedRun,
|
|
869
|
+
result: null,
|
|
870
|
+
};
|
|
871
|
+
}),
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: "list_runs",
|
|
875
|
+
description: "List recent Smithers runs with stable structured summaries.",
|
|
876
|
+
inputSchema: listRunsInputSchema,
|
|
877
|
+
outputSchema: resultSchema(listRunsDataSchema),
|
|
878
|
+
annotations: { readOnlyHint: true },
|
|
879
|
+
handler: (input) => executeSemanticTool("list_runs", async () => withDb(context, async (adapter) => {
|
|
880
|
+
const runs = await adapter.listRuns(input.limit, input.status);
|
|
881
|
+
const summaries = await Promise.all(runs.map((run) => buildRunSummary(adapter, run)));
|
|
882
|
+
return { runs: summaries };
|
|
883
|
+
})),
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
name: "get_run",
|
|
887
|
+
description: "Get enriched structured state for a specific run, including steps, approvals, timers, lineage, and config.",
|
|
888
|
+
inputSchema: getRunInputSchema,
|
|
889
|
+
outputSchema: resultSchema(getRunDataSchema),
|
|
890
|
+
annotations: { readOnlyHint: true },
|
|
891
|
+
handler: (input) => executeSemanticTool("get_run", async () => withDb(context, async (adapter) => ({
|
|
892
|
+
run: await buildRunDetail(adapter, input.runId),
|
|
893
|
+
}))),
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: "watch_run",
|
|
897
|
+
description: "Poll a run until it reaches a terminal state or timeout. This is the explicit watch/poll semantic tool.",
|
|
898
|
+
inputSchema: watchRunInputSchema,
|
|
899
|
+
outputSchema: resultSchema(watchRunDataSchema),
|
|
900
|
+
annotations: { readOnlyHint: true },
|
|
901
|
+
handler: (input) => executeSemanticTool("watch_run", async () => withDb(context, async (adapter) => {
|
|
902
|
+
const intervalMs = Math.max(WATCH_MIN_INTERVAL_MS, input.intervalMs);
|
|
903
|
+
const deadline = Date.now() + input.timeoutMs;
|
|
904
|
+
const snapshots = [];
|
|
905
|
+
let pollCount = 0;
|
|
906
|
+
while (true) {
|
|
907
|
+
const run = await adapter.getRun(input.runId);
|
|
908
|
+
if (!run) {
|
|
909
|
+
throw new SmithersError("RUN_NOT_FOUND", `Run not found: ${input.runId}`, {
|
|
910
|
+
runId: input.runId,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
const summary = await buildRunSummary(adapter, run);
|
|
914
|
+
snapshots.push({
|
|
915
|
+
observedAtMs: Date.now(),
|
|
916
|
+
run: summary,
|
|
917
|
+
});
|
|
918
|
+
if (run.status !== "running" &&
|
|
919
|
+
run.status !== "waiting-approval" &&
|
|
920
|
+
run.status !== "waiting-event" &&
|
|
921
|
+
run.status !== "waiting-timer") {
|
|
922
|
+
return {
|
|
923
|
+
runId: input.runId,
|
|
924
|
+
intervalMs,
|
|
925
|
+
pollCount,
|
|
926
|
+
reachedTerminal: true,
|
|
927
|
+
timedOut: false,
|
|
928
|
+
finalRun: summary,
|
|
929
|
+
snapshots,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
if (Date.now() >= deadline) {
|
|
933
|
+
return {
|
|
934
|
+
runId: input.runId,
|
|
935
|
+
intervalMs,
|
|
936
|
+
pollCount,
|
|
937
|
+
reachedTerminal: false,
|
|
938
|
+
timedOut: true,
|
|
939
|
+
finalRun: summary,
|
|
940
|
+
snapshots,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
pollCount += 1;
|
|
944
|
+
await sleep(intervalMs);
|
|
945
|
+
}
|
|
946
|
+
})),
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
name: "explain_run",
|
|
950
|
+
description: "Explain why a run is waiting, stale, or blocked by returning the diagnosis model directly.",
|
|
951
|
+
inputSchema: explainRunInputSchema,
|
|
952
|
+
outputSchema: resultSchema(explainRunDataSchema),
|
|
953
|
+
annotations: { readOnlyHint: true },
|
|
954
|
+
handler: (input) => executeSemanticTool("explain_run", async () => withDb(context, async (adapter) => ({
|
|
955
|
+
diagnosis: await runPromise(diagnoseRunEffect(adapter, input.runId)),
|
|
956
|
+
}))),
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
name: "list_pending_approvals",
|
|
960
|
+
description: "List pending approvals across all runs or a filtered subset.",
|
|
961
|
+
inputSchema: listPendingApprovalsInputSchema,
|
|
962
|
+
outputSchema: resultSchema(listPendingApprovalsDataSchema),
|
|
963
|
+
annotations: { readOnlyHint: true },
|
|
964
|
+
handler: (input) => executeSemanticTool("list_pending_approvals", async () => withDb(context, async (adapter) => {
|
|
965
|
+
const approvals = await adapter.listAllPendingApprovals();
|
|
966
|
+
return {
|
|
967
|
+
approvals: filterPendingApprovals(approvals, input).map(parsePendingApproval),
|
|
968
|
+
};
|
|
969
|
+
})),
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
name: "resolve_approval",
|
|
973
|
+
description: "Destructive: approve or deny a pending approval. If filters match more than one approval, this tool returns an ambiguity error instead of guessing.",
|
|
974
|
+
inputSchema: resolveApprovalInputSchema,
|
|
975
|
+
outputSchema: resultSchema(resolveApprovalDataSchema),
|
|
976
|
+
annotations: {
|
|
977
|
+
readOnlyHint: false,
|
|
978
|
+
destructiveHint: true,
|
|
979
|
+
idempotentHint: false,
|
|
980
|
+
},
|
|
981
|
+
handler: (input) => executeSemanticTool("resolve_approval", async () => withDb(context, async (adapter) => {
|
|
982
|
+
const approvals = await adapter.listAllPendingApprovals();
|
|
983
|
+
const matches = filterPendingApprovals(approvals, input);
|
|
984
|
+
if (matches.length === 0) {
|
|
985
|
+
throw new SmithersError("INVALID_INPUT", "No pending approval matched the provided filters.", {
|
|
986
|
+
filters: input,
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
if (matches.length > 1) {
|
|
990
|
+
throw new SmithersError("INVALID_INPUT", "Multiple pending approvals matched. Provide runId/nodeId/iteration to disambiguate.", {
|
|
991
|
+
matches: matches.map((approval) => ({
|
|
992
|
+
runId: approval.runId,
|
|
993
|
+
nodeId: approval.nodeId,
|
|
994
|
+
iteration: approval.iteration ?? 0,
|
|
995
|
+
workflowName: approval.workflowName ?? null,
|
|
996
|
+
})),
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const approval = matches[0];
|
|
1000
|
+
if (input.action === "approve") {
|
|
1001
|
+
await Effect.runPromise(approveNode(adapter, approval.runId, approval.nodeId, approval.iteration ?? 0, input.note, input.decidedBy, input.decision));
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
await Effect.runPromise(denyNode(adapter, approval.runId, approval.nodeId, approval.iteration ?? 0, input.note, input.decidedBy, input.decision));
|
|
1005
|
+
}
|
|
1006
|
+
const run = await adapter.getRun(approval.runId);
|
|
1007
|
+
return {
|
|
1008
|
+
action: input.action,
|
|
1009
|
+
approval: {
|
|
1010
|
+
...parsePendingApproval(approval),
|
|
1011
|
+
status: input.action === "approve" ? "approved" : "denied",
|
|
1012
|
+
requestedAtMs: approval.requestedAtMs ?? null,
|
|
1013
|
+
decidedAtMs: Date.now(),
|
|
1014
|
+
note: input.note ?? null,
|
|
1015
|
+
decidedBy: input.decidedBy ?? null,
|
|
1016
|
+
decision: input.decision ?? null,
|
|
1017
|
+
},
|
|
1018
|
+
run: run != null
|
|
1019
|
+
? await buildRunSummary(adapter, run)
|
|
1020
|
+
: null,
|
|
1021
|
+
};
|
|
1022
|
+
})),
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "get_node_detail",
|
|
1026
|
+
description: "Get enriched node state including attempts, tool calls, token usage, scorers, and validated output.",
|
|
1027
|
+
inputSchema: getNodeDetailInputSchema,
|
|
1028
|
+
outputSchema: resultSchema(getNodeDetailDataSchema),
|
|
1029
|
+
annotations: { readOnlyHint: true },
|
|
1030
|
+
handler: (input) => executeSemanticTool("get_node_detail", async () => withDb(context, async (adapter) => ({
|
|
1031
|
+
detail: await runPromise(aggregateNodeDetailEffect(adapter, {
|
|
1032
|
+
runId: input.runId,
|
|
1033
|
+
nodeId: input.nodeId,
|
|
1034
|
+
iteration: input.iteration,
|
|
1035
|
+
})),
|
|
1036
|
+
}))),
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
name: "revert_attempt",
|
|
1040
|
+
description: "Destructive: revert the workspace and frame history back to a recorded attempt.",
|
|
1041
|
+
inputSchema: revertAttemptInputSchema,
|
|
1042
|
+
outputSchema: resultSchema(revertAttemptDataSchema),
|
|
1043
|
+
annotations: {
|
|
1044
|
+
readOnlyHint: false,
|
|
1045
|
+
destructiveHint: true,
|
|
1046
|
+
idempotentHint: false,
|
|
1047
|
+
},
|
|
1048
|
+
handler: (input) => executeSemanticTool("revert_attempt", async () => withDb(context, async (adapter) => {
|
|
1049
|
+
const result = await revertToAttempt(adapter, input);
|
|
1050
|
+
const run = await adapter.getRun(input.runId);
|
|
1051
|
+
return {
|
|
1052
|
+
runId: input.runId,
|
|
1053
|
+
nodeId: input.nodeId,
|
|
1054
|
+
iteration: input.iteration,
|
|
1055
|
+
attempt: input.attempt,
|
|
1056
|
+
success: result.success,
|
|
1057
|
+
...(result.error ? { error: result.error } : {}),
|
|
1058
|
+
...(result.jjPointer ? { jjPointer: result.jjPointer } : {}),
|
|
1059
|
+
run: run != null
|
|
1060
|
+
? await buildRunSummary(adapter, run)
|
|
1061
|
+
: null,
|
|
1062
|
+
};
|
|
1063
|
+
})),
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "list_artifacts",
|
|
1067
|
+
description: "List structured output artifacts produced by nodes in a run.",
|
|
1068
|
+
inputSchema: listArtifactsInputSchema,
|
|
1069
|
+
outputSchema: resultSchema(listArtifactsDataSchema),
|
|
1070
|
+
annotations: { readOnlyHint: true },
|
|
1071
|
+
handler: (input) => executeSemanticTool("list_artifacts", async () => withDb(context, async (adapter) => {
|
|
1072
|
+
await requireRun(adapter, input.runId);
|
|
1073
|
+
const nodes = await adapter.listNodes(input.runId);
|
|
1074
|
+
const selectedNodes = nodes.filter((node) => {
|
|
1075
|
+
if (input.nodeId && node.nodeId !== input.nodeId)
|
|
1076
|
+
return false;
|
|
1077
|
+
return Boolean(node.outputTable);
|
|
1078
|
+
});
|
|
1079
|
+
const artifacts = [];
|
|
1080
|
+
for (const node of selectedNodes) {
|
|
1081
|
+
const detail = await runPromise(aggregateNodeDetailEffect(adapter, {
|
|
1082
|
+
runId: input.runId,
|
|
1083
|
+
nodeId: node.nodeId,
|
|
1084
|
+
iteration: node.iteration ?? 0,
|
|
1085
|
+
}));
|
|
1086
|
+
if (detail.output.source === "none")
|
|
1087
|
+
continue;
|
|
1088
|
+
artifacts.push({
|
|
1089
|
+
artifactId: `${input.runId}:${node.nodeId}:${node.iteration ?? 0}`,
|
|
1090
|
+
kind: "node-output",
|
|
1091
|
+
runId: input.runId,
|
|
1092
|
+
nodeId: node.nodeId,
|
|
1093
|
+
iteration: node.iteration ?? 0,
|
|
1094
|
+
label: node.label ?? null,
|
|
1095
|
+
state: node.state,
|
|
1096
|
+
outputTable: node.outputTable ?? null,
|
|
1097
|
+
source: detail.output.source,
|
|
1098
|
+
cacheKey: detail.output.cacheKey,
|
|
1099
|
+
value: detail.output.validated,
|
|
1100
|
+
...(input.includeRaw ? { rawValue: detail.output.raw } : {}),
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return { artifacts };
|
|
1104
|
+
})),
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: "get_chat_transcript",
|
|
1108
|
+
description: "Return the structured agent chat transcript for a run, grouped by attempts and message role.",
|
|
1109
|
+
inputSchema: getChatTranscriptInputSchema,
|
|
1110
|
+
outputSchema: resultSchema(getChatTranscriptDataSchema),
|
|
1111
|
+
annotations: { readOnlyHint: true },
|
|
1112
|
+
handler: (input) => executeSemanticTool("get_chat_transcript", async () => withDb(context, async (adapter) => {
|
|
1113
|
+
await requireRun(adapter, input.runId);
|
|
1114
|
+
const attempts = await adapter.listAttemptsForRun(input.runId);
|
|
1115
|
+
const events = await listAllEvents(adapter, input.runId);
|
|
1116
|
+
const knownOutputAttemptKeys = new Set();
|
|
1117
|
+
const parsedOutputs = events
|
|
1118
|
+
.map((event) => parseNodeOutputEvent(event))
|
|
1119
|
+
.filter(Boolean);
|
|
1120
|
+
for (const event of parsedOutputs) {
|
|
1121
|
+
knownOutputAttemptKeys.add(chatAttemptKey(event));
|
|
1122
|
+
}
|
|
1123
|
+
const selectedAttempts = selectChatAttempts(attempts, knownOutputAttemptKeys, input.all);
|
|
1124
|
+
const selectedAttemptKeys = new Set(selectedAttempts.map((attempt) => chatAttemptKey(attempt)));
|
|
1125
|
+
const stdoutSeenAttempts = new Set();
|
|
1126
|
+
const messages = [];
|
|
1127
|
+
for (const attempt of selectedAttempts) {
|
|
1128
|
+
const attemptKey = chatAttemptKey(attempt);
|
|
1129
|
+
const meta = parseChatAttemptMeta(attempt.metaJson);
|
|
1130
|
+
const prompt = typeof meta.prompt === "string" ? meta.prompt.trim() : "";
|
|
1131
|
+
if (prompt) {
|
|
1132
|
+
messages.push({
|
|
1133
|
+
id: `prompt:${attemptKey}`,
|
|
1134
|
+
attemptKey,
|
|
1135
|
+
nodeId: attempt.nodeId,
|
|
1136
|
+
iteration: attempt.iteration ?? 0,
|
|
1137
|
+
attempt: attempt.attempt,
|
|
1138
|
+
role: "user",
|
|
1139
|
+
stream: null,
|
|
1140
|
+
timestampMs: attempt.startedAtMs,
|
|
1141
|
+
text: prompt,
|
|
1142
|
+
source: "prompt",
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
for (const event of parsedOutputs) {
|
|
1147
|
+
const attemptKey = chatAttemptKey(event);
|
|
1148
|
+
if (!selectedAttemptKeys.has(attemptKey))
|
|
1149
|
+
continue;
|
|
1150
|
+
if (event.stream === "stderr" && !input.includeStderr)
|
|
1151
|
+
continue;
|
|
1152
|
+
if (event.stream === "stdout") {
|
|
1153
|
+
stdoutSeenAttempts.add(attemptKey);
|
|
1154
|
+
}
|
|
1155
|
+
messages.push({
|
|
1156
|
+
id: `event:${event.seq}`,
|
|
1157
|
+
attemptKey,
|
|
1158
|
+
nodeId: event.nodeId,
|
|
1159
|
+
iteration: event.iteration,
|
|
1160
|
+
attempt: event.attempt,
|
|
1161
|
+
role: event.stream === "stderr" ? "stderr" : "assistant",
|
|
1162
|
+
stream: event.stream,
|
|
1163
|
+
timestampMs: event.timestampMs,
|
|
1164
|
+
text: event.text,
|
|
1165
|
+
source: "event",
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
for (const attempt of selectedAttempts) {
|
|
1169
|
+
const attemptKey = chatAttemptKey(attempt);
|
|
1170
|
+
const responseText = typeof attempt.responseText === "string"
|
|
1171
|
+
? attempt.responseText.trim()
|
|
1172
|
+
: "";
|
|
1173
|
+
if (!responseText || stdoutSeenAttempts.has(attemptKey))
|
|
1174
|
+
continue;
|
|
1175
|
+
messages.push({
|
|
1176
|
+
id: `response:${attemptKey}`,
|
|
1177
|
+
attemptKey,
|
|
1178
|
+
nodeId: attempt.nodeId,
|
|
1179
|
+
iteration: attempt.iteration ?? 0,
|
|
1180
|
+
attempt: attempt.attempt,
|
|
1181
|
+
role: "assistant",
|
|
1182
|
+
stream: null,
|
|
1183
|
+
timestampMs: attempt.finishedAtMs ?? attempt.startedAtMs ?? Date.now(),
|
|
1184
|
+
text: responseText,
|
|
1185
|
+
source: "responseText",
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
messages.sort((left, right) => {
|
|
1189
|
+
if (left.timestampMs !== right.timestampMs) {
|
|
1190
|
+
return left.timestampMs - right.timestampMs;
|
|
1191
|
+
}
|
|
1192
|
+
return left.id.localeCompare(right.id);
|
|
1193
|
+
});
|
|
1194
|
+
const tailedMessages = typeof input.tail === "number"
|
|
1195
|
+
? messages.slice(-input.tail)
|
|
1196
|
+
: messages;
|
|
1197
|
+
return {
|
|
1198
|
+
runId: input.runId,
|
|
1199
|
+
attempts: selectedAttempts.map((attempt) => ({
|
|
1200
|
+
attemptKey: chatAttemptKey(attempt),
|
|
1201
|
+
nodeId: attempt.nodeId,
|
|
1202
|
+
iteration: attempt.iteration ?? 0,
|
|
1203
|
+
attempt: attempt.attempt,
|
|
1204
|
+
state: attempt.state,
|
|
1205
|
+
startedAtMs: attempt.startedAtMs,
|
|
1206
|
+
finishedAtMs: attempt.finishedAtMs ?? null,
|
|
1207
|
+
cached: Boolean(attempt.cached),
|
|
1208
|
+
meta: parseJsonValue(attempt.metaJson),
|
|
1209
|
+
})),
|
|
1210
|
+
messages: tailedMessages,
|
|
1211
|
+
};
|
|
1212
|
+
})),
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
name: "get_run_events",
|
|
1216
|
+
description: "Return structured event history for a run without relying on CLI table or NDJSON formatting.",
|
|
1217
|
+
inputSchema: getRunEventsInputSchema,
|
|
1218
|
+
outputSchema: resultSchema(getRunEventsDataSchema),
|
|
1219
|
+
annotations: { readOnlyHint: true },
|
|
1220
|
+
handler: (input) => executeSemanticTool("get_run_events", async () => withDb(context, async (adapter) => {
|
|
1221
|
+
await requireRun(adapter, input.runId);
|
|
1222
|
+
const events = await adapter.listEventHistory(input.runId, {
|
|
1223
|
+
afterSeq: input.afterSeq,
|
|
1224
|
+
limit: input.limit,
|
|
1225
|
+
nodeId: input.nodeId,
|
|
1226
|
+
types: input.types,
|
|
1227
|
+
sinceTimestampMs: input.sinceTimestampMs,
|
|
1228
|
+
});
|
|
1229
|
+
return {
|
|
1230
|
+
runId: input.runId,
|
|
1231
|
+
events: events.map((event) => ({
|
|
1232
|
+
runId: event.runId,
|
|
1233
|
+
seq: event.seq,
|
|
1234
|
+
timestampMs: event.timestampMs,
|
|
1235
|
+
type: event.type,
|
|
1236
|
+
payload: parseJsonValue(event.payloadJson),
|
|
1237
|
+
})),
|
|
1238
|
+
};
|
|
1239
|
+
})),
|
|
1240
|
+
},
|
|
1241
|
+
];
|
|
1242
|
+
}
|