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.
- package/README.md +1123 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +19 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/program-name.d.ts +2 -0
- package/dist/cli/program-name.js +12 -0
- package/dist/cli/program-name.js.map +1 -0
- package/dist/cli/typescript-runtime.d.ts +52 -0
- package/dist/cli/typescript-runtime.js +18009 -0
- package/dist/cli/typescript-runtime.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/audit.d.ts +96 -0
- package/dist/runtime/audit.js +298 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/classify.d.ts +8 -0
- package/dist/runtime/classify.js +128 -0
- package/dist/runtime/classify.js.map +1 -0
- package/dist/runtime/codex-session.d.ts +103 -0
- package/dist/runtime/codex-session.js +408 -0
- package/dist/runtime/codex-session.js.map +1 -0
- package/dist/runtime/commands.d.ts +92 -0
- package/dist/runtime/commands.js +408 -0
- package/dist/runtime/commands.js.map +1 -0
- package/dist/runtime/dispatch.d.ts +74 -0
- package/dist/runtime/dispatch.js +669 -0
- package/dist/runtime/dispatch.js.map +1 -0
- package/dist/runtime/export.d.ts +22 -0
- package/dist/runtime/export.js +77 -0
- package/dist/runtime/export.js.map +1 -0
- package/dist/runtime/ingest.d.ts +28 -0
- package/dist/runtime/ingest.js +177 -0
- package/dist/runtime/ingest.js.map +1 -0
- package/dist/runtime/loop-evidence.d.ts +87 -0
- package/dist/runtime/loop-evidence.js +448 -0
- package/dist/runtime/loop-evidence.js.map +1 -0
- package/dist/runtime/manager-config.d.ts +20 -0
- package/dist/runtime/manager-config.js +34 -0
- package/dist/runtime/manager-config.js.map +1 -0
- package/dist/runtime/manager-permissions.d.ts +7 -0
- package/dist/runtime/manager-permissions.js +85 -0
- package/dist/runtime/manager-permissions.js.map +1 -0
- package/dist/runtime/notifications.d.ts +89 -0
- package/dist/runtime/notifications.js +208 -0
- package/dist/runtime/notifications.js.map +1 -0
- package/dist/runtime/replay.d.ts +29 -0
- package/dist/runtime/replay.js +331 -0
- package/dist/runtime/replay.js.map +1 -0
- package/dist/runtime/tasks.d.ts +54 -0
- package/dist/runtime/tasks.js +195 -0
- package/dist/runtime/tasks.js.map +1 -0
- package/dist/runtime/tmux.d.ts +61 -0
- package/dist/runtime/tmux.js +189 -0
- package/dist/runtime/tmux.js.map +1 -0
- package/dist/runtime/visual-diff.d.ts +23 -0
- package/dist/runtime/visual-diff.js +234 -0
- package/dist/runtime/visual-diff.js.map +1 -0
- package/dist/state/database.d.ts +21 -0
- package/dist/state/database.js +142 -0
- package/dist/state/database.js.map +1 -0
- package/dist/state/files.d.ts +38 -0
- package/dist/state/files.js +73 -0
- package/dist/state/files.js.map +1 -0
- package/dist/state/schema-v22.d.ts +1 -0
- package/dist/state/schema-v22.js +566 -0
- package/dist/state/schema-v22.js.map +1 -0
- package/dist/state/sqlite-contract.d.ts +4 -0
- package/dist/state/sqlite-contract.js +78 -0
- package/dist/state/sqlite-contract.js.map +1 -0
- package/dist/state/status.d.ts +12 -0
- package/dist/state/status.js +40 -0
- package/dist/state/status.js.map +1 -0
- package/docs/typescript-migration/cli-contract.md +147 -0
- package/docs/typescript-migration/dashboard-contract.md +76 -0
- package/docs/typescript-migration/package-install-contract.md +98 -0
- package/docs/typescript-migration/qa-gate-matrix.md +103 -0
- package/docs/typescript-migration/sqlite-state-contract.md +92 -0
- package/docs/typescript-migration/t005-runtime-parity.md +47 -0
- package/package.json +88 -0
- package/scripts/capture-static-html-screenshot.mjs +88 -0
- package/skills/codex-review/SKILL.md +116 -0
- package/skills/codex-review/scripts/codex-review +344 -0
- package/skills/manage-codex-workers/SKILL.md +696 -0
- 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
|