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,89 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
export type RoutedNotificationDeliveryMode = "pull_required" | "push";
|
|
3
|
+
export type RoutedNotificationState = "delivered" | "failed" | "pending" | "suppressed";
|
|
4
|
+
export interface RoutedNotificationRecord {
|
|
5
|
+
binding_id: string;
|
|
6
|
+
claimed_at: string | null;
|
|
7
|
+
claimed_by: string | null;
|
|
8
|
+
claim_expires_at: string | null;
|
|
9
|
+
command_id: string | null;
|
|
10
|
+
correlation_id: string;
|
|
11
|
+
created_at: string;
|
|
12
|
+
consumed_at: string | null;
|
|
13
|
+
consumed_by_session_id: string | null;
|
|
14
|
+
consumed_manager_cycle_id: number | null;
|
|
15
|
+
dedupe_key: string;
|
|
16
|
+
delivered_at: string | null;
|
|
17
|
+
delivery_mode: RoutedNotificationDeliveryMode;
|
|
18
|
+
error: string | null;
|
|
19
|
+
id: number;
|
|
20
|
+
payload: Record<string, unknown>;
|
|
21
|
+
side_effect_completed: boolean;
|
|
22
|
+
side_effect_started: boolean;
|
|
23
|
+
signal_type: string;
|
|
24
|
+
source_event_id: number | null;
|
|
25
|
+
source_event_timestamp: string | null;
|
|
26
|
+
source_session_id: string;
|
|
27
|
+
state: RoutedNotificationState;
|
|
28
|
+
target_session_id: string;
|
|
29
|
+
task_id: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SessionInboxRecord extends RoutedNotificationRecord {
|
|
32
|
+
source_session_name: string;
|
|
33
|
+
source_session_role: string;
|
|
34
|
+
target_session_name: string;
|
|
35
|
+
target_session_role: string;
|
|
36
|
+
task_name: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class RoutedNotificationError extends Error {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
|
41
|
+
export declare function deliveryModeForTargetSessionSync(database: DatabaseSync, targetSessionId: string): RoutedNotificationDeliveryMode;
|
|
42
|
+
export declare function insertRoutedNotificationSync(database: DatabaseSync, options: {
|
|
43
|
+
bindingId: string;
|
|
44
|
+
claimExpiresAt?: string | null;
|
|
45
|
+
claimedAt?: string | null;
|
|
46
|
+
claimedBy?: string | null;
|
|
47
|
+
commandId?: string | null;
|
|
48
|
+
correlationId: string;
|
|
49
|
+
dedupeKey: string;
|
|
50
|
+
deliveryMode?: RoutedNotificationDeliveryMode;
|
|
51
|
+
now?: string;
|
|
52
|
+
payload: Record<string, unknown>;
|
|
53
|
+
signalType: string;
|
|
54
|
+
sourceEventId?: number | null;
|
|
55
|
+
sourceEventTimestamp?: string | null;
|
|
56
|
+
sourceSessionId: string;
|
|
57
|
+
state?: RoutedNotificationState;
|
|
58
|
+
targetSessionId: string;
|
|
59
|
+
taskId: string;
|
|
60
|
+
}): number;
|
|
61
|
+
export declare function finishRoutedNotificationSync(database: DatabaseSync, options: {
|
|
62
|
+
error?: string | null;
|
|
63
|
+
notificationId: number;
|
|
64
|
+
now?: string;
|
|
65
|
+
sideEffectCompleted?: boolean | null;
|
|
66
|
+
state: "delivered" | "failed" | "suppressed";
|
|
67
|
+
}): void;
|
|
68
|
+
export declare function markRoutedNotificationSideEffectStartedSync(database: DatabaseSync, options: {
|
|
69
|
+
claimExpiresAt?: string | null;
|
|
70
|
+
claimedBy?: string | null;
|
|
71
|
+
notificationId: number;
|
|
72
|
+
now?: string;
|
|
73
|
+
}): void;
|
|
74
|
+
export declare function deferRoutedNotificationBeforeSideEffectSync(database: DatabaseSync, options: {
|
|
75
|
+
error: string;
|
|
76
|
+
notificationId: number;
|
|
77
|
+
}): void;
|
|
78
|
+
export declare function routedNotificationsSync(database: DatabaseSync, options?: {
|
|
79
|
+
taskId?: string | null;
|
|
80
|
+
}): RoutedNotificationRecord[];
|
|
81
|
+
export declare function sessionInboxSync(database: DatabaseSync, options: {
|
|
82
|
+
includeConsumed?: boolean;
|
|
83
|
+
limit?: number;
|
|
84
|
+
sessionName: string;
|
|
85
|
+
}): SessionInboxRecord[];
|
|
86
|
+
export declare function consumeNextSessionInboxItemSync(database: DatabaseSync, options: {
|
|
87
|
+
now?: string;
|
|
88
|
+
sessionName: string;
|
|
89
|
+
}): SessionInboxRecord | null;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
export class RoutedNotificationError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "RoutedNotificationError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function deliveryModeForTargetSessionSync(database, targetSessionId) {
|
|
8
|
+
const row = database.prepare("select tmux_session from sessions where id = ?").get(targetSessionId);
|
|
9
|
+
if (!row) {
|
|
10
|
+
throw new RoutedNotificationError(`target session ${JSON.stringify(targetSessionId)} no longer exists`);
|
|
11
|
+
}
|
|
12
|
+
return row.tmux_session ? "push" : "pull_required";
|
|
13
|
+
}
|
|
14
|
+
export function insertRoutedNotificationSync(database, options) {
|
|
15
|
+
const state = options.state ?? "pending";
|
|
16
|
+
if (!["pending", "delivered", "failed", "suppressed"].includes(state)) {
|
|
17
|
+
throw new RoutedNotificationError(`invalid routed notification state: ${state}`);
|
|
18
|
+
}
|
|
19
|
+
const deliveryMode = options.deliveryMode ?? "push";
|
|
20
|
+
if (!["push", "pull_required"].includes(deliveryMode)) {
|
|
21
|
+
throw new RoutedNotificationError(`invalid routed notification delivery mode: ${deliveryMode}`);
|
|
22
|
+
}
|
|
23
|
+
const createdAt = options.now ?? new Date().toISOString();
|
|
24
|
+
const claimedAt = options.claimedAt ?? (options.claimedBy ? createdAt : null);
|
|
25
|
+
const result = database.prepare(`
|
|
26
|
+
insert into routed_notifications(
|
|
27
|
+
task_id, binding_id, correlation_id, source_session_id, target_session_id,
|
|
28
|
+
signal_type, source_event_id, source_event_timestamp, dedupe_key, command_id,
|
|
29
|
+
created_at, state, payload_json, claimed_by, claimed_at, claim_expires_at,
|
|
30
|
+
delivery_mode
|
|
31
|
+
)
|
|
32
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
33
|
+
`).run(options.taskId, options.bindingId, options.correlationId, options.sourceSessionId, options.targetSessionId, options.signalType, options.sourceEventId ?? null, options.sourceEventTimestamp ?? null, options.dedupeKey, options.commandId ?? null, createdAt, state, stableJson(options.payload), options.claimedBy ?? null, claimedAt, options.claimExpiresAt ?? null, deliveryMode);
|
|
34
|
+
return Number(result.lastInsertRowid);
|
|
35
|
+
}
|
|
36
|
+
export function finishRoutedNotificationSync(database, options) {
|
|
37
|
+
const deliveredAt = options.state === "delivered" ? options.now ?? new Date().toISOString() : null;
|
|
38
|
+
const completed = options.sideEffectCompleted ?? (options.state === "delivered");
|
|
39
|
+
database.prepare(`
|
|
40
|
+
update routed_notifications
|
|
41
|
+
set state = ?, delivered_at = ?, error = ?, side_effect_completed = ?
|
|
42
|
+
where id = ?
|
|
43
|
+
`).run(options.state, deliveredAt, options.error ?? null, completed ? 1 : 0, options.notificationId);
|
|
44
|
+
}
|
|
45
|
+
export function markRoutedNotificationSideEffectStartedSync(database, options) {
|
|
46
|
+
if (options.claimedBy === undefined && options.claimExpiresAt === undefined) {
|
|
47
|
+
database.prepare("update routed_notifications set side_effect_started = 1 where id = ?").run(options.notificationId);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const timestamp = options.now ?? new Date().toISOString();
|
|
51
|
+
database.prepare(`
|
|
52
|
+
update routed_notifications
|
|
53
|
+
set side_effect_started = 1,
|
|
54
|
+
claimed_by = coalesce(?, claimed_by),
|
|
55
|
+
claimed_at = coalesce(claimed_at, ?),
|
|
56
|
+
claim_expires_at = coalesce(?, claim_expires_at)
|
|
57
|
+
where id = ?
|
|
58
|
+
`).run(options.claimedBy ?? null, timestamp, options.claimExpiresAt ?? null, options.notificationId);
|
|
59
|
+
}
|
|
60
|
+
export function deferRoutedNotificationBeforeSideEffectSync(database, options) {
|
|
61
|
+
database.prepare(`
|
|
62
|
+
update routed_notifications
|
|
63
|
+
set state = 'pending',
|
|
64
|
+
error = ?,
|
|
65
|
+
claimed_by = null,
|
|
66
|
+
claimed_at = null,
|
|
67
|
+
claim_expires_at = null,
|
|
68
|
+
side_effect_started = 0,
|
|
69
|
+
side_effect_completed = 0
|
|
70
|
+
where id = ?
|
|
71
|
+
`).run(options.error, options.notificationId);
|
|
72
|
+
}
|
|
73
|
+
export function routedNotificationsSync(database, options) {
|
|
74
|
+
const rows = options?.taskId
|
|
75
|
+
? database.prepare(`${routedNotificationSelect()} where task_id = ? order by id`).all(options.taskId)
|
|
76
|
+
: database.prepare(`${routedNotificationSelect()} order by id`).all();
|
|
77
|
+
return rows.map(routedNotificationRecord);
|
|
78
|
+
}
|
|
79
|
+
export function sessionInboxSync(database, options) {
|
|
80
|
+
const session = sessionRow(database, options.sessionName);
|
|
81
|
+
const where = options.includeConsumed
|
|
82
|
+
? "rn.target_session_id = ? and rn.state = 'delivered'"
|
|
83
|
+
: "rn.target_session_id = ? and rn.state = 'delivered' and rn.consumed_at is null";
|
|
84
|
+
const rows = database.prepare(`${sessionInboxQuery(where)} limit ?`).all(session.id, Math.max(1, options.limit ?? 10));
|
|
85
|
+
return rows.map(sessionInboxRecord);
|
|
86
|
+
}
|
|
87
|
+
export function consumeNextSessionInboxItemSync(database, options) {
|
|
88
|
+
const session = sessionRow(database, options.sessionName);
|
|
89
|
+
const row = database.prepare(`
|
|
90
|
+
select id
|
|
91
|
+
from routed_notifications
|
|
92
|
+
where target_session_id = ?
|
|
93
|
+
and state = 'delivered'
|
|
94
|
+
and consumed_at is null
|
|
95
|
+
order by created_at, id
|
|
96
|
+
limit 1
|
|
97
|
+
`).get(session.id);
|
|
98
|
+
if (!row) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const result = database.prepare(`
|
|
102
|
+
update routed_notifications
|
|
103
|
+
set consumed_at = ?, consumed_by_session_id = ?
|
|
104
|
+
where id = ?
|
|
105
|
+
and target_session_id = ?
|
|
106
|
+
and state = 'delivered'
|
|
107
|
+
and consumed_at is null
|
|
108
|
+
`).run(options.now ?? new Date().toISOString(), session.id, row.id, session.id);
|
|
109
|
+
if (result.changes === 0) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const consumed = database.prepare(sessionInboxQuery("rn.id = ?")).get(row.id);
|
|
113
|
+
return consumed ? sessionInboxRecord(consumed) : null;
|
|
114
|
+
}
|
|
115
|
+
function routedNotificationSelect() {
|
|
116
|
+
return `
|
|
117
|
+
select id, task_id, binding_id, correlation_id, source_session_id,
|
|
118
|
+
target_session_id, signal_type, source_event_id, source_event_timestamp,
|
|
119
|
+
dedupe_key, command_id, created_at, delivered_at, consumed_manager_cycle_id,
|
|
120
|
+
consumed_by_session_id, consumed_at, delivery_mode, state, claimed_by,
|
|
121
|
+
claimed_at, claim_expires_at, side_effect_started, side_effect_completed,
|
|
122
|
+
payload_json, error
|
|
123
|
+
from routed_notifications
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
function sessionInboxQuery(whereClause) {
|
|
127
|
+
return `
|
|
128
|
+
select
|
|
129
|
+
rn.id, rn.task_id, rn.binding_id, rn.correlation_id,
|
|
130
|
+
rn.source_session_id, rn.target_session_id, rn.signal_type,
|
|
131
|
+
rn.source_event_id, rn.source_event_timestamp, rn.dedupe_key,
|
|
132
|
+
rn.command_id, rn.created_at, rn.delivered_at,
|
|
133
|
+
rn.consumed_manager_cycle_id, rn.consumed_by_session_id,
|
|
134
|
+
rn.consumed_at, rn.delivery_mode, rn.state, rn.claimed_by,
|
|
135
|
+
rn.claimed_at, rn.claim_expires_at, rn.side_effect_started,
|
|
136
|
+
rn.side_effect_completed, rn.payload_json, rn.error,
|
|
137
|
+
ss.name as source_session_name, ss.role as source_session_role,
|
|
138
|
+
ts.name as target_session_name, ts.role as target_session_role,
|
|
139
|
+
t.name as task_name
|
|
140
|
+
from routed_notifications rn
|
|
141
|
+
join sessions ss on ss.id = rn.source_session_id
|
|
142
|
+
join sessions ts on ts.id = rn.target_session_id
|
|
143
|
+
join tasks t on t.id = rn.task_id
|
|
144
|
+
where ${whereClause}
|
|
145
|
+
order by rn.created_at, rn.id
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
function sessionInboxRecord(row) {
|
|
149
|
+
return {
|
|
150
|
+
...routedNotificationRecord(row),
|
|
151
|
+
source_session_name: row.source_session_name,
|
|
152
|
+
source_session_role: row.source_session_role,
|
|
153
|
+
target_session_name: row.target_session_name,
|
|
154
|
+
target_session_role: row.target_session_role,
|
|
155
|
+
task_name: row.task_name,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function routedNotificationRecord(row) {
|
|
159
|
+
return {
|
|
160
|
+
binding_id: row.binding_id,
|
|
161
|
+
claimed_at: row.claimed_at,
|
|
162
|
+
claimed_by: row.claimed_by,
|
|
163
|
+
claim_expires_at: row.claim_expires_at,
|
|
164
|
+
command_id: row.command_id,
|
|
165
|
+
correlation_id: row.correlation_id,
|
|
166
|
+
created_at: row.created_at,
|
|
167
|
+
consumed_at: row.consumed_at,
|
|
168
|
+
consumed_by_session_id: row.consumed_by_session_id,
|
|
169
|
+
consumed_manager_cycle_id: row.consumed_manager_cycle_id,
|
|
170
|
+
dedupe_key: row.dedupe_key,
|
|
171
|
+
delivered_at: row.delivered_at,
|
|
172
|
+
delivery_mode: row.delivery_mode,
|
|
173
|
+
error: row.error,
|
|
174
|
+
id: row.id,
|
|
175
|
+
payload: JSON.parse(row.payload_json),
|
|
176
|
+
side_effect_completed: Boolean(row.side_effect_completed),
|
|
177
|
+
side_effect_started: Boolean(row.side_effect_started),
|
|
178
|
+
signal_type: row.signal_type,
|
|
179
|
+
source_event_id: row.source_event_id,
|
|
180
|
+
source_event_timestamp: row.source_event_timestamp,
|
|
181
|
+
source_session_id: row.source_session_id,
|
|
182
|
+
state: row.state,
|
|
183
|
+
target_session_id: row.target_session_id,
|
|
184
|
+
task_id: row.task_id,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function sessionRow(database, sessionName) {
|
|
188
|
+
const row = database.prepare("select id, name, role from sessions where name = ?").get(sessionName);
|
|
189
|
+
if (!row) {
|
|
190
|
+
throw new RoutedNotificationError(`no session registered with name ${JSON.stringify(sessionName)}`);
|
|
191
|
+
}
|
|
192
|
+
return row;
|
|
193
|
+
}
|
|
194
|
+
function stableJson(value) {
|
|
195
|
+
return JSON.stringify(sortJson(value));
|
|
196
|
+
}
|
|
197
|
+
function sortJson(value) {
|
|
198
|
+
if (Array.isArray(value)) {
|
|
199
|
+
return value.map(sortJson);
|
|
200
|
+
}
|
|
201
|
+
if (value !== null && typeof value === "object") {
|
|
202
|
+
return Object.fromEntries(Object.entries(value)
|
|
203
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
204
|
+
.map(([key, item]) => [key, sortJson(item)]));
|
|
205
|
+
}
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../src/runtime/notifications.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TaskAuditResult } from "./audit.js";
|
|
2
|
+
export type ReplayMode = "compact" | "timeline" | "transcript" | "full-transcript";
|
|
3
|
+
export type ReplayRole = "all" | "worker" | "manager" | "reviewer" | "workerctl";
|
|
4
|
+
export interface ReplayEntry {
|
|
5
|
+
actor: string;
|
|
6
|
+
details: Record<string, unknown>;
|
|
7
|
+
kind: string;
|
|
8
|
+
source: string;
|
|
9
|
+
source_id: number | string | null;
|
|
10
|
+
summary: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ReplayResult {
|
|
14
|
+
entries: ReplayEntry[];
|
|
15
|
+
entry_count: number;
|
|
16
|
+
mode: ReplayMode;
|
|
17
|
+
role: ReplayRole;
|
|
18
|
+
task: TaskAuditResult["task"];
|
|
19
|
+
}
|
|
20
|
+
export declare function replayEntriesFromAudit(audit: TaskAuditResult, options?: {
|
|
21
|
+
mode?: ReplayMode;
|
|
22
|
+
role?: ReplayRole;
|
|
23
|
+
}): ReplayEntry[];
|
|
24
|
+
export declare function replayResultFromAudit(audit: TaskAuditResult, options?: {
|
|
25
|
+
limit?: number | null;
|
|
26
|
+
mode?: ReplayMode;
|
|
27
|
+
role?: ReplayRole;
|
|
28
|
+
}): ReplayResult;
|
|
29
|
+
export declare function renderReplayText(result: ReplayResult): string;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
export function replayEntriesFromAudit(audit, options = {}) {
|
|
2
|
+
const mode = options.mode ?? "timeline";
|
|
3
|
+
const role = options.role ?? "all";
|
|
4
|
+
const entries = [];
|
|
5
|
+
const includeObserves = mode !== "compact";
|
|
6
|
+
for (const command of audit.commands) {
|
|
7
|
+
const [actor, kind, summary] = commandSummary(command);
|
|
8
|
+
if (!roleIncludesActor(role, actor)) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
entries.push({
|
|
12
|
+
actor,
|
|
13
|
+
details: {
|
|
14
|
+
command_id: command.id,
|
|
15
|
+
state: command.state,
|
|
16
|
+
type: command.type,
|
|
17
|
+
},
|
|
18
|
+
kind,
|
|
19
|
+
source: "commands",
|
|
20
|
+
source_id: command.id,
|
|
21
|
+
summary,
|
|
22
|
+
timestamp: command.created_at,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
for (const attempt of audit.command_attempts) {
|
|
26
|
+
if (role !== "all" && role !== "manager") {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
entries.push(commandAttemptEntry(attempt));
|
|
30
|
+
}
|
|
31
|
+
for (const notification of audit.routed_notifications) {
|
|
32
|
+
if (role !== "all" && role !== "manager") {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
entries.push(routedNotificationEntry(notification));
|
|
36
|
+
}
|
|
37
|
+
for (const chain of audit.correlation_chains) {
|
|
38
|
+
if (role !== "all" && role !== "manager") {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
entries.push(correlationChainEntry(audit, chain));
|
|
42
|
+
}
|
|
43
|
+
for (const decision of audit.manager_decisions) {
|
|
44
|
+
if (role !== "all" && role !== "manager") {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
entries.push(managerDecisionEntry(decision));
|
|
48
|
+
}
|
|
49
|
+
if (includeObserves) {
|
|
50
|
+
for (const event of audit.events) {
|
|
51
|
+
if (role !== "all" && role !== "manager") {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const summary = acceptanceCriterionSummary(event);
|
|
55
|
+
if (summary === null) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
entries.push({
|
|
59
|
+
actor: event.actor || "workerctl",
|
|
60
|
+
details: acceptanceCriterionDetails(event),
|
|
61
|
+
kind: "acceptance_criterion",
|
|
62
|
+
source: "events",
|
|
63
|
+
source_id: event.id,
|
|
64
|
+
summary,
|
|
65
|
+
timestamp: event.created_at,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return entries.sort((left, right) => {
|
|
70
|
+
const timestamp = left.timestamp.localeCompare(right.timestamp);
|
|
71
|
+
if (timestamp !== 0) {
|
|
72
|
+
return timestamp;
|
|
73
|
+
}
|
|
74
|
+
const source = left.source.localeCompare(right.source);
|
|
75
|
+
if (source !== 0) {
|
|
76
|
+
return source;
|
|
77
|
+
}
|
|
78
|
+
return String(left.source_id).localeCompare(String(right.source_id));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function replayResultFromAudit(audit, options = {}) {
|
|
82
|
+
const mode = options.mode ?? "timeline";
|
|
83
|
+
const role = options.role ?? "all";
|
|
84
|
+
let entries = replayEntriesFromAudit(audit, { mode, role });
|
|
85
|
+
if (options.limit !== undefined && options.limit !== null) {
|
|
86
|
+
entries = entries.slice(-options.limit);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
entries,
|
|
90
|
+
entry_count: entries.length,
|
|
91
|
+
mode,
|
|
92
|
+
role,
|
|
93
|
+
task: audit.task,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function renderReplayText(result) {
|
|
97
|
+
const lines = [
|
|
98
|
+
`Task: ${result.task.name}`,
|
|
99
|
+
`State: ${result.task.state}`,
|
|
100
|
+
`Mode: ${result.mode}`,
|
|
101
|
+
"",
|
|
102
|
+
];
|
|
103
|
+
const finishEntries = result.entries.filter((entry) => entry.kind === "finish");
|
|
104
|
+
if ((result.task.state === "done" || result.task.state === "failed") && finishEntries.length > 0) {
|
|
105
|
+
const final = finishEntries[finishEntries.length - 1];
|
|
106
|
+
lines.push("Finished:", `- ${final.summary}`, "- Review: conveyor replay <task> --format compact", "- Audit: conveyor mutation-audit <task> --json", "");
|
|
107
|
+
}
|
|
108
|
+
for (const entry of result.entries) {
|
|
109
|
+
const hhmmss = entry.timestamp.split("T", 2).at(1)?.replace(/Z$/, "") ?? entry.timestamp;
|
|
110
|
+
lines.push(`${hhmmss} ${entry.actor.padEnd(16, " ")} ${entry.summary}`);
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
function commandAttemptEntry(attempt) {
|
|
115
|
+
return {
|
|
116
|
+
actor: "dispatch",
|
|
117
|
+
details: {
|
|
118
|
+
attempt_id: attempt.id,
|
|
119
|
+
command_id: attempt.command_id,
|
|
120
|
+
correlation_id: attempt.correlation_id,
|
|
121
|
+
dispatcher_id: attempt.dispatcher_id,
|
|
122
|
+
error: attempt.error,
|
|
123
|
+
result: attempt.result,
|
|
124
|
+
side_effect_completed: attempt.side_effect_completed,
|
|
125
|
+
side_effect_started: attempt.side_effect_started,
|
|
126
|
+
state: attempt.state,
|
|
127
|
+
},
|
|
128
|
+
kind: "command_attempt",
|
|
129
|
+
source: "command_attempts",
|
|
130
|
+
source_id: attempt.id,
|
|
131
|
+
summary: `dispatch attempt ${attempt.state}: ${attempt.command_id}`,
|
|
132
|
+
timestamp: attempt.started_at,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function routedNotificationEntry(notification) {
|
|
136
|
+
return {
|
|
137
|
+
actor: "dispatch",
|
|
138
|
+
details: {
|
|
139
|
+
command_id: notification.command_id,
|
|
140
|
+
consumed_at: notification.consumed_at,
|
|
141
|
+
consumed_by_session_id: notification.consumed_by_session_id,
|
|
142
|
+
consumed_by_session_name: notification.consumed_by_session_name,
|
|
143
|
+
correlation_id: notification.correlation_id,
|
|
144
|
+
delivered_at: notification.delivered_at,
|
|
145
|
+
delivery_mode: notification.delivery_mode,
|
|
146
|
+
notification_id: notification.id,
|
|
147
|
+
signal_type: notification.signal_type,
|
|
148
|
+
source_session_id: notification.source_session_id,
|
|
149
|
+
source_session_name: notification.source_session_name,
|
|
150
|
+
state: notification.state,
|
|
151
|
+
target_session_id: notification.target_session_id,
|
|
152
|
+
target_session_name: notification.target_session_name,
|
|
153
|
+
},
|
|
154
|
+
kind: "routed_notification",
|
|
155
|
+
source: "routed_notifications",
|
|
156
|
+
source_id: notification.id,
|
|
157
|
+
summary: (`dispatch notification ${notification.signal_type}: `
|
|
158
|
+
+ `${notification.state} via ${notification.delivery_mode ?? "unknown"}`),
|
|
159
|
+
timestamp: notification.delivered_at ?? notification.created_at,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function correlationChainEntry(audit, chain) {
|
|
163
|
+
const parts = [chain.command_type, chain.command_state];
|
|
164
|
+
if (chain.command_id === null && chain.source_event_id !== undefined && chain.source_event_id !== null) {
|
|
165
|
+
parts.push(`source event #${chain.source_event_id}`);
|
|
166
|
+
}
|
|
167
|
+
if (chain.manager_decision_id !== null) {
|
|
168
|
+
parts.push(`decision #${chain.manager_decision_id}`);
|
|
169
|
+
}
|
|
170
|
+
if (chain.manager_cycle_id !== null) {
|
|
171
|
+
parts.push(`cycle #${chain.manager_cycle_id}`);
|
|
172
|
+
}
|
|
173
|
+
if (chain.attempt_ids.length > 0) {
|
|
174
|
+
parts.push(`${chain.attempt_ids.length} attempt(s)`);
|
|
175
|
+
}
|
|
176
|
+
if (chain.routed_notification_ids.length > 0) {
|
|
177
|
+
parts.push(`${chain.routed_notification_ids.length} notification(s)`);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
actor: "dispatch",
|
|
181
|
+
details: chain,
|
|
182
|
+
kind: "correlation_chain",
|
|
183
|
+
source: "correlation_chains",
|
|
184
|
+
source_id: chain.command_id ?? chain.correlation_id ?? chain.source_event_id ?? null,
|
|
185
|
+
summary: parts.join(" -> "),
|
|
186
|
+
timestamp: commandCreatedAt(audit, chain.command_id) ?? chain.created_at ?? audit.task.created_at,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function managerDecisionEntry(decision) {
|
|
190
|
+
return {
|
|
191
|
+
actor: "manager",
|
|
192
|
+
details: {
|
|
193
|
+
decision: decision.decision,
|
|
194
|
+
manager_cycle_id: decision.manager_cycle_id,
|
|
195
|
+
},
|
|
196
|
+
kind: "decision",
|
|
197
|
+
source: "manager_decisions",
|
|
198
|
+
source_id: decision.id,
|
|
199
|
+
summary: `decision ${decision.decision}: ${shorten(decision.reason)}`,
|
|
200
|
+
timestamp: decision.created_at,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function commandCreatedAt(audit, commandId) {
|
|
204
|
+
if (commandId === null) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return audit.commands.find((command) => command.id === commandId)?.created_at ?? null;
|
|
208
|
+
}
|
|
209
|
+
function commandSummary(command) {
|
|
210
|
+
const payload = command.payload;
|
|
211
|
+
const result = command.result ?? {};
|
|
212
|
+
if (command.type === "promote") {
|
|
213
|
+
return [
|
|
214
|
+
"system",
|
|
215
|
+
"command",
|
|
216
|
+
`promoted worker ${payload.worker ?? result.worker} and launched manager ${result.manager_session}`,
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
if (command.type === "task_interrupt") {
|
|
220
|
+
const followup = stringValue(result.followup ?? payload.followup) ?? "interrupt";
|
|
221
|
+
return ["manager -> worker", "command", `sent interrupt: ${shorten(followup)}`];
|
|
222
|
+
}
|
|
223
|
+
if (command.type === "task_nudge") {
|
|
224
|
+
const message = stringValue(result.message ?? payload.message) ?? "nudge";
|
|
225
|
+
return ["manager -> worker", "command", `sent nudge: ${shorten(message)}`];
|
|
226
|
+
}
|
|
227
|
+
if (command.type === "finish_task") {
|
|
228
|
+
const reason = stringValue(result.reason ?? payload.reason) ?? "task finished";
|
|
229
|
+
const suffix = result.stop_manager ? "manager stopped" : "manager left open";
|
|
230
|
+
return ["manager", "finish", `finished task: ${shorten(reason)} (${suffix})`];
|
|
231
|
+
}
|
|
232
|
+
if (command.type === "close_manager") {
|
|
233
|
+
const reason = stringValue(result.reason ?? payload.reason) ?? "manager closed";
|
|
234
|
+
return ["workerctl", "command", `closed manager: ${shorten(reason)}`];
|
|
235
|
+
}
|
|
236
|
+
return ["workerctl", "command", `${command.type} ${command.state}`];
|
|
237
|
+
}
|
|
238
|
+
function acceptanceCriterionSummary(event) {
|
|
239
|
+
const payload = event.payload;
|
|
240
|
+
const criterionId = payload.criterion_id;
|
|
241
|
+
const criterionLabel = criterionId !== undefined && criterionId !== null ? `#${criterionId}` : "<unknown>";
|
|
242
|
+
const status = stringValue(payload.status);
|
|
243
|
+
const criterion = stringValue(payload.criterion) ?? "";
|
|
244
|
+
const previousStatus = stringValue(payload.previous_status);
|
|
245
|
+
const transition = previousStatus && status ? ` (${previousStatus} -> ${status})` : "";
|
|
246
|
+
if (event.type === "acceptance_criterion_added") {
|
|
247
|
+
if (status === "proposed") {
|
|
248
|
+
return `proposed criterion ${criterionLabel}: ${shorten(criterion)}`;
|
|
249
|
+
}
|
|
250
|
+
if (status === "accepted") {
|
|
251
|
+
return `accepted criterion ${criterionLabel}: ${shorten(criterion)}`;
|
|
252
|
+
}
|
|
253
|
+
if (status === "satisfied") {
|
|
254
|
+
const proof = stringValue(payload.proof);
|
|
255
|
+
return proof
|
|
256
|
+
? `satisfied criterion ${criterionLabel}: proof recorded (${shorten(proof)})`
|
|
257
|
+
: `satisfied criterion ${criterionLabel}: proof recorded`;
|
|
258
|
+
}
|
|
259
|
+
if (status === "deferred") {
|
|
260
|
+
return `deferred criterion ${criterionLabel}: ${shorten(stringValue(payload.rationale) ?? criterion)}`;
|
|
261
|
+
}
|
|
262
|
+
if (status === "rejected") {
|
|
263
|
+
return `rejected criterion ${criterionLabel}: ${shorten(stringValue(payload.rationale) ?? criterion)}`;
|
|
264
|
+
}
|
|
265
|
+
return `added ${status ?? "unknown"} criterion ${criterionLabel}: ${shorten(criterion)}`;
|
|
266
|
+
}
|
|
267
|
+
if (event.type !== "acceptance_criterion_updated") {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
if (status === "accepted") {
|
|
271
|
+
return `accepted criterion ${criterionLabel}${transition}: ${shorten(criterion)}`;
|
|
272
|
+
}
|
|
273
|
+
if (status === "satisfied") {
|
|
274
|
+
const proof = stringValue(payload.proof);
|
|
275
|
+
return proof
|
|
276
|
+
? `satisfied criterion ${criterionLabel}${transition}: proof recorded (${shorten(proof)})`
|
|
277
|
+
: `satisfied criterion ${criterionLabel}${transition}: proof recorded`;
|
|
278
|
+
}
|
|
279
|
+
if (status === "deferred") {
|
|
280
|
+
return `deferred criterion ${criterionLabel}${transition}: ${shorten(stringValue(payload.rationale) ?? criterion)}`;
|
|
281
|
+
}
|
|
282
|
+
if (status === "rejected") {
|
|
283
|
+
return `rejected criterion ${criterionLabel}${transition}: ${shorten(stringValue(payload.rationale) ?? criterion)}`;
|
|
284
|
+
}
|
|
285
|
+
const fallbackTransition = previousStatus ? `${previousStatus} -> ${status}` : status ?? "updated";
|
|
286
|
+
return `updated criterion ${criterionLabel}: ${fallbackTransition}`;
|
|
287
|
+
}
|
|
288
|
+
function acceptanceCriterionDetails(event) {
|
|
289
|
+
const details = { event_type: event.type };
|
|
290
|
+
const keys = [
|
|
291
|
+
"criterion_id",
|
|
292
|
+
"criterion",
|
|
293
|
+
"status",
|
|
294
|
+
"previous_status",
|
|
295
|
+
"source",
|
|
296
|
+
"task_id",
|
|
297
|
+
"proof",
|
|
298
|
+
"previous_proof",
|
|
299
|
+
"rationale",
|
|
300
|
+
"previous_rationale",
|
|
301
|
+
"evidence",
|
|
302
|
+
"previous_evidence",
|
|
303
|
+
"created",
|
|
304
|
+
];
|
|
305
|
+
for (const key of keys) {
|
|
306
|
+
if (key in event.payload) {
|
|
307
|
+
details[key] = event.payload[key];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return details;
|
|
311
|
+
}
|
|
312
|
+
function roleIncludesActor(role, actor) {
|
|
313
|
+
if (role === "all") {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
if (actor === role || actor === `manager -> ${role}` || actor === `${role} -> manager`) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
return role === "manager" && (actor === "workerctl" || actor === "system");
|
|
320
|
+
}
|
|
321
|
+
function shorten(value, maxLength = 220) {
|
|
322
|
+
const text = value.split(/\s+/).filter(Boolean).join(" ");
|
|
323
|
+
if (text.length <= maxLength) {
|
|
324
|
+
return text;
|
|
325
|
+
}
|
|
326
|
+
return `${text.slice(0, maxLength - 1).trimEnd()}...`;
|
|
327
|
+
}
|
|
328
|
+
function stringValue(value) {
|
|
329
|
+
return typeof value === "string" ? value : null;
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=replay.js.map
|