agent-conveyor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +1123 -0
  2. package/dist/cli/main.d.ts +2 -0
  3. package/dist/cli/main.js +19 -0
  4. package/dist/cli/main.js.map +1 -0
  5. package/dist/cli/program-name.d.ts +2 -0
  6. package/dist/cli/program-name.js +12 -0
  7. package/dist/cli/program-name.js.map +1 -0
  8. package/dist/cli/typescript-runtime.d.ts +52 -0
  9. package/dist/cli/typescript-runtime.js +18009 -0
  10. package/dist/cli/typescript-runtime.js.map +1 -0
  11. package/dist/index.d.ts +37 -0
  12. package/dist/index.js +20 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/runtime/audit.d.ts +96 -0
  15. package/dist/runtime/audit.js +298 -0
  16. package/dist/runtime/audit.js.map +1 -0
  17. package/dist/runtime/classify.d.ts +8 -0
  18. package/dist/runtime/classify.js +128 -0
  19. package/dist/runtime/classify.js.map +1 -0
  20. package/dist/runtime/codex-session.d.ts +103 -0
  21. package/dist/runtime/codex-session.js +408 -0
  22. package/dist/runtime/codex-session.js.map +1 -0
  23. package/dist/runtime/commands.d.ts +92 -0
  24. package/dist/runtime/commands.js +408 -0
  25. package/dist/runtime/commands.js.map +1 -0
  26. package/dist/runtime/dispatch.d.ts +74 -0
  27. package/dist/runtime/dispatch.js +669 -0
  28. package/dist/runtime/dispatch.js.map +1 -0
  29. package/dist/runtime/export.d.ts +22 -0
  30. package/dist/runtime/export.js +77 -0
  31. package/dist/runtime/export.js.map +1 -0
  32. package/dist/runtime/ingest.d.ts +28 -0
  33. package/dist/runtime/ingest.js +177 -0
  34. package/dist/runtime/ingest.js.map +1 -0
  35. package/dist/runtime/loop-evidence.d.ts +87 -0
  36. package/dist/runtime/loop-evidence.js +448 -0
  37. package/dist/runtime/loop-evidence.js.map +1 -0
  38. package/dist/runtime/manager-config.d.ts +20 -0
  39. package/dist/runtime/manager-config.js +34 -0
  40. package/dist/runtime/manager-config.js.map +1 -0
  41. package/dist/runtime/manager-permissions.d.ts +7 -0
  42. package/dist/runtime/manager-permissions.js +85 -0
  43. package/dist/runtime/manager-permissions.js.map +1 -0
  44. package/dist/runtime/notifications.d.ts +89 -0
  45. package/dist/runtime/notifications.js +208 -0
  46. package/dist/runtime/notifications.js.map +1 -0
  47. package/dist/runtime/replay.d.ts +29 -0
  48. package/dist/runtime/replay.js +331 -0
  49. package/dist/runtime/replay.js.map +1 -0
  50. package/dist/runtime/tasks.d.ts +54 -0
  51. package/dist/runtime/tasks.js +195 -0
  52. package/dist/runtime/tasks.js.map +1 -0
  53. package/dist/runtime/tmux.d.ts +61 -0
  54. package/dist/runtime/tmux.js +189 -0
  55. package/dist/runtime/tmux.js.map +1 -0
  56. package/dist/runtime/visual-diff.d.ts +23 -0
  57. package/dist/runtime/visual-diff.js +234 -0
  58. package/dist/runtime/visual-diff.js.map +1 -0
  59. package/dist/state/database.d.ts +21 -0
  60. package/dist/state/database.js +142 -0
  61. package/dist/state/database.js.map +1 -0
  62. package/dist/state/files.d.ts +38 -0
  63. package/dist/state/files.js +73 -0
  64. package/dist/state/files.js.map +1 -0
  65. package/dist/state/schema-v22.d.ts +1 -0
  66. package/dist/state/schema-v22.js +566 -0
  67. package/dist/state/schema-v22.js.map +1 -0
  68. package/dist/state/sqlite-contract.d.ts +4 -0
  69. package/dist/state/sqlite-contract.js +78 -0
  70. package/dist/state/sqlite-contract.js.map +1 -0
  71. package/dist/state/status.d.ts +12 -0
  72. package/dist/state/status.js +40 -0
  73. package/dist/state/status.js.map +1 -0
  74. package/docs/typescript-migration/cli-contract.md +147 -0
  75. package/docs/typescript-migration/dashboard-contract.md +76 -0
  76. package/docs/typescript-migration/package-install-contract.md +98 -0
  77. package/docs/typescript-migration/qa-gate-matrix.md +103 -0
  78. package/docs/typescript-migration/sqlite-state-contract.md +92 -0
  79. package/docs/typescript-migration/t005-runtime-parity.md +47 -0
  80. package/package.json +88 -0
  81. package/scripts/capture-static-html-screenshot.mjs +88 -0
  82. package/skills/codex-review/SKILL.md +116 -0
  83. package/skills/codex-review/scripts/codex-review +344 -0
  84. package/skills/manage-codex-workers/SKILL.md +696 -0
  85. package/skills/manage-codex-workers/agents/openai.yaml +5 -0
