patchrelay 0.35.11 → 0.35.13
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 +41 -9
- package/dist/build-info.json +3 -3
- package/dist/cli/args.js +19 -1
- package/dist/cli/commands/issues.js +18 -56
- package/dist/cli/commands/watch.js +5 -0
- package/dist/cli/data.js +160 -47
- package/dist/cli/formatters/text.js +51 -90
- package/dist/cli/help.js +15 -8
- package/dist/cli/index.js +3 -58
- package/dist/cli/operator-client.js +0 -82
- package/dist/cli/watch/App.js +21 -12
- package/dist/cli/watch/HelpBar.js +3 -3
- package/dist/cli/watch/IssueDetailView.js +63 -130
- package/dist/cli/watch/IssueRow.js +82 -27
- package/dist/cli/watch/StatusBar.js +8 -4
- package/dist/cli/watch/detail-rows.js +589 -0
- package/dist/cli/watch/render-rich-text.js +226 -0
- package/dist/cli/watch/state-visualization.js +48 -23
- package/dist/cli/watch/timeline-builder.js +2 -1
- package/dist/cli/watch/use-detail-stream.js +10 -104
- package/dist/cli/watch/use-watch-stream.js +11 -102
- package/dist/cli/watch/watch-state.js +129 -56
- package/dist/codex-thread-utils.js +3 -0
- package/dist/db/migrations.js +239 -2
- package/dist/db.js +628 -39
- package/dist/github-app-token.js +7 -0
- package/dist/github-failure-context.js +44 -1
- package/dist/github-rollup.js +47 -0
- package/dist/github-webhook-handler.js +423 -52
- package/dist/github-webhooks.js +7 -0
- package/dist/http.js +12 -264
- package/dist/idle-reconciliation.js +268 -76
- package/dist/issue-query-service.js +221 -129
- package/dist/issue-session-events.js +151 -0
- package/dist/issue-session.js +99 -0
- package/dist/linear-client.js +39 -25
- package/dist/linear-session-reporting.js +12 -0
- package/dist/linear-session-sync.js +253 -24
- package/dist/linear-workflow.js +33 -0
- package/dist/merge-queue-protocol.js +0 -51
- package/dist/preflight.js +1 -4
- package/dist/queue-health-monitor.js +11 -7
- package/dist/run-orchestrator.js +1364 -147
- package/dist/run-reporting.js +5 -3
- package/dist/service.js +279 -102
- package/dist/status-note.js +56 -0
- package/dist/waiting-reason.js +65 -0
- package/dist/webhook-handler.js +270 -79
- package/package.json +3 -2
- package/dist/cli/commands/feed.js +0 -60
- package/dist/cli/watch/FeedView.js +0 -28
- package/dist/cli/watch/use-feed-stream.js +0 -92
package/dist/github-webhooks.js
CHANGED
|
@@ -64,6 +64,10 @@ function normalizePullRequestEvent(payload, repoFullName) {
|
|
|
64
64
|
prNumber: pr.number,
|
|
65
65
|
prUrl: pr.html_url,
|
|
66
66
|
prState,
|
|
67
|
+
prAuthorLogin: pr.user?.login ?? undefined,
|
|
68
|
+
prLabels: Array.isArray(pr.labels)
|
|
69
|
+
? pr.labels.map((label) => label?.name).filter((label) => typeof label === "string" && label.trim().length > 0)
|
|
70
|
+
: undefined,
|
|
67
71
|
};
|
|
68
72
|
}
|
|
69
73
|
function normalizePullRequestReviewEvent(payload, repoFullName) {
|
|
@@ -97,8 +101,11 @@ function normalizePullRequestReviewEvent(payload, repoFullName) {
|
|
|
97
101
|
prNumber: pr.number,
|
|
98
102
|
prUrl: pr.html_url,
|
|
99
103
|
prState: "open",
|
|
104
|
+
prAuthorLogin: pr.user?.login ?? undefined,
|
|
100
105
|
reviewState,
|
|
101
106
|
reviewBody: review.body ?? undefined,
|
|
107
|
+
reviewId: typeof review.id === "number" ? review.id : undefined,
|
|
108
|
+
reviewCommitId: typeof review.commit_id === "string" ? review.commit_id : undefined,
|
|
102
109
|
reviewerName: review.user?.login ?? undefined,
|
|
103
110
|
};
|
|
104
111
|
}
|
package/dist/http.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import fastify from "fastify";
|
|
2
2
|
import rawBody from "fastify-raw-body";
|
|
3
3
|
import { getBuildInfo } from "./build-info.js";
|
|
4
|
-
import { matchesOperatorFeedEvent } from "./operator-feed.js";
|
|
5
|
-
import { buildStateHistory } from "./cli/watch/history-builder.js";
|
|
6
|
-
import { buildPatchRelayQueueObservations, buildPatchRelayStateGraph } from "./cli/watch/state-visualization.js";
|
|
7
4
|
export async function buildHttpServer(config, service, logger) {
|
|
8
5
|
const buildInfo = getBuildInfo();
|
|
9
6
|
const loopbackBind = isLoopbackBind(config.server.bind);
|
|
@@ -247,6 +244,9 @@ export async function buildHttpServer(config, service, logger) {
|
|
|
247
244
|
});
|
|
248
245
|
}
|
|
249
246
|
if (managementRoutesEnabled) {
|
|
247
|
+
app.get("/api/issues", async (_request, reply) => {
|
|
248
|
+
return reply.send({ ok: true, issues: service.listTrackedIssues() });
|
|
249
|
+
});
|
|
250
250
|
app.get("/api/issues/:issueKey", async (request, reply) => {
|
|
251
251
|
const issueKey = request.params.issueKey;
|
|
252
252
|
const result = await service.getIssueOverview(issueKey);
|
|
@@ -255,22 +255,6 @@ export async function buildHttpServer(config, service, logger) {
|
|
|
255
255
|
}
|
|
256
256
|
return reply.send({ ok: true, ...result });
|
|
257
257
|
});
|
|
258
|
-
app.get("/api/issues/:issueKey/report", async (request, reply) => {
|
|
259
|
-
const issueKey = request.params.issueKey;
|
|
260
|
-
const result = await service.getIssueReport(issueKey);
|
|
261
|
-
if (!result) {
|
|
262
|
-
return reply.code(404).send({ ok: false, reason: "issue_not_found" });
|
|
263
|
-
}
|
|
264
|
-
return reply.send({ ok: true, ...result });
|
|
265
|
-
});
|
|
266
|
-
app.get("/api/issues/:issueKey/timeline", async (request, reply) => {
|
|
267
|
-
const issueKey = request.params.issueKey;
|
|
268
|
-
const result = await service.getIssueTimeline(issueKey);
|
|
269
|
-
if (!result) {
|
|
270
|
-
return reply.code(404).send({ ok: false, reason: "issue_not_found" });
|
|
271
|
-
}
|
|
272
|
-
return reply.send({ ok: true, ...result });
|
|
273
|
-
});
|
|
274
258
|
app.get("/api/issues/:issueKey/live", async (request, reply) => {
|
|
275
259
|
const issueKey = request.params.issueKey;
|
|
276
260
|
const result = await service.getActiveRunStatus(issueKey);
|
|
@@ -279,14 +263,6 @@ export async function buildHttpServer(config, service, logger) {
|
|
|
279
263
|
}
|
|
280
264
|
return reply.send({ ok: true, ...result });
|
|
281
265
|
});
|
|
282
|
-
app.get("/api/issues/:issueKey/runs/:runId/events", async (request, reply) => {
|
|
283
|
-
const { issueKey, runId } = request.params;
|
|
284
|
-
const result = await service.getRunEvents(issueKey, Number(runId));
|
|
285
|
-
if (!result) {
|
|
286
|
-
return reply.code(404).send({ ok: false, reason: "run_not_found" });
|
|
287
|
-
}
|
|
288
|
-
return reply.send({ ok: true, ...result });
|
|
289
|
-
});
|
|
290
266
|
app.get("/api/issues/:issueKey/session-url", async (request, reply) => {
|
|
291
267
|
const issueKey = request.params.issueKey;
|
|
292
268
|
const ttlSeconds = getPositiveIntegerQueryParam(request, "ttlSeconds");
|
|
@@ -338,92 +314,6 @@ export async function buildHttpServer(config, service, logger) {
|
|
|
338
314
|
}
|
|
339
315
|
return reply.send({ ok: true, ...result });
|
|
340
316
|
});
|
|
341
|
-
app.get("/api/feed", async (request, reply) => {
|
|
342
|
-
const feedQuery = {
|
|
343
|
-
limit: getPositiveIntegerQueryParam(request, "limit") ?? 50,
|
|
344
|
-
...readFeedQueryFilters(request),
|
|
345
|
-
};
|
|
346
|
-
if (getQueryParam(request, "follow") !== "1") {
|
|
347
|
-
return reply.send({ ok: true, events: service.listOperatorFeed(feedQuery) });
|
|
348
|
-
}
|
|
349
|
-
reply.hijack();
|
|
350
|
-
reply.raw.writeHead(200, {
|
|
351
|
-
"content-type": "text/event-stream; charset=utf-8",
|
|
352
|
-
"cache-control": "no-cache, no-transform",
|
|
353
|
-
connection: "keep-alive",
|
|
354
|
-
"x-accel-buffering": "no",
|
|
355
|
-
});
|
|
356
|
-
const writeEvent = (event) => {
|
|
357
|
-
reply.raw.write(`event: feed\n`);
|
|
358
|
-
reply.raw.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
359
|
-
};
|
|
360
|
-
for (const event of service.listOperatorFeed(feedQuery)) {
|
|
361
|
-
writeEvent(event);
|
|
362
|
-
}
|
|
363
|
-
const cleanup = () => {
|
|
364
|
-
clearInterval(keepAlive);
|
|
365
|
-
unsubscribe();
|
|
366
|
-
if (!reply.raw.destroyed)
|
|
367
|
-
reply.raw.end();
|
|
368
|
-
};
|
|
369
|
-
const unsubscribe = service.subscribeOperatorFeed((event) => {
|
|
370
|
-
if (!matchesOperatorFeedEvent(event, feedQuery)) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
writeEvent(event);
|
|
374
|
-
});
|
|
375
|
-
const keepAlive = setInterval(() => {
|
|
376
|
-
reply.raw.write(": keepalive\n\n");
|
|
377
|
-
}, 15000);
|
|
378
|
-
reply.raw.on("error", cleanup);
|
|
379
|
-
request.raw.on("close", cleanup);
|
|
380
|
-
});
|
|
381
|
-
app.get("/api/watch/issues", async (_request, reply) => {
|
|
382
|
-
return reply.send({ ok: true, issues: service.listTrackedIssues() });
|
|
383
|
-
});
|
|
384
|
-
app.get("/api/watch", async (request, reply) => {
|
|
385
|
-
reply.hijack();
|
|
386
|
-
reply.raw.writeHead(200, {
|
|
387
|
-
"content-type": "text/event-stream; charset=utf-8",
|
|
388
|
-
"cache-control": "no-cache, no-transform",
|
|
389
|
-
connection: "keep-alive",
|
|
390
|
-
"x-accel-buffering": "no",
|
|
391
|
-
});
|
|
392
|
-
const writeSse = (eventType, data) => {
|
|
393
|
-
reply.raw.write(`event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
394
|
-
};
|
|
395
|
-
// Send initial issue snapshot
|
|
396
|
-
writeSse("issues", service.listTrackedIssues());
|
|
397
|
-
// Stream operator feed events
|
|
398
|
-
const issueFilter = getQueryParam(request, "issue");
|
|
399
|
-
const unsubscribeFeed = service.subscribeOperatorFeed((event) => {
|
|
400
|
-
if (issueFilter && event.issueKey !== issueFilter) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
writeSse("feed", event);
|
|
404
|
-
});
|
|
405
|
-
// When filtered to a specific issue, also stream codex notifications
|
|
406
|
-
const unsubscribeCodex = issueFilter
|
|
407
|
-
? service.subscribeCodexNotifications((event) => {
|
|
408
|
-
if (event.issueKey !== issueFilter) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
writeSse("codex", { method: event.method, params: event.params });
|
|
412
|
-
})
|
|
413
|
-
: undefined;
|
|
414
|
-
const cleanup = () => {
|
|
415
|
-
clearInterval(keepAlive);
|
|
416
|
-
unsubscribeFeed();
|
|
417
|
-
unsubscribeCodex?.();
|
|
418
|
-
if (!reply.raw.destroyed)
|
|
419
|
-
reply.raw.end();
|
|
420
|
-
};
|
|
421
|
-
const keepAlive = setInterval(() => {
|
|
422
|
-
reply.raw.write(": keepalive\n\n");
|
|
423
|
-
}, 15000);
|
|
424
|
-
reply.raw.on("error", cleanup);
|
|
425
|
-
request.raw.on("close", cleanup);
|
|
426
|
-
});
|
|
427
317
|
app.get("/api/installations", async (_request, reply) => {
|
|
428
318
|
return reply.send({ ok: true, installations: service.listLinearInstallations() });
|
|
429
319
|
});
|
|
@@ -515,22 +405,6 @@ function getQueryParam(request, key) {
|
|
|
515
405
|
const value = request.query?.[key];
|
|
516
406
|
return typeof value === "string" ? value : undefined;
|
|
517
407
|
}
|
|
518
|
-
function readFeedQueryFilters(request) {
|
|
519
|
-
const issueKey = getQueryParam(request, "issue")?.trim() || undefined;
|
|
520
|
-
const projectId = getQueryParam(request, "project")?.trim() || undefined;
|
|
521
|
-
const kind = (getQueryParam(request, "kind")?.trim() || undefined);
|
|
522
|
-
const stage = getQueryParam(request, "stage")?.trim() || undefined;
|
|
523
|
-
const status = getQueryParam(request, "status")?.trim() || undefined;
|
|
524
|
-
const workflowId = getQueryParam(request, "workflow")?.trim() || undefined;
|
|
525
|
-
return {
|
|
526
|
-
...(issueKey ? { issueKey } : {}),
|
|
527
|
-
...(projectId ? { projectId } : {}),
|
|
528
|
-
...(kind ? { kind } : {}),
|
|
529
|
-
...(stage ? { stage } : {}),
|
|
530
|
-
...(status ? { status } : {}),
|
|
531
|
-
...(workflowId ? { workflowId } : {}),
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
408
|
function getPositiveIntegerQueryParam(request, key) {
|
|
535
409
|
const value = getQueryParam(request, key);
|
|
536
410
|
if (!value || !/^\d+$/.test(value)) {
|
|
@@ -596,6 +470,7 @@ function renderAgentSessionStatusPage(params) {
|
|
|
596
470
|
const commandCount = params.sessionStatus.liveThread?.commandCount ?? params.sessionStatus.latestReportSummary?.commandCount ?? 0;
|
|
597
471
|
const fileChangeCount = params.sessionStatus.liveThread?.fileChangeCount ?? params.sessionStatus.latestReportSummary?.fileChangeCount ?? 0;
|
|
598
472
|
const toolCallCount = params.sessionStatus.liveThread?.toolCallCount ?? params.sessionStatus.latestReportSummary?.toolCallCount ?? 0;
|
|
473
|
+
const sessionState = params.sessionStatus.issue.sessionState ?? "unknown";
|
|
599
474
|
const factoryState = params.sessionStatus.issue.factoryState ?? "unknown";
|
|
600
475
|
const linearState = params.sessionStatus.issue.currentLinearState ?? "unknown";
|
|
601
476
|
const prState = params.sessionStatus.issue.prState ?? "unknown";
|
|
@@ -603,23 +478,8 @@ function renderAgentSessionStatusPage(params) {
|
|
|
603
478
|
const checkState = params.sessionStatus.issue.prCheckStatus ?? "unknown";
|
|
604
479
|
const ciAttempts = params.sessionStatus.issue.ciRepairAttempts ?? 0;
|
|
605
480
|
const queueAttempts = params.sessionStatus.issue.queueRepairAttempts ?? 0;
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
currentFactoryState: factoryState,
|
|
609
|
-
activeRunId: params.sessionStatus.activeRunId ?? null,
|
|
610
|
-
...(params.sessionStatus.feedEvents ? { feedEvents: params.sessionStatus.feedEvents } : {}),
|
|
611
|
-
runs: params.sessionStatus.runs,
|
|
612
|
-
});
|
|
613
|
-
const graph = buildPatchRelayStateGraph(history, factoryState);
|
|
614
|
-
const queueObservations = buildPatchRelayQueueObservations({
|
|
615
|
-
factoryState,
|
|
616
|
-
...(params.sessionStatus.activeRun?.runType ? { activeRunType: params.sessionStatus.activeRun.runType } : {}),
|
|
617
|
-
...(params.sessionStatus.issue.prNumber !== undefined ? { prNumber: params.sessionStatus.issue.prNumber } : {}),
|
|
618
|
-
...(params.sessionStatus.issue.prReviewState ? { prReviewState: params.sessionStatus.issue.prReviewState } : {}),
|
|
619
|
-
}, normalizeFeedEvents(params.sessionStatus.feedEvents));
|
|
620
|
-
const pathHtml = renderStatePath(history, factoryState);
|
|
621
|
-
const graphHtml = renderStateGraph(graph.main, graph.prLoops, graph.queueLoop, graph.exits);
|
|
622
|
-
const observationsHtml = renderObservationList(queueObservations);
|
|
481
|
+
const waitingReason = params.sessionStatus.issue.waitingReason ?? "No outstanding wait reason.";
|
|
482
|
+
const lastWakeReason = params.sessionStatus.issue.lastWakeReason ?? "unknown";
|
|
623
483
|
return `<!doctype html>
|
|
624
484
|
<html lang="en">
|
|
625
485
|
<head>
|
|
@@ -693,7 +553,9 @@ function renderAgentSessionStatusPage(params) {
|
|
|
693
553
|
${issueUrl ? `<p><a href="${escapeHtml(issueUrl)}" target="_blank" rel="noopener noreferrer">Open issue in Linear</a></p>` : ""}
|
|
694
554
|
${prUrl ? `<p><a href="${escapeHtml(prUrl)}" target="_blank" rel="noopener noreferrer">Open pull request ${escapeHtml(prLabel ?? "")}</a></p>` : ""}
|
|
695
555
|
<div class="chips">
|
|
696
|
-
<span class="chip"><strong>
|
|
556
|
+
<span class="chip"><strong>Session:</strong> <code>${escapeHtml(sessionState)}</code></span>
|
|
557
|
+
<span class="chip"><strong>Waiting reason:</strong> <code>${escapeHtml(waitingReason)}</code></span>
|
|
558
|
+
<span class="chip"><strong>Debug stage:</strong> <code>${escapeHtml(factoryState)}</code></span>
|
|
697
559
|
<span class="chip"><strong>Linear:</strong> <code>${escapeHtml(linearState)}</code></span>
|
|
698
560
|
<span class="chip"><strong>Active:</strong> ${activeStage}</span>
|
|
699
561
|
<span class="chip"><strong>Latest:</strong> ${latestStage}</span>
|
|
@@ -706,14 +568,8 @@ function renderAgentSessionStatusPage(params) {
|
|
|
706
568
|
<tr><th>Pull request</th><td>${escapeHtml(prLabel ?? "none")} (${escapeHtml(prState)})</td></tr>
|
|
707
569
|
<tr><th>Review</th><td>${escapeHtml(reviewState)}</td></tr>
|
|
708
570
|
<tr><th>Checks</th><td>${escapeHtml(checkState)}</td></tr>
|
|
709
|
-
<tr><th>
|
|
710
|
-
<tr><th>
|
|
711
|
-
<tr><th>Last queue signal</th><td><code>${escapeHtml(queueProtocol?.lastQueueSignalAt ?? queueProtocol?.lastFailureAt ?? "none")}</code></td></tr>
|
|
712
|
-
<tr><th>Last queue incident</th><td>${queueProtocol?.lastIncidentUrl
|
|
713
|
-
? `<a href="${escapeHtml(queueProtocol.lastIncidentUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtml(queueProtocol.lastIncidentId ?? queueProtocol.lastIncidentUrl)}</a>`
|
|
714
|
-
: escapeHtml(queueProtocol?.lastIncidentId ?? "none")}</td></tr>
|
|
715
|
-
<tr><th>Queue failure class</th><td><code>${escapeHtml(queueProtocol?.lastIncidentFailureClass ?? "unknown")}</code></td></tr>
|
|
716
|
-
<tr><th>Queue incident summary</th><td>${escapeHtml(queueProtocol?.lastIncidentSummary ?? "none")}</td></tr>
|
|
571
|
+
<tr><th>Waiting reason</th><td>${escapeHtml(waitingReason)}</td></tr>
|
|
572
|
+
<tr><th>Last wake</th><td><code>${escapeHtml(lastWakeReason)}</code></td></tr>
|
|
717
573
|
<tr><th>Latest plan</th><td>${escapeHtml(latestPlan)}</td></tr>
|
|
718
574
|
<tr><th>Active command</th><td><code>${escapeHtml(activeCommand)}</code></td></tr>
|
|
719
575
|
<tr><th>Latest summary</th><td>${escapeHtml(latestAgentMessage)}</td></tr>
|
|
@@ -725,24 +581,7 @@ function renderAgentSessionStatusPage(params) {
|
|
|
725
581
|
<span class="chip"><strong>File changes:</strong> ${escapeHtml(String(fileChangeCount))}</span>
|
|
726
582
|
<span class="chip"><strong>Tool calls:</strong> ${escapeHtml(String(toolCallCount))}</span>
|
|
727
583
|
<span class="chip"><strong>CI repairs:</strong> ${escapeHtml(String(ciAttempts))}</span>
|
|
728
|
-
<span class="chip"><strong>
|
|
729
|
-
</div>
|
|
730
|
-
<div class="section">
|
|
731
|
-
<h2>State Path</h2>
|
|
732
|
-
<div class="grid">
|
|
733
|
-
<div class="card">
|
|
734
|
-
<h3>Native Graph</h3>
|
|
735
|
-
${graphHtml}
|
|
736
|
-
</div>
|
|
737
|
-
<div class="card">
|
|
738
|
-
<h3>Queue Observation</h3>
|
|
739
|
-
${observationsHtml}
|
|
740
|
-
</div>
|
|
741
|
-
</div>
|
|
742
|
-
<div class="card" style="margin-top: 18px;">
|
|
743
|
-
<h3>Observed Path</h3>
|
|
744
|
-
${pathHtml}
|
|
745
|
-
</div>
|
|
584
|
+
<span class="chip"><strong>Steward repairs:</strong> ${escapeHtml(String(queueAttempts))}</span>
|
|
746
585
|
</div>
|
|
747
586
|
<div class="section">
|
|
748
587
|
<h2>Recent Stages</h2>
|
|
@@ -791,94 +630,3 @@ function formatStageRow(run) {
|
|
|
791
630
|
const endedAt = run.endedAt ?? "-";
|
|
792
631
|
return `<tr><td><code>${escapeHtml(runType)}</code></td><td>${escapeHtml(status)}</td><td><code>${escapeHtml(startedAt)}</code></td><td><code>${escapeHtml(endedAt)}</code></td></tr>`;
|
|
793
632
|
}
|
|
794
|
-
function normalizeFeedEvents(feedEvents) {
|
|
795
|
-
return (feedEvents ?? []).map((event, index) => ({
|
|
796
|
-
id: event.id ?? -(index + 1),
|
|
797
|
-
at: event.at,
|
|
798
|
-
level: event.level === "warn" || event.level === "error" ? event.level : "info",
|
|
799
|
-
kind: event.kind === "service"
|
|
800
|
-
|| event.kind === "webhook"
|
|
801
|
-
|| event.kind === "agent"
|
|
802
|
-
|| event.kind === "comment"
|
|
803
|
-
|| event.kind === "stage"
|
|
804
|
-
|| event.kind === "turn"
|
|
805
|
-
|| event.kind === "workflow"
|
|
806
|
-
|| event.kind === "hook"
|
|
807
|
-
|| event.kind === "github"
|
|
808
|
-
|| event.kind === "linear"
|
|
809
|
-
? event.kind
|
|
810
|
-
: "service",
|
|
811
|
-
summary: event.summary ?? "",
|
|
812
|
-
...(event.detail ? { detail: event.detail } : {}),
|
|
813
|
-
...(event.issueKey ? { issueKey: event.issueKey } : {}),
|
|
814
|
-
...(event.projectId ? { projectId: event.projectId } : {}),
|
|
815
|
-
...(event.stage ? { stage: event.stage } : {}),
|
|
816
|
-
...(event.status ? { status: event.status } : {}),
|
|
817
|
-
...(event.workflowId ? { workflowId: event.workflowId } : {}),
|
|
818
|
-
...(event.nextStage ? { nextStage: event.nextStage } : {}),
|
|
819
|
-
}));
|
|
820
|
-
}
|
|
821
|
-
function buildPublicStateHistory(params) {
|
|
822
|
-
const runs = params.runs.flatMap((entry, index) => {
|
|
823
|
-
if (!entry.run?.runType || !entry.run?.status || !entry.run?.startedAt) {
|
|
824
|
-
return [];
|
|
825
|
-
}
|
|
826
|
-
return [{
|
|
827
|
-
id: entry.run.id ?? index + 1,
|
|
828
|
-
runType: entry.run.runType,
|
|
829
|
-
status: entry.run.status,
|
|
830
|
-
startedAt: entry.run.startedAt,
|
|
831
|
-
endedAt: entry.run.endedAt,
|
|
832
|
-
...(entry.report ? {
|
|
833
|
-
report: {
|
|
834
|
-
runType: entry.run.runType,
|
|
835
|
-
status: entry.run.status,
|
|
836
|
-
prompt: "",
|
|
837
|
-
assistantMessages: entry.report.assistantMessages ?? [],
|
|
838
|
-
plans: [],
|
|
839
|
-
reasoning: [],
|
|
840
|
-
commands: (entry.report.commands ?? []),
|
|
841
|
-
fileChanges: (entry.report.fileChanges ?? []),
|
|
842
|
-
toolCalls: [],
|
|
843
|
-
eventCounts: {},
|
|
844
|
-
},
|
|
845
|
-
} : {}),
|
|
846
|
-
}];
|
|
847
|
-
});
|
|
848
|
-
return buildStateHistory(runs, normalizeFeedEvents(params.feedEvents), params.currentFactoryState, params.activeRunId);
|
|
849
|
-
}
|
|
850
|
-
function renderStateGraph(main, prLoops, queueLoop, exits) {
|
|
851
|
-
return [
|
|
852
|
-
renderGraphRow("main", main, true),
|
|
853
|
-
renderGraphRow("pr loops", prLoops, false),
|
|
854
|
-
renderGraphRow("queue loop", queueLoop, false),
|
|
855
|
-
renderGraphRow("exits", exits, false),
|
|
856
|
-
].join("");
|
|
857
|
-
}
|
|
858
|
-
function renderGraphRow(label, nodes, withConnectors) {
|
|
859
|
-
const items = nodes.map((node, index) => {
|
|
860
|
-
const connector = withConnectors && index > 0 ? '<span class="graph-connector">→</span>' : "";
|
|
861
|
-
return `${connector}<span class="node ${escapeHtml(node.status)}">${escapeHtml(node.label)}</span>`;
|
|
862
|
-
}).join("");
|
|
863
|
-
return `<div class="graph-row"><strong>${escapeHtml(label)}:</strong> ${items}</div>`;
|
|
864
|
-
}
|
|
865
|
-
function renderObservationList(observations) {
|
|
866
|
-
if (observations.length === 0) {
|
|
867
|
-
return '<p>No queue observation is available yet.</p>';
|
|
868
|
-
}
|
|
869
|
-
return `<ul class="observation-list">${observations.map((observation) => `<li class="tone-${escapeHtml(observation.tone)}">${escapeHtml(observation.text)}</li>`).join("")}</ul>`;
|
|
870
|
-
}
|
|
871
|
-
function renderStatePath(history, currentFactoryState) {
|
|
872
|
-
if (history.length === 0) {
|
|
873
|
-
return `<p>Current native state: <code>${escapeHtml(currentFactoryState)}</code>.</p>`;
|
|
874
|
-
}
|
|
875
|
-
const items = [];
|
|
876
|
-
for (const node of history) {
|
|
877
|
-
items.push(`<li><code>${escapeHtml(node.state)}</code>${node.reason ? ` — ${escapeHtml(node.reason)}` : ""}${node.isCurrent ? " (current)" : ""}</li>`);
|
|
878
|
-
for (const trip of node.sideTrips) {
|
|
879
|
-
const returnText = trip.returnState ? ` → ${trip.returnedAt ? escapeHtml(trip.returnState) : escapeHtml(trip.returnState)}` : "";
|
|
880
|
-
items.push(`<li><code>${escapeHtml(trip.state)}</code> side trip${trip.reason ? ` — ${escapeHtml(trip.reason)}` : ""}${returnText ? ` ${returnText}` : ""}</li>`);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return `<ul class="path-list">${items.join("")}</ul>`;
|
|
884
|
-
}
|