@axhub/genie 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/api-docs.html +2 -2
  2. package/dist/assets/App-Clb2COtW.js +274 -0
  3. package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
  4. package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
  5. package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
  6. package/dist/assets/{_basePickBy-BDnj7-0Z.js → _basePickBy-jUZsM51q.js} +1 -1
  7. package/dist/assets/{_baseUniq-Bl0JKOyl.js → _baseUniq-BXglE6_v.js} +1 -1
  8. package/dist/assets/{arc-DY-4Kev3.js → arc-D-oFCFBv.js} +1 -1
  9. package/dist/assets/{architectureDiagram-2XIMDMQ5-qw7crNVd.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-B9xg7ep3.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
  11. package/dist/assets/{c4Diagram-IC4MRINW-H9xp3ytb.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
  12. package/dist/assets/channel-BF4woPXX.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-B3EVDUxI.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-CGv945ef.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-uAT4CKWM.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-Cbvlpkf7.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-CcqIuGat.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
  18. package/dist/assets/{chunk-NQ4KR5QH-CgrcsRuX.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
  19. package/dist/assets/{chunk-QZHKN3VN-Cx0APOoV.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
  20. package/dist/assets/{chunk-WL4C6EOR-BbZirvBk.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
  21. package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
  23. package/dist/assets/clone-CqBvwCJW.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-CrvmGFLD.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
  25. package/dist/assets/{dagre-KLK3FWXG-C-W6VPjS.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
  26. package/dist/assets/{diagram-E7M64L7V-IP2q3bL0.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
  27. package/dist/assets/{diagram-IFDJBPK2-CQaL-XyV.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
  28. package/dist/assets/{diagram-P4PSJMXO-BxBLThfv.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
  29. package/dist/assets/{erDiagram-INFDFZHY-Dyl7bJTt.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
  30. package/dist/assets/{flowDiagram-PKNHOUZH-B7NFMgFK.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
  31. package/dist/assets/{ganttDiagram-A5KZAMGK-hReWSDu2.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-gVgcr0ST.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
  33. package/dist/assets/{graph-DNDiJhTn.js → graph-Dkeg9oys.js} +1 -1
  34. package/dist/assets/{highlighted-body-TPN3WLV5-DclLmTou.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
  35. package/dist/assets/index-DgGmiqsP.css +1 -0
  36. package/dist/assets/index-DvA901Vs.js +2 -0
  37. package/dist/assets/{infoDiagram-LFFYTUFH-CqQOOzDA.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-PHBUUO56-CZ0iLiHg.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
  39. package/dist/assets/{journeyDiagram-4ABVD52K-DdfYKfNh.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
  40. package/dist/assets/{kanban-definition-K7BYSVSG-C5Vf32u6.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
  41. package/dist/assets/{layout-rvTEu2KS.js → layout-Xr9Z2VGF.js} +1 -1
  42. package/dist/assets/{linear-CD9SiYze.js → linear-JBmzAJtl.js} +1 -1
  43. package/dist/assets/{mermaid-O7DHMXV3-OZ8qWWwa.js → mermaid-O7DHMXV3-fDuyNLKe.js} +230 -222
  44. package/dist/assets/{mindmap-definition-YRQLILUH-CQxrLNVc.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
  45. package/dist/assets/{pieDiagram-SKSYHLDU-XgAUByWg.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-337W2JSQ-CH16ls7G.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
  47. package/dist/assets/{requirementDiagram-Z7DCOOCP-B_kQO06L.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-WA2Y5GQK-ofe78CyS.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-2WXFIKYE-Ckbxwny6.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
  50. package/dist/assets/{stateDiagram-RAJIS63D-DNtzCk14.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
  51. package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
  52. package/dist/assets/{timeline-definition-YZTLITO2-zT6CklKt.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
  53. package/dist/assets/{treemap-KZPCXAKY-y0U2c3xG.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
  54. package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
  55. package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
  56. package/dist/assets/{vennDiagram-LZ73GAT5-xKj3SjYG.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
  57. package/dist/assets/{xychartDiagram-JWTSCODW-Da_qyEoX.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
  58. package/dist/index.html +4 -4
  59. package/package.json +25 -6
  60. package/scripts/refresh-acp-default-capabilities.mjs +160 -0
  61. package/server/acp-runtime/client.js +1137 -181
  62. package/server/acp-runtime/command-overrides.js +48 -0
  63. package/server/acp-runtime/index.js +576 -16
  64. package/server/acp-runtime/registry.js +6 -4
  65. package/server/acp-runtime/session-store.js +235 -92
  66. package/server/database/db.js +12 -3
  67. package/server/external-agent/ws.js +212 -11
  68. package/server/index.js +145 -52
  69. package/server/projects-watcher-config.js +4 -0
  70. package/server/projects.js +466 -125
  71. package/server/routes/cc-connect.js +5 -4
  72. package/server/routes/codex.js +24 -0
  73. package/server/routes/commands.js +144 -1
  74. package/server/routes/runs.js +641 -0
  75. package/server/routes/session-core.js +357 -109
  76. package/server/session-core/eventStore.js +0 -121
  77. package/server/session-core/providerAdapters.js +644 -163
  78. package/server/session-core/providerDiscovery.js +66 -38
  79. package/server/session-core/runRegistry.js +244 -0
  80. package/server/session-core/runtimeState.js +75 -3
  81. package/server/session-core/runtimeWriter.js +132 -10
  82. package/server/utils/codexImagePlayground.js +479 -0
  83. package/server/utils/localTerminal.js +56 -0
  84. package/server/utils/shellCommand.js +70 -0
  85. package/shared/acpCapabilities.js +393 -0
  86. package/shared/acpDefaultCapabilities.generated.json +141 -0
  87. package/shared/conversationEvents.js +425 -121
  88. package/dist/assets/App-VH1wNUHs.js +0 -259
  89. package/dist/assets/ReviewApp-D_9EN4TM.js +0 -1
  90. package/dist/assets/channel-CyNUnRfc.js +0 -1
  91. package/dist/assets/classDiagram-VBA2DB6C-DxBtyz2A.js +0 -1
  92. package/dist/assets/classDiagram-v2-RAHNMMFH-DxBtyz2A.js +0 -1
  93. package/dist/assets/clone-C341l3d0.js +0 -1
  94. package/dist/assets/index-DBkz_W_P.css +0 -1
  95. package/dist/assets/index-DdRyoXKh.js +0 -2
  96. package/dist/assets/stateDiagram-v2-FVOUBMTO-B3VPhiE1.js +0 -1
@@ -0,0 +1,641 @@
1
+ import express from 'express';
2
+
3
+ import { executeAgentPrompt } from '../acp-runtime/index.js';
4
+ import { resolveAgentElicitation, resolveAgentPermission } from '../acp-runtime/index.js';
5
+ import { mergeAgentCommandOverrides } from '../acp-runtime/command-overrides.js';
6
+ import { abortAgentSession } from '../session-core/abortSession.js';
7
+ import { runRegistry as defaultRunRegistry } from '../session-core/runRegistry.js';
8
+ import { SessionEventMirrorWriter } from '../session-core/runtimeWriter.js';
9
+ import {
10
+ CONVERSATION_EVENT_KINDS,
11
+ createConversationEvent,
12
+ } from '../../shared/conversationEvents.js';
13
+
14
+ const AGENT_COMMAND_TYPES_BY_PROVIDER = new Map([
15
+ ['claude', 'claude-command'],
16
+ ['codex', 'codex-command'],
17
+ ['gemini', 'gemini-command'],
18
+ ['opencode', 'opencode-command'],
19
+ ]);
20
+
21
+ const TERMINAL_STATUSES = new Set(['succeeded', 'failed', 'canceled']);
22
+ const DEFAULT_RUN_INACTIVITY_TIMEOUT_MS = 600_000;
23
+
24
+ class NullRunWriter {
25
+ constructor() {
26
+ this.sessionId = null;
27
+ }
28
+
29
+ send() {}
30
+
31
+ setSessionId(sessionId) {
32
+ this.sessionId = sessionId;
33
+ }
34
+
35
+ getSessionId() {
36
+ return this.sessionId;
37
+ }
38
+ }
39
+
40
+ function sendRunNotFound(res, runId) {
41
+ return res.status(404).json({
42
+ success: false,
43
+ error: 'Run not found or expired',
44
+ runId,
45
+ });
46
+ }
47
+
48
+ function parseAfterSeq(value) {
49
+ const parsed = Number(value);
50
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
51
+ }
52
+
53
+ function normalizeProvider(provider) {
54
+ const normalized = String(provider || 'claude').trim().toLowerCase();
55
+ return AGENT_COMMAND_TYPES_BY_PROVIDER.has(normalized) ? normalized : 'claude';
56
+ }
57
+
58
+ function normalizeNullableString(value) {
59
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
60
+ }
61
+
62
+ function normalizePositiveInteger(value, fallback) {
63
+ const parsed = Number(value);
64
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
65
+ }
66
+
67
+ function createRunInactivityTimeoutError({ timeoutMs, provider, sessionId, clientRequestId, run }) {
68
+ const error = new Error(`Run stalled without activity for ${timeoutMs}ms`);
69
+ error.code = 'RUN_INACTIVITY_TIMEOUT';
70
+ error.details = {
71
+ timeoutMs,
72
+ provider,
73
+ sessionId: run?.sessionId || sessionId || null,
74
+ clientRequestId: clientRequestId || null,
75
+ lastEventSeq: run?.lastEventSeq || 0,
76
+ lastActivityAt: run?.updatedAt || null,
77
+ };
78
+ return error;
79
+ }
80
+
81
+ function createRunCanceledError({ provider, sessionId, clientRequestId, run }) {
82
+ const error = new Error('Run was canceled');
83
+ error.code = 'RUN_CANCELED';
84
+ error.details = {
85
+ provider,
86
+ sessionId: run?.sessionId || sessionId || null,
87
+ clientRequestId: clientRequestId || null,
88
+ lastEventSeq: run?.lastEventSeq || 0,
89
+ lastActivityAt: run?.updatedAt || null,
90
+ };
91
+ return error;
92
+ }
93
+
94
+ function withRunActivityWatchdog(promise, {
95
+ runRegistry,
96
+ runId,
97
+ provider,
98
+ sessionId = null,
99
+ clientRequestId = null,
100
+ timeoutMs,
101
+ }) {
102
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
103
+ return promise;
104
+ }
105
+
106
+ let timeoutId = null;
107
+ let unsubscribe = null;
108
+ let settled = false;
109
+ const clearTimer = () => {
110
+ if (timeoutId) {
111
+ clearTimeout(timeoutId);
112
+ timeoutId = null;
113
+ }
114
+ };
115
+ const timeoutPromise = new Promise((_, reject) => {
116
+ const schedule = () => {
117
+ clearTimer();
118
+ if (settled) {
119
+ return;
120
+ }
121
+ const latestRun = runRegistry.getRun(runId);
122
+ if (latestRun?.status === 'canceled') {
123
+ reject(createRunCanceledError({
124
+ provider,
125
+ sessionId,
126
+ clientRequestId,
127
+ run: latestRun,
128
+ }));
129
+ return;
130
+ }
131
+ if (latestRun?.status === 'awaiting_approval' || latestRun?.status === 'awaiting_input') {
132
+ return;
133
+ }
134
+ timeoutId = setTimeout(() => {
135
+ reject(createRunInactivityTimeoutError({
136
+ timeoutMs,
137
+ provider,
138
+ sessionId,
139
+ clientRequestId,
140
+ run: runRegistry.getRun(runId),
141
+ }));
142
+ }, timeoutMs);
143
+ };
144
+ unsubscribe = runRegistry.subscribeRunEvents(runId, () => {
145
+ queueMicrotask(schedule);
146
+ });
147
+ schedule();
148
+ });
149
+
150
+ return Promise.race([promise, timeoutPromise]).finally(() => {
151
+ settled = true;
152
+ clearTimer();
153
+ unsubscribe?.();
154
+ });
155
+ }
156
+
157
+ function shouldEmitTerminalRunState(currentStatus, nextStatus) {
158
+ const current = String(currentStatus || '').trim().toLowerCase();
159
+ const next = String(nextStatus || '').trim().toLowerCase();
160
+ if (!TERMINAL_STATUSES.has(current)) {
161
+ return true;
162
+ }
163
+ return current === next;
164
+ }
165
+
166
+ function sendBadRequest(res, message) {
167
+ return res.status(400).json({
168
+ success: false,
169
+ error: message,
170
+ });
171
+ }
172
+
173
+ function emitRunState({ runRegistry, writer = null, runId, provider, sessionId = null, clientRequestId = null, status, event = {} }) {
174
+ const updatedRun = runRegistry.updateRun(runId, {
175
+ status,
176
+ sessionId,
177
+ clientRequestId,
178
+ }) || runRegistry.getRun(runId);
179
+ const runEvent = runRegistry.appendRunEvent(runId, {
180
+ type: 'run-state',
181
+ provider,
182
+ sessionId: updatedRun?.sessionId || sessionId,
183
+ clientRequestId,
184
+ event: {
185
+ status,
186
+ ...event,
187
+ },
188
+ });
189
+
190
+ writer?.send?.({
191
+ type: 'run-state',
192
+ provider,
193
+ sessionId: updatedRun?.sessionId || sessionId,
194
+ clientRequestId,
195
+ runId,
196
+ runEventSeq: runEvent?.seq || updatedRun?.lastEventSeq || null,
197
+ run: updatedRun,
198
+ event: {
199
+ status,
200
+ ...event,
201
+ },
202
+ });
203
+
204
+ return {
205
+ run: updatedRun,
206
+ event: runEvent,
207
+ };
208
+ }
209
+
210
+ function appendSubmittedPromptRunEvent({
211
+ writer,
212
+ provider,
213
+ sessionId = null,
214
+ clientRequestId = null,
215
+ command = '',
216
+ options = {},
217
+ }) {
218
+ const promptText = String(command || '');
219
+ if (!promptText.trim()) {
220
+ return;
221
+ }
222
+
223
+ const contentBlocks = [
224
+ ...(Array.isArray(options.images)
225
+ ? options.images.map((image, index) => ({
226
+ type: 'image',
227
+ data: image?.data || image?.image || '',
228
+ mimeType: image?.mimeType || 'application/octet-stream',
229
+ name: image?.name || image?.filename || `image-${index + 1}`,
230
+ }))
231
+ : []),
232
+ ...(Array.isArray(options.resourceLinks) ? options.resourceLinks : []),
233
+ ];
234
+
235
+ writer?.send?.({
236
+ type: 'conversation-event',
237
+ provider,
238
+ sessionId,
239
+ clientRequestId,
240
+ event: createConversationEvent({
241
+ kind: CONVERSATION_EVENT_KINDS.USER_MESSAGE,
242
+ provider,
243
+ sessionId,
244
+ payload: {
245
+ text: promptText,
246
+ contentBlocks,
247
+ },
248
+ extensions: clientRequestId ? { clientRequestId } : {},
249
+ rawRef: {
250
+ runtime: 'acp',
251
+ sourceType: 'run-submitted-prompt',
252
+ },
253
+ }),
254
+ });
255
+ }
256
+
257
+ function writeSseFrame(res, eventName, payload = {}) {
258
+ res.write(`event: ${eventName}\n`);
259
+ res.write(`data: ${JSON.stringify(payload)}\n\n`);
260
+ }
261
+
262
+ function eventNameForRunEvent(runEvent) {
263
+ if (runEvent?.type === 'run-state') return 'run-state';
264
+ return 'conversation-event';
265
+ }
266
+
267
+ export function createRunsRouter({
268
+ runRegistry = defaultRunRegistry,
269
+ executeRun = async ({ agentKey, command, options, writer }) => executeAgentPrompt({
270
+ agentKey,
271
+ command,
272
+ options,
273
+ writer,
274
+ }),
275
+ cancelRun = async ({ provider, sessionId }) => abortAgentSession(provider, sessionId),
276
+ resolvePermission = resolveAgentPermission,
277
+ resolveElicitation = resolveAgentElicitation,
278
+ env = process.env,
279
+ runInactivityTimeoutMs = normalizePositiveInteger(
280
+ env?.AXHUB_GENIE_RUN_INACTIVITY_TIMEOUT_MS || env?.AXHUB_GENIE_RUN_TIMEOUT_MS || env?.ACP_PROMPT_TIMEOUT_MS || env?.AXHUB_GENIE_ACP_PROMPT_TIMEOUT_MS,
281
+ DEFAULT_RUN_INACTIVITY_TIMEOUT_MS,
282
+ ),
283
+ runTimeoutMs,
284
+ } = {}) {
285
+ const effectiveRunInactivityTimeoutMs = normalizePositiveInteger(runInactivityTimeoutMs ?? runTimeoutMs, DEFAULT_RUN_INACTIVITY_TIMEOUT_MS);
286
+ const router = express.Router();
287
+
288
+ router.post('/runs', (req, res) => {
289
+ const provider = normalizeProvider(req.body?.provider || req.body?.agentKey);
290
+ const commandType = AGENT_COMMAND_TYPES_BY_PROVIDER.get(provider);
291
+ const command = typeof req.body?.command === 'string'
292
+ ? req.body.command
293
+ : typeof req.body?.message === 'string'
294
+ ? req.body.message
295
+ : '';
296
+
297
+ if (!command.trim()) {
298
+ return sendBadRequest(res, 'command must be a non-empty string');
299
+ }
300
+
301
+ const clientRequestId = normalizeNullableString(req.body?.clientRequestId || req.body?.options?.clientRequestId);
302
+ const sessionId = normalizeNullableString(req.body?.sessionId || req.body?.options?.sessionId);
303
+ const run = runRegistry.createRun({
304
+ provider,
305
+ sessionId,
306
+ clientRequestId,
307
+ });
308
+ const baseWriter = new NullRunWriter();
309
+ const writer = new SessionEventMirrorWriter(baseWriter, provider, {
310
+ runRegistry,
311
+ runId: run.runId,
312
+ clientRequestId,
313
+ });
314
+ const requestOptions = req.body?.options && typeof req.body.options === 'object'
315
+ ? req.body.options
316
+ : {};
317
+ const agentCommandOverrides = mergeAgentCommandOverrides(
318
+ env?.AXHUB_ACP_COMMAND_OVERRIDES,
319
+ requestOptions.agentCommandOverrides,
320
+ );
321
+ const options = {
322
+ ...requestOptions,
323
+ ...(Object.keys(agentCommandOverrides).length > 0 ? { agentCommandOverrides } : {}),
324
+ ...(sessionId ? { sessionId } : {}),
325
+ ...(clientRequestId ? { clientRequestId } : {}),
326
+ };
327
+
328
+ emitRunState({
329
+ runRegistry,
330
+ writer: baseWriter,
331
+ runId: run.runId,
332
+ provider,
333
+ sessionId,
334
+ clientRequestId,
335
+ status: 'queued',
336
+ event: { reason: 'accepted', commandType },
337
+ });
338
+
339
+ appendSubmittedPromptRunEvent({
340
+ writer,
341
+ provider,
342
+ sessionId,
343
+ clientRequestId,
344
+ command,
345
+ options,
346
+ });
347
+
348
+ setImmediate(async () => {
349
+ try {
350
+ emitRunState({
351
+ runRegistry,
352
+ writer: baseWriter,
353
+ runId: run.runId,
354
+ provider,
355
+ sessionId: writer.getSessionId?.() || sessionId,
356
+ clientRequestId,
357
+ status: 'running',
358
+ event: { reason: 'execution_started' },
359
+ });
360
+ const result = await withRunActivityWatchdog(executeRun({
361
+ agentKey: provider,
362
+ command,
363
+ options,
364
+ writer,
365
+ run,
366
+ }), {
367
+ runRegistry,
368
+ runId: run.runId,
369
+ provider,
370
+ sessionId: writer.getSessionId?.() || sessionId,
371
+ clientRequestId,
372
+ timeoutMs: effectiveRunInactivityTimeoutMs,
373
+ });
374
+ if (shouldEmitTerminalRunState(runRegistry.getRun(run.runId)?.status, 'succeeded')) {
375
+ emitRunState({
376
+ runRegistry,
377
+ writer: baseWriter,
378
+ runId: run.runId,
379
+ provider,
380
+ sessionId: result?.sessionId || writer.getSessionId?.() || sessionId,
381
+ clientRequestId,
382
+ status: 'succeeded',
383
+ event: {
384
+ sessionId: result?.sessionId || writer.getSessionId?.() || sessionId,
385
+ stopReason: result?.stopReason || null,
386
+ },
387
+ });
388
+ }
389
+ } catch (error) {
390
+ const failedSessionId = writer.getSessionId?.() || sessionId;
391
+ const errorPayload = {
392
+ message: error?.message || 'Run failed',
393
+ ...(error?.code ? { code: error.code } : {}),
394
+ ...(error?.details ? { details: error.details } : {}),
395
+ };
396
+ if (error?.code !== 'RUN_CANCELED') {
397
+ writer.send({
398
+ type: 'conversation-event',
399
+ provider,
400
+ sessionId: failedSessionId,
401
+ clientRequestId,
402
+ event: createConversationEvent({
403
+ kind: CONVERSATION_EVENT_KINDS.ERROR,
404
+ provider,
405
+ sessionId: failedSessionId,
406
+ payload: errorPayload,
407
+ extensions: clientRequestId ? { clientRequestId } : {},
408
+ rawRef: {
409
+ type: 'run-error',
410
+ runId: run.runId,
411
+ },
412
+ }),
413
+ });
414
+ }
415
+ if (shouldEmitTerminalRunState(runRegistry.getRun(run.runId)?.status, 'failed')) {
416
+ emitRunState({
417
+ runRegistry,
418
+ writer: baseWriter,
419
+ runId: run.runId,
420
+ provider,
421
+ sessionId: failedSessionId,
422
+ clientRequestId,
423
+ status: 'failed',
424
+ event: {
425
+ error: error?.message || 'Run failed',
426
+ ...(error?.code ? { code: error.code } : {}),
427
+ ...(error?.details ? { details: error.details } : {}),
428
+ },
429
+ });
430
+ }
431
+ }
432
+ });
433
+
434
+ return res.status(202).json({
435
+ success: true,
436
+ runId: run.runId,
437
+ status: run.status,
438
+ run,
439
+ });
440
+ });
441
+
442
+ router.get('/runs/:runId', (req, res) => {
443
+ const run = runRegistry.getRun(req.params.runId);
444
+ if (!run) {
445
+ return sendRunNotFound(res, req.params.runId);
446
+ }
447
+
448
+ return res.json({
449
+ success: true,
450
+ run,
451
+ });
452
+ });
453
+
454
+ router.get('/runs/:runId/events', (req, res) => {
455
+ const run = runRegistry.getRun(req.params.runId);
456
+ if (!run) {
457
+ return sendRunNotFound(res, req.params.runId);
458
+ }
459
+
460
+ return res.json({
461
+ success: true,
462
+ run,
463
+ events: runRegistry.listRunEvents(run.runId, {
464
+ after: parseAfterSeq(req.query.after),
465
+ }),
466
+ });
467
+ });
468
+
469
+ router.get('/runs/:runId/stream', (req, res) => {
470
+ const run = runRegistry.getRun(req.params.runId);
471
+ if (!run) {
472
+ return sendRunNotFound(res, req.params.runId);
473
+ }
474
+
475
+ res.status(200);
476
+ res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
477
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
478
+ res.setHeader('Connection', 'keep-alive');
479
+ res.flushHeaders?.();
480
+
481
+ res.write(': connected\n\n');
482
+
483
+ const sendRunEvent = (runEvent) => {
484
+ writeSseFrame(res, eventNameForRunEvent(runEvent), runEvent);
485
+ const status = runEvent?.type === 'run-state'
486
+ ? String(runEvent.event?.status || '').trim().toLowerCase()
487
+ : '';
488
+ if (TERMINAL_STATUSES.has(status)) {
489
+ writeSseFrame(res, 'end', {
490
+ runId: run.runId,
491
+ status,
492
+ seq: runEvent.seq,
493
+ provider: runEvent.provider || run.provider,
494
+ sessionId: runEvent.sessionId || run.sessionId,
495
+ clientRequestId: runEvent.clientRequestId || run.clientRequestId,
496
+ });
497
+ cleanup();
498
+ res.end();
499
+ }
500
+ };
501
+
502
+ const cleanup = () => {
503
+ clearInterval(keepAliveTimer);
504
+ unsubscribe();
505
+ };
506
+
507
+ const replayEvents = runRegistry.listRunEvents(run.runId, {
508
+ after: parseAfterSeq(req.query.after),
509
+ });
510
+
511
+ const unsubscribe = runRegistry.subscribeRunEvents(run.runId, sendRunEvent);
512
+ const keepAliveTimer = setInterval(() => {
513
+ if (!res.writableEnded) {
514
+ res.write(': keepalive\n\n');
515
+ }
516
+ }, 25_000);
517
+ keepAliveTimer.unref?.();
518
+
519
+ req.on('close', cleanup);
520
+ replayEvents.forEach(sendRunEvent);
521
+
522
+ const latestRun = runRegistry.getRun(run.runId);
523
+ if (latestRun && TERMINAL_STATUSES.has(latestRun.status) && replayEvents.length === 0) {
524
+ writeSseFrame(res, 'end', {
525
+ runId: run.runId,
526
+ status: latestRun.status,
527
+ seq: latestRun.lastEventSeq,
528
+ provider: latestRun.provider,
529
+ sessionId: latestRun.sessionId,
530
+ clientRequestId: latestRun.clientRequestId,
531
+ });
532
+ cleanup();
533
+ res.end();
534
+ }
535
+ });
536
+
537
+ router.post('/runs/:runId/cancel', async (req, res) => {
538
+ const run = runRegistry.getRun(req.params.runId);
539
+ if (!run) {
540
+ return sendRunNotFound(res, req.params.runId);
541
+ }
542
+
543
+ const sessionId = normalizeNullableString(req.body?.sessionId) || run.sessionId;
544
+ let canceled = false;
545
+ if (sessionId) {
546
+ canceled = await cancelRun({
547
+ runId: run.runId,
548
+ provider: run.provider,
549
+ sessionId,
550
+ });
551
+ }
552
+
553
+ const { run: updatedRun } = emitRunState({
554
+ runRegistry,
555
+ runId: run.runId,
556
+ provider: run.provider,
557
+ sessionId,
558
+ clientRequestId: run.clientRequestId,
559
+ status: 'canceled',
560
+ event: {
561
+ reason: canceled ? 'cancel_requested' : 'cancel_recorded',
562
+ canceled,
563
+ },
564
+ });
565
+
566
+ return res.json({
567
+ success: true,
568
+ canceled,
569
+ run: updatedRun,
570
+ });
571
+ });
572
+
573
+ router.post('/runs/:runId/permission', (req, res) => {
574
+ const run = runRegistry.getRun(req.params.runId);
575
+ if (!run) {
576
+ return sendRunNotFound(res, req.params.runId);
577
+ }
578
+
579
+ const requestId = normalizeNullableString(req.body?.requestId);
580
+ if (!requestId) {
581
+ return sendBadRequest(res, 'requestId is required');
582
+ }
583
+
584
+ const resolved = resolvePermission(requestId, {
585
+ allow: Boolean(req.body?.allow),
586
+ optionId: req.body?.optionId,
587
+ outcome: req.body?.outcome,
588
+ cancelled: req.body?.outcome?.outcome === 'cancelled' || Boolean(req.body?.cancelled),
589
+ updatedInput: req.body?.updatedInput,
590
+ message: req.body?.message,
591
+ rememberEntry: req.body?.rememberEntry,
592
+ });
593
+
594
+ return res.json({
595
+ success: true,
596
+ resolved,
597
+ run,
598
+ });
599
+ });
600
+
601
+ router.post('/runs/:runId/elicitation', (req, res) => {
602
+ const run = runRegistry.getRun(req.params.runId);
603
+ if (!run) {
604
+ return sendRunNotFound(res, req.params.runId);
605
+ }
606
+
607
+ const requestId = normalizeNullableString(req.body?.requestId);
608
+ if (!requestId) {
609
+ return sendBadRequest(res, 'requestId is required');
610
+ }
611
+
612
+ const action = normalizeNullableString(req.body?.action) || 'cancel';
613
+ const resolved = resolveElicitation(requestId, {
614
+ action,
615
+ content: req.body?.content && typeof req.body.content === 'object' && !Array.isArray(req.body.content)
616
+ ? req.body.content
617
+ : {},
618
+ });
619
+
620
+ return res.json({
621
+ success: true,
622
+ resolved,
623
+ run,
624
+ });
625
+ });
626
+
627
+ router.get('/sessions/:provider/:sessionId/runs', (req, res) => {
628
+ return res.json({
629
+ success: true,
630
+ runs: runRegistry.listSessionRuns({
631
+ provider: req.params.provider,
632
+ sessionId: req.params.sessionId,
633
+ status: req.query.status,
634
+ }),
635
+ });
636
+ });
637
+
638
+ return router;
639
+ }
640
+
641
+ export default createRunsRouter();