@@ -0,0 +1,669 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { finishCommandAttemptSync, markCommandAttemptSideEffectStartedSync } from "./commands.js";
3
+ import { managerConfigPermissionAllowed, managerConfigSync } from "./manager-config.js";
4
+ import { deferRoutedNotificationBeforeSideEffectSync, deliveryModeForTargetSessionSync, finishRoutedNotificationSync, insertRoutedNotificationSync, markRoutedNotificationSideEffectStartedSync, } from "./notifications.js";
5
+ import { activeBindingForTaskSync } from "./tasks.js";
6
+ import { sendTextToSessionWithRunner } from "./tmux.js";
7
+ export class DispatchPermissionError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "DispatchPermissionError";
11
+ }
12
+ }
13
+ export class DispatchRoutingError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "DispatchRoutingError";
17
+ }
18
+ }
19
+ export function resolveDispatchCommandRouteSync(database, command) {
20
+ if (!command.task_id) {
21
+ throw new DispatchRoutingError(`${command.type} command requires task_id for active binding resolution`);
22
+ }
23
+ const binding = activeBindingForTaskSync(database, command.task_id);
24
+ if (command.type === "notify_manager") {
25
+ return {
26
+ ...binding,
27
+ signal_type: "notify_manager",
28
+ source_session_id: binding.worker_session_id,
29
+ source_session_name: binding.worker_session_name,
30
+ target_session_id: binding.manager_session_id,
31
+ target_session_name: binding.manager_session_name,
32
+ };
33
+ }
34
+ if (command.type === "nudge_worker" || command.type === "continue_iteration") {
35
+ return {
36
+ ...binding,
37
+ signal_type: command.type,
38
+ source_session_id: binding.manager_session_id,
39
+ source_session_name: binding.manager_session_name,
40
+ target_session_id: binding.worker_session_id,
41
+ target_session_name: binding.worker_session_name,
42
+ };
43
+ }
44
+ throw new DispatchRoutingError(`unsupported dispatch command type: ${command.type}`);
45
+ }
46
+ export function executeDispatchCommandSync(database, options) {
47
+ const timestamp = options.now ?? new Date().toISOString();
48
+ const command = options.claimed.command;
49
+ const attempt = options.claimed.attempt;
50
+ const baseResult = {
51
+ attempt_id: attempt.id,
52
+ command_id: command.id,
53
+ command_type: command.type,
54
+ correlation_id: command.correlation_id,
55
+ dispatcher_id: options.dispatcherId,
56
+ };
57
+ const text = dispatchCommandText(command);
58
+ const route = resolveDispatchCommandRouteSync(database, command);
59
+ if (options.dryRun) {
60
+ return {
61
+ ...baseResult,
62
+ dry_run: true,
63
+ state: "planned",
64
+ target_session: route.target_session_name,
65
+ };
66
+ }
67
+ const permissionCheck = checkDispatchRequiredPermissionSync(database, { command, now: timestamp });
68
+ const deliveryMode = deliveryModeForTargetSessionSync(database, route.target_session_id);
69
+ const loopPolicy = dispatchRalphLoopPolicySync(database, command);
70
+ if (loopPolicy?.reason) {
71
+ const error = loopPolicyBlockError(loopPolicy);
72
+ const result = {
73
+ ...baseResult,
74
+ ...loopResultFields(loopPolicy),
75
+ delivered: false,
76
+ delivery_mode: deliveryMode,
77
+ dry_run: false,
78
+ manager_decision_id: managerDecisionIdFromCommand(command),
79
+ notification_id: null,
80
+ side_effect_completed: false,
81
+ side_effect_started: false,
82
+ state: "blocked",
83
+ target_session: route.target_session_name,
84
+ target_worker_notified: false,
85
+ };
86
+ finishCommandAttemptSync(database, {
87
+ attemptId: attempt.id,
88
+ error,
89
+ now: timestamp,
90
+ result,
91
+ sideEffectCompleted: false,
92
+ sideEffectStarted: false,
93
+ state: "blocked",
94
+ });
95
+ return result;
96
+ }
97
+ const payload = {
98
+ command_id: command.id,
99
+ command_type: command.type,
100
+ delivery_mode: deliveryMode,
101
+ dispatcher_id: options.dispatcherId,
102
+ message: text,
103
+ permission_check: permissionCheck,
104
+ source_session: route.source_session_name,
105
+ target_session: route.target_session_name,
106
+ task_id: command.task_id,
107
+ ...(loopPolicy ? notificationLoopPayload(loopPolicy) : {}),
108
+ };
109
+ const tmuxRunner = options.tmuxRunner;
110
+ if (deliveryMode !== "pull_required" && !tmuxRunner) {
111
+ throw new DispatchRoutingError("push delivery requires a tmux runner and is not available in this TypeScript slice");
112
+ }
113
+ const notificationId = insertRoutedNotificationSync(database, {
114
+ bindingId: route.binding_id,
115
+ commandId: command.id,
116
+ correlationId: command.correlation_id ?? `dispatch-${randomUUID()}`,
117
+ dedupeKey: `${route.binding_id}:${command.type}:${command.id}`,
118
+ deliveryMode,
119
+ now: timestamp,
120
+ payload,
121
+ signalType: route.signal_type,
122
+ sourceSessionId: route.source_session_id,
123
+ targetSessionId: route.target_session_id,
124
+ taskId: route.task_id,
125
+ });
126
+ emitTelemetry(database, {
127
+ attributes: {
128
+ delivery_mode: deliveryMode,
129
+ permission_check: permissionCheck,
130
+ source_session: route.source_session_name,
131
+ target_session: route.target_session_name,
132
+ },
133
+ correlation: {
134
+ attempt_id: attempt.id,
135
+ command_id: command.id,
136
+ command_type: command.type,
137
+ correlation_id: command.correlation_id,
138
+ dispatcher_id: options.dispatcherId,
139
+ routed_notification_id: notificationId,
140
+ },
141
+ eventType: "dispatch_command_attempted",
142
+ severity: "info",
143
+ summary: `Dispatch is executing command ${command.type}.`,
144
+ taskId: route.task_id,
145
+ timestamp,
146
+ });
147
+ if (deliveryMode === "push") {
148
+ const runner = tmuxRunner;
149
+ if (!runner) {
150
+ throw new DispatchRoutingError("push delivery requires a tmux runner and is not available in this TypeScript slice");
151
+ }
152
+ const sideEffectAudit = {
153
+ side_effect_completed: false,
154
+ side_effect_started: false,
155
+ };
156
+ try {
157
+ const sendResult = sendTextToSessionWithRunner(sessionForTmux(database, route.target_session_id), formatLoopPolicyPushText(text, loopPolicy), runner, {
158
+ now: () => timestamp,
159
+ sideEffectAudit,
160
+ sideEffectStartedCallback: () => {
161
+ markCommandAttemptSideEffectStartedSync(database, attempt.id);
162
+ markRoutedNotificationSideEffectStartedSync(database, {
163
+ notificationId,
164
+ now: timestamp,
165
+ });
166
+ },
167
+ sleep: options.sleep,
168
+ });
169
+ const result = {
170
+ ...baseResult,
171
+ ...loopResultFields(loopPolicy),
172
+ delivery_mode: deliveryMode,
173
+ dry_run: false,
174
+ notification_id: notificationId,
175
+ permission_check: permissionCheck,
176
+ send_result: sendResult,
177
+ side_effect_completed: sendResult.side_effect_completed,
178
+ side_effect_started: sendResult.side_effect_started,
179
+ state: "delivered",
180
+ target_session: route.target_session_name,
181
+ };
182
+ finishRoutedNotificationSync(database, {
183
+ notificationId,
184
+ now: timestamp,
185
+ state: "delivered",
186
+ });
187
+ emitTelemetry(database, {
188
+ attributes: {
189
+ target: sendResult.target,
190
+ target_session: route.target_session_name,
191
+ },
192
+ correlation: {
193
+ attempt_id: attempt.id,
194
+ command_id: command.id,
195
+ command_type: command.type,
196
+ correlation_id: command.correlation_id,
197
+ dispatcher_id: options.dispatcherId,
198
+ routed_notification_id: notificationId,
199
+ signal_type: route.signal_type,
200
+ },
201
+ eventType: "dispatch_signal_routed",
202
+ severity: "info",
203
+ summary: `Dispatch routed command ${command.type} to ${route.target_session_name}.`,
204
+ taskId: route.task_id,
205
+ timestamp,
206
+ });
207
+ finishCommandAttemptSync(database, {
208
+ attemptId: attempt.id,
209
+ now: timestamp,
210
+ result,
211
+ sideEffectCompleted: sendResult.side_effect_completed,
212
+ sideEffectStarted: sendResult.side_effect_started,
213
+ state: "succeeded",
214
+ });
215
+ return result;
216
+ }
217
+ catch (error) {
218
+ const message = error instanceof Error ? error.message : String(error);
219
+ if (sideEffectAudit.side_effect_started) {
220
+ finishRoutedNotificationSync(database, {
221
+ error: message,
222
+ notificationId,
223
+ state: "failed",
224
+ });
225
+ }
226
+ else {
227
+ deferRoutedNotificationBeforeSideEffectSync(database, {
228
+ error: message,
229
+ notificationId,
230
+ });
231
+ }
232
+ const result = {
233
+ ...baseResult,
234
+ ...loopResultFields(loopPolicy),
235
+ delivery_mode: deliveryMode,
236
+ dry_run: false,
237
+ error: message,
238
+ notification_id: notificationId,
239
+ permission_check: permissionCheck,
240
+ side_effect_completed: sideEffectAudit.side_effect_completed,
241
+ side_effect_started: sideEffectAudit.side_effect_started,
242
+ state: "failed",
243
+ target_session: route.target_session_name,
244
+ };
245
+ emitTelemetry(database, {
246
+ attributes: {
247
+ error: message,
248
+ error_type: error instanceof Error ? error.name : typeof error,
249
+ target_session: route.target_session_name,
250
+ },
251
+ correlation: {
252
+ attempt_id: attempt.id,
253
+ command_id: command.id,
254
+ command_type: command.type,
255
+ correlation_id: command.correlation_id,
256
+ dispatcher_id: options.dispatcherId,
257
+ routed_notification_id: notificationId,
258
+ signal_type: route.signal_type,
259
+ },
260
+ eventType: "dispatch_signal_failed",
261
+ severity: "error",
262
+ summary: `Dispatch failed to route command ${command.type} to ${route.target_session_name}.`,
263
+ taskId: route.task_id,
264
+ timestamp,
265
+ });
266
+ finishCommandAttemptSync(database, {
267
+ attemptId: attempt.id,
268
+ error: message,
269
+ now: timestamp,
270
+ result,
271
+ sideEffectCompleted: sideEffectAudit.side_effect_completed,
272
+ sideEffectStarted: sideEffectAudit.side_effect_started,
273
+ state: "failed",
274
+ });
275
+ return result;
276
+ }
277
+ }
278
+ const result = {
279
+ ...baseResult,
280
+ ...loopResultFields(loopPolicy),
281
+ delivery_mode: deliveryMode,
282
+ dry_run: false,
283
+ notification_id: notificationId,
284
+ permission_check: permissionCheck,
285
+ side_effect_completed: false,
286
+ side_effect_started: false,
287
+ state: "pull_required",
288
+ target_session: route.target_session_name,
289
+ };
290
+ finishRoutedNotificationSync(database, {
291
+ notificationId,
292
+ now: timestamp,
293
+ sideEffectCompleted: false,
294
+ state: "delivered",
295
+ });
296
+ emitTelemetry(database, {
297
+ attributes: {
298
+ delivery_mode: deliveryMode,
299
+ target_session: route.target_session_name,
300
+ },
301
+ correlation: {
302
+ attempt_id: attempt.id,
303
+ binding_id: route.binding_id,
304
+ command_id: command.id,
305
+ command_type: command.type,
306
+ correlation_id: command.correlation_id,
307
+ dispatcher_id: options.dispatcherId,
308
+ routed_notification_id: notificationId,
309
+ signal_type: route.signal_type,
310
+ },
311
+ eventType: "dispatch_signal_pull_required",
312
+ severity: "info",
313
+ summary: `Dispatch recorded pull-required ${route.signal_type} for ${route.target_session_name}.`,
314
+ taskId: route.task_id,
315
+ timestamp,
316
+ });
317
+ finishCommandAttemptSync(database, {
318
+ attemptId: attempt.id,
319
+ now: timestamp,
320
+ result,
321
+ sideEffectCompleted: false,
322
+ sideEffectStarted: false,
323
+ state: "succeeded",
324
+ });
325
+ return result;
326
+ }
327
+ export function checkDispatchRequiredPermissionSync(database, options) {
328
+ const requiredPermission = options.command.required_permission;
329
+ if (!requiredPermission) {
330
+ return null;
331
+ }
332
+ if (!options.command.task_id) {
333
+ throw new DispatchPermissionError(`${options.command.type} command requires task_id for permission check`);
334
+ }
335
+ const config = managerConfigSync(database, options.command.task_id);
336
+ const permissionCheck = {
337
+ allowed: managerConfigPermissionAllowed(config, requiredPermission),
338
+ configured: config !== null,
339
+ required_permission: requiredPermission,
340
+ };
341
+ emitTelemetry(database, {
342
+ attributes: permissionCheck,
343
+ correlation: {
344
+ command_id: options.command.id,
345
+ command_type: options.command.type,
346
+ correlation_id: options.command.correlation_id,
347
+ required_permission: requiredPermission,
348
+ },
349
+ eventType: "dispatch_command_permission_checked",
350
+ severity: permissionCheck.allowed ? "info" : "warning",
351
+ summary: `Dispatch checked manager permission ${requiredPermission}.`,
352
+ taskId: options.command.task_id,
353
+ timestamp: options.now ?? new Date().toISOString(),
354
+ });
355
+ if (!permissionCheck.allowed) {
356
+ throw new DispatchPermissionError(`manager permission required for dispatch command: ${requiredPermission}`);
357
+ }
358
+ return permissionCheck;
359
+ }
360
+ function emitTelemetry(database, options) {
361
+ const eventId = `telemetry-${randomUUID()}`;
362
+ database.prepare(`
363
+ insert into telemetry_events(
364
+ id, run_id, task_id, timestamp, actor, event_type, severity,
365
+ summary, correlation_json, attributes_json
366
+ )
367
+ values (?, null, ?, ?, 'dispatch', ?, ?, ?, ?, ?)
368
+ `).run(eventId, options.taskId, options.timestamp, options.eventType, options.severity, options.summary, stableJson(options.correlation), stableJson(options.attributes));
369
+ database.prepare(`
370
+ insert into telemetry_events_fts(
371
+ event_id, task_id, run_id, actor, event_type, summary, attributes
372
+ )
373
+ values (?, ?, null, 'dispatch', ?, ?, ?)
374
+ `).run(eventId, options.taskId, options.eventType, options.summary, stableJson(options.attributes));
375
+ }
376
+ function dispatchCommandText(command) {
377
+ const text = command.payload.message ?? command.payload.text;
378
+ if (typeof text !== "string" || !text.trim()) {
379
+ throw new DispatchRoutingError(`${command.type} command requires non-empty payload.message or payload.text`);
380
+ }
381
+ return text;
382
+ }
383
+ function dispatchRalphLoopPolicySync(database, command) {
384
+ if (command.type !== "continue_iteration") {
385
+ return null;
386
+ }
387
+ const loopPayload = command.payload.ralph_loop;
388
+ if (!isRecord(loopPayload)) {
389
+ throw new DispatchRoutingError("continue_iteration command requires payload.ralph_loop");
390
+ }
391
+ const runId = loopPayload.run_id;
392
+ if (typeof runId !== "string" || !runId.trim()) {
393
+ throw new DispatchRoutingError("continue_iteration command requires payload.ralph_loop.run_id");
394
+ }
395
+ const requestedIterationValue = loopPayload.requested_iteration;
396
+ if (typeof requestedIterationValue !== "number" || !Number.isInteger(requestedIterationValue)) {
397
+ throw new DispatchRoutingError("continue_iteration command requires integer payload.ralph_loop.requested_iteration");
398
+ }
399
+ const requestedIteration = requestedIterationValue;
400
+ const run = ralphLoopRunSync(database, runId);
401
+ if (run.task_id !== command.task_id) {
402
+ throw new DispatchRoutingError("continue_iteration Ralph loop run does not belong to command task");
403
+ }
404
+ let reason;
405
+ let missingEvidence = [];
406
+ if (requestedIteration <= run.current_iteration) {
407
+ reason = "stale_requested_iteration";
408
+ }
409
+ else if (run.current_iteration >= run.max_iterations || requestedIteration > run.max_iterations) {
410
+ reason = "max_iterations_reached";
411
+ }
412
+ else {
413
+ missingEvidence = missingRalphLoopEvidenceSync(database, {
414
+ requestedIteration,
415
+ requiredBeforeContinue: run.required_before_continue,
416
+ runId: run.id,
417
+ taskId: command.task_id ?? "",
418
+ });
419
+ reason = missingEvidenceReason(missingEvidence);
420
+ }
421
+ return {
422
+ cleanup_policy: run.cleanup_policy,
423
+ current_iteration: run.current_iteration,
424
+ loop_policy: loopPolicyPayload(run),
425
+ max_iterations: run.max_iterations,
426
+ missing_evidence: missingEvidence,
427
+ reason,
428
+ required_before_continue: run.required_before_continue,
429
+ requested_iteration: requestedIteration,
430
+ run_id: run.id,
431
+ seed_prompt_sha256: run.seed_prompt_sha256,
432
+ stop_conditions: run.stop_conditions,
433
+ };
434
+ }
435
+ function ralphLoopRunSync(database, runId) {
436
+ const row = database.prepare(`
437
+ select id, task_id, purpose, metadata_json
438
+ from runs
439
+ where id = ?
440
+ `).get(runId);
441
+ if (!row) {
442
+ throw new DispatchRoutingError(`Unknown run: ${runId}`);
443
+ }
444
+ const metadata = parseJsonObject(row.metadata_json);
445
+ if (metadata.kind !== "ralph_loop" && row.purpose !== "ralph_loop") {
446
+ throw new DispatchRoutingError(`Run ${JSON.stringify(runId)} is not a Ralph loop run`);
447
+ }
448
+ const currentIteration = integerMetadata(metadata.current_iteration);
449
+ const maxIterations = integerMetadata(metadata.max_iterations);
450
+ if (currentIteration === null || maxIterations === null) {
451
+ throw new DispatchRoutingError(`Ralph loop run ${JSON.stringify(runId)} is missing iteration policy`);
452
+ }
453
+ return {
454
+ cleanup_policy: typeof metadata.cleanup_policy === "string" ? metadata.cleanup_policy : null,
455
+ current_iteration: currentIteration,
456
+ id: row.id,
457
+ max_iterations: maxIterations,
458
+ metadata,
459
+ preset: typeof metadata.preset === "string" ? metadata.preset : null,
460
+ required_before_continue: stringList(metadata.required_before_continue),
461
+ seed_prompt_sha256: typeof metadata.seed_prompt_sha256 === "string" ? metadata.seed_prompt_sha256 : null,
462
+ stop_conditions: stringList(metadata.stop_conditions),
463
+ task_id: row.task_id,
464
+ };
465
+ }
466
+ function missingRalphLoopEvidenceSync(database, options) {
467
+ if (options.requestedIteration <= 1 || options.requiredBeforeContinue.length === 0) {
468
+ return [];
469
+ }
470
+ const previousIteration = options.requestedIteration - 1;
471
+ const rows = database.prepare(`
472
+ select evidence_json
473
+ from acceptance_criteria
474
+ where task_id = ? and status = 'satisfied'
475
+ order by id
476
+ `).all(options.taskId);
477
+ const evidenceRows = rows.map((row) => parseJsonObject(row.evidence_json));
478
+ return options.requiredBeforeContinue.filter((evidenceType) => !evidenceRows.some((evidence) => ralphLoopEvidenceMatches(evidence, {
479
+ evidenceType,
480
+ iteration: previousIteration,
481
+ runId: options.runId,
482
+ })));
483
+ }
484
+ function ralphLoopEvidenceMatches(evidence, options) {
485
+ if (evidence.evidence_type !== options.evidenceType
486
+ || evidence.ralph_loop_run_id !== options.runId
487
+ || evidence.iteration !== options.iteration) {
488
+ return false;
489
+ }
490
+ if (options.evidenceType === "adversarial_check") {
491
+ return isStructuredAdversarialEvidence(evidence);
492
+ }
493
+ return true;
494
+ }
495
+ function isStructuredAdversarialEvidence(evidence) {
496
+ for (const key of ["failure_mode", "check", "result"]) {
497
+ const value = evidence[key];
498
+ if (typeof value !== "string" || !value.trim()) {
499
+ return false;
500
+ }
501
+ }
502
+ const status = evidence.status;
503
+ if (status === undefined || status === null) {
504
+ return true;
505
+ }
506
+ if (typeof status !== "string") {
507
+ return false;
508
+ }
509
+ return !new Set(["error", "errored", "fail", "failed", "failure", "rejected"]).has(status.trim().toLowerCase());
510
+ }
511
+ function missingEvidenceReason(missingEvidence) {
512
+ if (missingEvidence.length === 0) {
513
+ return null;
514
+ }
515
+ if (missingEvidence.length === 1) {
516
+ return `missing_${missingEvidence[0]}_evidence`;
517
+ }
518
+ return "missing_required_evidence";
519
+ }
520
+ function loopPolicyPayload(run) {
521
+ const template = run.metadata.template ?? run.preset;
522
+ return {
523
+ artifact_requirements: isRecord(run.metadata.artifact_requirements) ? run.metadata.artifact_requirements : {},
524
+ cleanup_policy: run.cleanup_policy,
525
+ current_iteration: run.current_iteration,
526
+ max_iterations: run.max_iterations,
527
+ preset: run.preset,
528
+ recommended_tools: Array.isArray(run.metadata.recommended_tools) ? run.metadata.recommended_tools : [],
529
+ required_before_continue: run.required_before_continue,
530
+ run_id: run.id,
531
+ seed_prompt_sha256: run.seed_prompt_sha256,
532
+ stop_conditions: run.stop_conditions,
533
+ tags: Array.isArray(run.metadata.tags) ? run.metadata.tags : [],
534
+ template,
535
+ };
536
+ }
537
+ function loopResultFields(loopPolicy) {
538
+ if (!loopPolicy) {
539
+ return {};
540
+ }
541
+ return {
542
+ cleanup_policy: loopPolicy.cleanup_policy,
543
+ current_iteration: loopPolicy.current_iteration,
544
+ loop_policy: loopPolicy.loop_policy,
545
+ max_iterations: loopPolicy.max_iterations,
546
+ missing_evidence: loopPolicy.missing_evidence,
547
+ reason: loopPolicy.reason,
548
+ requested_iteration: loopPolicy.requested_iteration,
549
+ required_before_continue: loopPolicy.required_before_continue,
550
+ run_id: loopPolicy.run_id,
551
+ seed_prompt_sha256: loopPolicy.seed_prompt_sha256,
552
+ stop_conditions: loopPolicy.stop_conditions,
553
+ };
554
+ }
555
+ function notificationLoopPayload(loopPolicy) {
556
+ return {
557
+ loop_policy: loopPolicy.loop_policy,
558
+ ralph_loop: {
559
+ artifact_requirements: isRecord(loopPolicy.loop_policy.artifact_requirements) ? loopPolicy.loop_policy.artifact_requirements : {},
560
+ cleanup_policy: loopPolicy.cleanup_policy,
561
+ current_iteration: loopPolicy.current_iteration,
562
+ max_iterations: loopPolicy.max_iterations,
563
+ preset: loopPolicy.loop_policy.preset ?? null,
564
+ recommended_tools: Array.isArray(loopPolicy.loop_policy.recommended_tools) ? loopPolicy.loop_policy.recommended_tools : [],
565
+ required_before_continue: loopPolicy.required_before_continue,
566
+ requested_iteration: loopPolicy.requested_iteration,
567
+ run_id: loopPolicy.run_id,
568
+ seed_prompt_sha256: loopPolicy.seed_prompt_sha256,
569
+ stop_conditions: loopPolicy.stop_conditions,
570
+ tags: Array.isArray(loopPolicy.loop_policy.tags) ? loopPolicy.loop_policy.tags : [],
571
+ template: loopPolicy.loop_policy.template ?? null,
572
+ },
573
+ };
574
+ }
575
+ function loopPolicyBlockError(loopPolicy) {
576
+ const parts = [
577
+ String(loopPolicy.reason),
578
+ `current_iteration=${loopPolicy.current_iteration}`,
579
+ `max_iterations=${loopPolicy.max_iterations}`,
580
+ `requested_iteration=${loopPolicy.requested_iteration}`,
581
+ ];
582
+ if (loopPolicy.missing_evidence.length > 0) {
583
+ parts.splice(1, 0, `missing_evidence=${loopPolicy.missing_evidence.join(",")}`);
584
+ }
585
+ return parts.join(" ");
586
+ }
587
+ function formatLoopPolicyPushText(text, loopPolicy) {
588
+ if (!loopPolicy) {
589
+ return text;
590
+ }
591
+ const policyPayload = loopPolicy.loop_policy;
592
+ const template = policyPayload.template ?? policyPayload.preset ?? "unknown";
593
+ const recommendedTools = Array.isArray(policyPayload.recommended_tools) ? policyPayload.recommended_tools.map(String) : [];
594
+ const artifactRequirements = isRecord(policyPayload.artifact_requirements) ? Object.keys(policyPayload.artifact_requirements).sort() : [];
595
+ return [
596
+ text.trimEnd(),
597
+ "",
598
+ "Loop policy:",
599
+ `- run_id: ${loopPolicy.run_id}`,
600
+ `- template: ${template}`,
601
+ `- iteration: requested ${loopPolicy.requested_iteration} (current ${loopPolicy.current_iteration} of ${loopPolicy.max_iterations})`,
602
+ `- cleanup_policy: ${loopPolicy.cleanup_policy ?? "none"}`,
603
+ `- required_before_continue: ${loopPolicy.required_before_continue.length ? loopPolicy.required_before_continue.join(", ") : "none"}`,
604
+ `- recommended_tools: ${recommendedTools.length ? recommendedTools.join(", ") : "none"}`,
605
+ `- artifact_requirements: ${artifactRequirements.length ? artifactRequirements.join(", ") : "none"}`,
606
+ ].join("\n");
607
+ }
608
+ function managerDecisionIdFromCommand(command) {
609
+ const managerDecision = command.payload.manager_decision;
610
+ if (!isRecord(managerDecision)) {
611
+ return null;
612
+ }
613
+ const decisionId = managerDecision.decision_id ?? managerDecision.id;
614
+ if (typeof decisionId === "number" && Number.isInteger(decisionId)) {
615
+ return decisionId;
616
+ }
617
+ if (typeof decisionId === "string" && /^\d+$/.test(decisionId)) {
618
+ return Number(decisionId);
619
+ }
620
+ return null;
621
+ }
622
+ function sessionForTmux(database, sessionId) {
623
+ const row = database.prepare(`
624
+ select name, tmux_pane_id, tmux_session
625
+ from sessions
626
+ where id = ?
627
+ `).get(sessionId);
628
+ if (!row) {
629
+ throw new DispatchRoutingError(`target session ${JSON.stringify(sessionId)} no longer exists`);
630
+ }
631
+ return row;
632
+ }
633
+ function parseJsonObject(json) {
634
+ const value = JSON.parse(json);
635
+ return isRecord(value) ? value : {};
636
+ }
637
+ function integerMetadata(value) {
638
+ if (typeof value === "number" && Number.isInteger(value)) {
639
+ return value;
640
+ }
641
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
642
+ return Number(value);
643
+ }
644
+ return null;
645
+ }
646
+ function stringList(value) {
647
+ if (!Array.isArray(value)) {
648
+ return [];
649
+ }
650
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
651
+ }
652
+ function isRecord(value) {
653
+ return value !== null && typeof value === "object" && !Array.isArray(value);
654
+ }
655
+ function stableJson(value) {
656
+ return JSON.stringify(sortJson(value));
657
+ }
658
+ function sortJson(value) {
659
+ if (Array.isArray(value)) {
660
+ return value.map(sortJson);
661
+ }
662
+ if (value !== null && typeof value === "object") {
663
+ return Object.fromEntries(Object.entries(value)
664
+ .sort(([left], [right]) => left.localeCompare(right))
665
+ .map(([key, item]) => [key, sortJson(item)]));
666
+ }
667
+ return value;
668
+ }
669
+ //# sourceMappingURL=dispatch.js.map