@h-rig/run-plugin 0.0.6-alpha.186
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 +1 -0
- package/dist/src/plugin.d.ts +4 -0
- package/dist/src/plugin.js +4319 -0
- package/dist/src/read-model/inspect-command.d.ts +19 -0
- package/dist/src/read-model/inspect-command.js +341 -0
- package/dist/src/read-model/plugin.d.ts +4 -0
- package/dist/src/read-model/plugin.js +2550 -0
- package/dist/src/read-model/read-model-backend/diagnostics.d.ts +9 -0
- package/dist/src/read-model/read-model-backend/diagnostics.js +51 -0
- package/dist/src/read-model/read-model-backend/guard.d.ts +4 -0
- package/dist/src/read-model/read-model-backend/guard.js +25 -0
- package/dist/src/read-model/read-model-backend/inbox.d.ts +23 -0
- package/dist/src/read-model/read-model-backend/inbox.js +669 -0
- package/dist/src/read-model/read-model-backend/index.d.ts +8 -0
- package/dist/src/read-model/read-model-backend/index.js +1163 -0
- package/dist/src/read-model/read-model-backend/inspect.d.ts +26 -0
- package/dist/src/read-model/read-model-backend/inspect.js +770 -0
- package/dist/src/read-model/read-model-backend/projection.d.ts +39 -0
- package/dist/src/read-model/read-model-backend/projection.js +669 -0
- package/dist/src/read-model/read-model-backend/run-status.d.ts +27 -0
- package/dist/src/read-model/read-model-backend/run-status.js +237 -0
- package/dist/src/read-model/read-model-backend/stats.d.ts +12 -0
- package/dist/src/read-model/read-model-backend/stats.js +800 -0
- package/dist/src/read-model/read-model-service.d.ts +2 -0
- package/dist/src/read-model/read-model-service.js +1725 -0
- package/dist/src/read-model/reconcile.d.ts +23 -0
- package/dist/src/read-model/reconcile.js +306 -0
- package/dist/src/read-model/run-format.d.ts +23 -0
- package/dist/src/read-model/run-format.js +202 -0
- package/dist/src/read-model/runs-screen.d.ts +14 -0
- package/dist/src/read-model/runs-screen.js +217 -0
- package/dist/src/read-model/session-journal.d.ts +26 -0
- package/dist/src/read-model/session-journal.js +355 -0
- package/dist/src/read-model/stats-command.d.ts +16 -0
- package/dist/src/read-model/stats-command.js +88 -0
- package/dist/src/read-model/stats-format.d.ts +1 -0
- package/dist/src/read-model/stats-format.js +8 -0
- package/dist/src/worker/autohost.d.ts +11 -0
- package/dist/src/worker/autohost.js +858 -0
- package/dist/src/worker/constants.d.ts +3 -0
- package/dist/src/worker/constants.js +10 -0
- package/dist/src/worker/extension.d.ts +14 -0
- package/dist/src/worker/extension.js +881 -0
- package/dist/src/worker/host.d.ts +1 -0
- package/dist/src/worker/host.js +1 -0
- package/dist/src/worker/inbox-command.d.ts +23 -0
- package/dist/src/worker/inbox-command.js +163 -0
- package/dist/src/worker/index.d.ts +2 -0
- package/dist/src/worker/index.js +1767 -0
- package/dist/src/worker/local-run-changes.d.ts +3 -0
- package/dist/src/worker/local-run-changes.js +65 -0
- package/dist/src/worker/notifications.d.ts +1 -0
- package/dist/src/worker/notifications.js +27 -0
- package/dist/src/worker/notify-cap.d.ts +11 -0
- package/dist/src/worker/notify-cap.js +13 -0
- package/dist/src/worker/panel-plugin.d.ts +11 -0
- package/dist/src/worker/panel-plugin.js +37 -0
- package/dist/src/worker/plugin.d.ts +3 -0
- package/dist/src/worker/plugin.js +1761 -0
- package/dist/src/worker/run-control-service.d.ts +2 -0
- package/dist/src/worker/run-control-service.js +210 -0
- package/dist/src/worker/session-journal-writer.d.ts +4 -0
- package/dist/src/worker/session-journal-writer.js +184 -0
- package/dist/src/worker/stall.d.ts +21 -0
- package/dist/src/worker/stall.js +55 -0
- package/dist/src/worker/utils.d.ts +21 -0
- package/dist/src/worker/utils.js +29 -0
- package/dist/src/worker/workflow-journal-writer.d.ts +7 -0
- package/dist/src/worker/workflow-journal-writer.js +76 -0
- package/package.json +47 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type IdentityRegistryFilter, type RunReadModelReconcileInput, type RunReadModelReconcileResult, type RunSessionJournalWriterService } from "@rig/contracts";
|
|
2
|
+
export type CollabSessionProjectionLike = {
|
|
3
|
+
readonly runId?: string;
|
|
4
|
+
readonly source?: string;
|
|
5
|
+
readonly customEntries: unknown;
|
|
6
|
+
readonly session: {
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly id?: string;
|
|
9
|
+
};
|
|
10
|
+
readonly collab?: {
|
|
11
|
+
readonly sessionId?: string;
|
|
12
|
+
readonly stale?: boolean;
|
|
13
|
+
readonly pid?: number;
|
|
14
|
+
readonly sessionPath?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
type ReconcileDeps = {
|
|
18
|
+
readonly listCollabSessionProjections?: (filter: IdentityRegistryFilter) => Promise<readonly CollabSessionProjectionLike[]>;
|
|
19
|
+
readonly journalWriter?: RunSessionJournalWriterService;
|
|
20
|
+
readonly processExists?: (pid: number) => boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function reconcileActiveRuns(input: RunReadModelReconcileInput, deps?: ReconcileDeps): Promise<RunReadModelReconcileResult>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// packages/run-plugin/src/read-model/reconcile.ts
|
|
5
|
+
import { defineCapability } from "@rig/core/capability";
|
|
6
|
+
import { loadCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
7
|
+
import {
|
|
8
|
+
RUN_SESSION_JOURNAL_WRITER
|
|
9
|
+
} from "@rig/contracts";
|
|
10
|
+
|
|
11
|
+
// packages/run-plugin/src/read-model/session-journal.ts
|
|
12
|
+
import { Schema } from "effect";
|
|
13
|
+
import {
|
|
14
|
+
CUSTOM_TYPE_FOR,
|
|
15
|
+
RIG_CONTROL_SENTINEL_END,
|
|
16
|
+
RIG_INBOX_RESOLUTION_SENTINEL,
|
|
17
|
+
RIG_PAUSE_SENTINEL,
|
|
18
|
+
RIG_RESUME_SENTINEL,
|
|
19
|
+
RIG_STOP_SENTINEL,
|
|
20
|
+
RIG_STOP_SENTINEL_END,
|
|
21
|
+
RunJournalEvent,
|
|
22
|
+
TYPE_FOR_CUSTOM
|
|
23
|
+
} from "@rig/contracts";
|
|
24
|
+
var decodeRunJournalEvent = (value) => Schema.decodeUnknownSync(RunJournalEvent)(value);
|
|
25
|
+
var RUN_STATUS_TRANSITIONS = {
|
|
26
|
+
created: ["queued", "preparing", "running", "failed", "stopped"],
|
|
27
|
+
queued: ["preparing", "running", "failed", "stopped"],
|
|
28
|
+
preparing: ["queued", "running", "needs-attention", "failed", "stopped"],
|
|
29
|
+
running: [
|
|
30
|
+
"queued",
|
|
31
|
+
"waiting-approval",
|
|
32
|
+
"waiting-user-input",
|
|
33
|
+
"paused",
|
|
34
|
+
"validating",
|
|
35
|
+
"reviewing",
|
|
36
|
+
"closing-out",
|
|
37
|
+
"needs-attention",
|
|
38
|
+
"completed",
|
|
39
|
+
"failed",
|
|
40
|
+
"stopped"
|
|
41
|
+
],
|
|
42
|
+
"waiting-approval": ["running", "needs-attention", "failed", "stopped"],
|
|
43
|
+
"waiting-user-input": ["running", "needs-attention", "failed", "stopped"],
|
|
44
|
+
paused: ["running", "failed", "stopped"],
|
|
45
|
+
validating: ["running", "reviewing", "closing-out", "needs-attention", "completed", "failed", "stopped"],
|
|
46
|
+
reviewing: ["running", "validating", "closing-out", "needs-attention", "completed", "failed", "stopped"],
|
|
47
|
+
"closing-out": ["running", "needs-attention", "completed", "failed", "stopped"],
|
|
48
|
+
"needs-attention": ["queued", "preparing", "running", "closing-out", "completed", "failed", "stopped"],
|
|
49
|
+
completed: [],
|
|
50
|
+
failed: ["queued", "preparing", "running", "closing-out"],
|
|
51
|
+
stopped: ["queued", "preparing", "running", "closing-out"]
|
|
52
|
+
};
|
|
53
|
+
var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
|
|
54
|
+
function isTerminalRunStatus(status) {
|
|
55
|
+
return TERMINAL_RUN_STATUSES.includes(status);
|
|
56
|
+
}
|
|
57
|
+
function canTransitionRunStatus(from, to) {
|
|
58
|
+
if (from === null)
|
|
59
|
+
return true;
|
|
60
|
+
if (from === to)
|
|
61
|
+
return true;
|
|
62
|
+
return RUN_STATUS_TRANSITIONS[from].includes(to);
|
|
63
|
+
}
|
|
64
|
+
function reduceRunJournal(events, runId = null) {
|
|
65
|
+
let record = {};
|
|
66
|
+
let status = null;
|
|
67
|
+
const statusHistory = [];
|
|
68
|
+
const pendingApprovals = new Map;
|
|
69
|
+
const resolvedApprovals = [];
|
|
70
|
+
const pendingUserInputs = new Map;
|
|
71
|
+
const resolvedUserInputs = [];
|
|
72
|
+
const closeoutPhases = [];
|
|
73
|
+
let resolvedPipeline = null;
|
|
74
|
+
const stageOutcomes = [];
|
|
75
|
+
const anomalies = [];
|
|
76
|
+
let steeringCount = 0;
|
|
77
|
+
let stallCount = 0;
|
|
78
|
+
let lastSeq = 0;
|
|
79
|
+
let lastEventAt = null;
|
|
80
|
+
const projectedRunId = runId ?? events[0]?.runId ?? null;
|
|
81
|
+
for (const event of events) {
|
|
82
|
+
lastSeq = event.seq;
|
|
83
|
+
lastEventAt = event.at;
|
|
84
|
+
switch (event.type) {
|
|
85
|
+
case "status-changed": {
|
|
86
|
+
if (!canTransitionRunStatus(status, event.to)) {
|
|
87
|
+
anomalies.push({ seq: event.seq, kind: "illegal-transition", detail: `${status ?? "(none)"} -> ${event.to}` });
|
|
88
|
+
}
|
|
89
|
+
statusHistory.push({ seq: event.seq, at: event.at, from: status, to: event.to, reason: event.reason ?? null });
|
|
90
|
+
const wasTerminal = status !== null && isTerminalRunStatus(status);
|
|
91
|
+
status = event.to;
|
|
92
|
+
record = { ...record, updatedAt: event.at };
|
|
93
|
+
if (isTerminalRunStatus(event.to) && !record.completedAt)
|
|
94
|
+
record = { ...record, completedAt: event.at };
|
|
95
|
+
if (!isTerminalRunStatus(event.to) && wasTerminal)
|
|
96
|
+
record = { ...record, completedAt: null };
|
|
97
|
+
if (event.to === "running" && !record.startedAt)
|
|
98
|
+
record = { ...record, startedAt: event.at };
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "record-patch": {
|
|
102
|
+
const next = { ...record };
|
|
103
|
+
for (const [key, value] of Object.entries(event.patch)) {
|
|
104
|
+
if (value !== undefined)
|
|
105
|
+
next[key] = value;
|
|
106
|
+
}
|
|
107
|
+
next.updatedAt = event.patch.updatedAt ?? event.at;
|
|
108
|
+
record = next;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "approval-requested": {
|
|
112
|
+
pendingApprovals.set(event.requestId, {
|
|
113
|
+
requestId: event.requestId,
|
|
114
|
+
requestKind: event.requestKind,
|
|
115
|
+
actionId: event.actionId ?? null,
|
|
116
|
+
payload: event.payload,
|
|
117
|
+
requestedAt: event.at
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "approval-resolved": {
|
|
122
|
+
const pending = pendingApprovals.get(event.requestId);
|
|
123
|
+
if (!pending) {
|
|
124
|
+
const alreadyResolved = resolvedApprovals.some((entry) => entry.requestId === event.requestId);
|
|
125
|
+
anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `approval ${event.requestId}` });
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
pendingApprovals.delete(event.requestId);
|
|
129
|
+
resolvedApprovals.push({ ...pending, decision: event.decision, note: event.note ?? null, actor: event.actor, resolvedAt: event.at });
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "input-requested": {
|
|
133
|
+
pendingUserInputs.set(event.requestId, {
|
|
134
|
+
requestId: event.requestId,
|
|
135
|
+
requestKind: "user-input",
|
|
136
|
+
actionId: null,
|
|
137
|
+
payload: event.payload,
|
|
138
|
+
requestedAt: event.at
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "input-resolved": {
|
|
143
|
+
const pending = pendingUserInputs.get(event.requestId);
|
|
144
|
+
if (!pending) {
|
|
145
|
+
const alreadyResolved = resolvedUserInputs.some((entry) => entry.requestId === event.requestId);
|
|
146
|
+
anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `user-input ${event.requestId}` });
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
pendingUserInputs.delete(event.requestId);
|
|
150
|
+
resolvedUserInputs.push({ ...pending, answers: event.answers, actor: event.actor, resolvedAt: event.at });
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "steering":
|
|
154
|
+
steeringCount += 1;
|
|
155
|
+
break;
|
|
156
|
+
case "adopted":
|
|
157
|
+
record = { ...record, pid: event.pid, updatedAt: event.at };
|
|
158
|
+
break;
|
|
159
|
+
case "stall-detected":
|
|
160
|
+
stallCount += 1;
|
|
161
|
+
break;
|
|
162
|
+
case "closeout-phase":
|
|
163
|
+
closeoutPhases.push({ seq: event.seq, at: event.at, phase: event.phase, outcome: event.outcome, detail: event.detail ?? null });
|
|
164
|
+
break;
|
|
165
|
+
case "pipeline-resolved":
|
|
166
|
+
resolvedPipeline = event.pipeline;
|
|
167
|
+
break;
|
|
168
|
+
case "stage-outcome":
|
|
169
|
+
stageOutcomes.push({ seq: event.seq, at: event.at, outcome: event.outcome });
|
|
170
|
+
break;
|
|
171
|
+
case "timeline-entry":
|
|
172
|
+
case "log-entry":
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
runId: projectedRunId,
|
|
178
|
+
record,
|
|
179
|
+
status,
|
|
180
|
+
statusHistory,
|
|
181
|
+
pendingApprovals: [...pendingApprovals.values()],
|
|
182
|
+
resolvedApprovals,
|
|
183
|
+
pendingUserInputs: [...pendingUserInputs.values()],
|
|
184
|
+
resolvedUserInputs,
|
|
185
|
+
steeringCount,
|
|
186
|
+
stallCount,
|
|
187
|
+
closeoutPhases,
|
|
188
|
+
resolvedPipeline,
|
|
189
|
+
stageOutcomes,
|
|
190
|
+
lastSeq,
|
|
191
|
+
lastEventAt,
|
|
192
|
+
anomalies
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function isRunSessionCustomType(customType) {
|
|
196
|
+
return customType !== undefined && Object.hasOwn(TYPE_FOR_CUSTOM, customType);
|
|
197
|
+
}
|
|
198
|
+
function foldRunSessionEntries(entries, runId) {
|
|
199
|
+
const events = [];
|
|
200
|
+
entries.forEach((entry, index) => {
|
|
201
|
+
if (entry.type !== "custom" || !isRunSessionCustomType(entry.customType))
|
|
202
|
+
return;
|
|
203
|
+
const data = entry.data !== null && typeof entry.data === "object" ? entry.data : {};
|
|
204
|
+
const stamped = {
|
|
205
|
+
v: 1,
|
|
206
|
+
seq: index + 1,
|
|
207
|
+
at: typeof data.at === "string" ? data.at : new Date(0).toISOString(),
|
|
208
|
+
runId,
|
|
209
|
+
...data,
|
|
210
|
+
type: TYPE_FOR_CUSTOM[entry.customType]
|
|
211
|
+
};
|
|
212
|
+
try {
|
|
213
|
+
events.push(decodeRunJournalEvent(stamped));
|
|
214
|
+
} catch {}
|
|
215
|
+
});
|
|
216
|
+
return reduceRunJournal(events, runId);
|
|
217
|
+
}
|
|
218
|
+
function projectRunFromSession(source, runId) {
|
|
219
|
+
const entries = "getEntries" in source ? source.getEntries() : source;
|
|
220
|
+
return foldRunSessionEntries(entries, runId);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// packages/run-plugin/src/read-model/reconcile.ts
|
|
224
|
+
var RunSessionJournalWriterCap = defineCapability(RUN_SESSION_JOURNAL_WRITER);
|
|
225
|
+
var collabApiPromise = null;
|
|
226
|
+
async function loadCollabApi() {
|
|
227
|
+
collabApiPromise ??= import("@oh-my-pi/pi-coding-agent/collab-api").then((api) => api).catch(() => null);
|
|
228
|
+
return collabApiPromise;
|
|
229
|
+
}
|
|
230
|
+
var ACTIVE_LOCAL_RUN_STATUSES = new Set([
|
|
231
|
+
"created",
|
|
232
|
+
"preparing",
|
|
233
|
+
"running",
|
|
234
|
+
"validating",
|
|
235
|
+
"reviewing",
|
|
236
|
+
"closing-out"
|
|
237
|
+
]);
|
|
238
|
+
function pidExists(pid) {
|
|
239
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
240
|
+
return false;
|
|
241
|
+
try {
|
|
242
|
+
process.kill(pid, 0);
|
|
243
|
+
return true;
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function projectionRunId(item) {
|
|
249
|
+
if (item.runId?.trim())
|
|
250
|
+
return item.runId.trim();
|
|
251
|
+
if (item.collab?.sessionId?.trim())
|
|
252
|
+
return item.collab.sessionId.trim();
|
|
253
|
+
if (item.session.id?.trim())
|
|
254
|
+
return item.session.id.trim();
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function isLocallyDiscovered(item) {
|
|
258
|
+
return item.source === "session-list";
|
|
259
|
+
}
|
|
260
|
+
function isActiveStatus(status) {
|
|
261
|
+
if (status === null)
|
|
262
|
+
return false;
|
|
263
|
+
return !isTerminalRunStatus(status);
|
|
264
|
+
}
|
|
265
|
+
function shouldFlip(item, status, exists) {
|
|
266
|
+
if (!isActiveStatus(status))
|
|
267
|
+
return false;
|
|
268
|
+
if (item.collab?.stale === true)
|
|
269
|
+
return true;
|
|
270
|
+
const pid = item.collab?.pid;
|
|
271
|
+
return isLocallyDiscovered(item) && pid !== undefined && ACTIVE_LOCAL_RUN_STATUSES.has(status) && !exists(pid);
|
|
272
|
+
}
|
|
273
|
+
async function reconcileActiveRuns(input, deps = {}) {
|
|
274
|
+
const collabApi = deps.listCollabSessionProjections ? null : await loadCollabApi();
|
|
275
|
+
const listProjections = deps.listCollabSessionProjections ?? collabApi?.listCollabSessionProjections;
|
|
276
|
+
const journalWriter = deps.journalWriter ?? await loadCapabilityForRoot(input.workspaceRoot, RunSessionJournalWriterCap);
|
|
277
|
+
if (!journalWriter)
|
|
278
|
+
return { flipped: [], resumable: [] };
|
|
279
|
+
const exists = deps.processExists ?? pidExists;
|
|
280
|
+
if (!listProjections)
|
|
281
|
+
return { flipped: [], resumable: [] };
|
|
282
|
+
const projections = await listProjections(input.identityFilter);
|
|
283
|
+
const flipped = [];
|
|
284
|
+
for (const item of projections) {
|
|
285
|
+
const runId = projectionRunId(item);
|
|
286
|
+
const sessionPath = item.session.path || item.collab?.sessionPath || "";
|
|
287
|
+
if (!runId || !sessionPath)
|
|
288
|
+
continue;
|
|
289
|
+
const projection = projectRunFromSession(item.customEntries, runId);
|
|
290
|
+
const status = projection.status;
|
|
291
|
+
if (status === null || !shouldFlip(item, status, exists))
|
|
292
|
+
continue;
|
|
293
|
+
const result = await journalWriter.reconcileDeadPid({
|
|
294
|
+
projectRoot: input.workspaceRoot,
|
|
295
|
+
runId,
|
|
296
|
+
sessionPath,
|
|
297
|
+
reason: "lazy-reconcile:dead-pid"
|
|
298
|
+
});
|
|
299
|
+
if (result.updated)
|
|
300
|
+
flipped.push({ runId, sessionPath, reason: "lazy-reconcile:dead-pid" });
|
|
301
|
+
}
|
|
302
|
+
return { flipped, resumable: flipped };
|
|
303
|
+
}
|
|
304
|
+
export {
|
|
305
|
+
reconcileActiveRuns
|
|
306
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RunPresentationRecord, RunReadModelStatusRole } from "@rig/contracts";
|
|
2
|
+
export type CliRunLike = RunPresentationRecord;
|
|
3
|
+
export declare function formatStatusPill(status: string, role: RunReadModelStatusRole | null): string;
|
|
4
|
+
export declare function formatRunList(runs: readonly CliRunLike[], options?: {
|
|
5
|
+
source?: "local" | "server";
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function formatSubmittedRun(input: {
|
|
8
|
+
runId: string;
|
|
9
|
+
taskId?: string | null;
|
|
10
|
+
title?: string | null;
|
|
11
|
+
queuePosition?: number | null;
|
|
12
|
+
}): string;
|
|
13
|
+
export declare function formatRunCard(run: CliRunLike, options?: {
|
|
14
|
+
title?: string;
|
|
15
|
+
}): string;
|
|
16
|
+
export declare function formatRunStatus(summary: {
|
|
17
|
+
activeRuns?: CliRunLike[];
|
|
18
|
+
recentRuns?: CliRunLike[];
|
|
19
|
+
runs?: CliRunLike[];
|
|
20
|
+
}, options?: {
|
|
21
|
+
source?: "local" | "server";
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function formatInboxList(kind: "approvals" | "inputs", entries: readonly Record<string, unknown>[]): string;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/run-plugin/src/read-model/run-format.ts
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { formatSection, formatSuccessCard, formatNextSteps } from "@rig/std-shared/cli-format";
|
|
5
|
+
var dim = pc.dim;
|
|
6
|
+
var faintBar = pc.dim("\u2502");
|
|
7
|
+
var accent = pc.cyan;
|
|
8
|
+
var STATUS_ROLES = new Set([
|
|
9
|
+
"success",
|
|
10
|
+
"action-yellow",
|
|
11
|
+
"active-cyan",
|
|
12
|
+
"failure",
|
|
13
|
+
"muted",
|
|
14
|
+
"neutral"
|
|
15
|
+
]);
|
|
16
|
+
function projectedStatusRole(value) {
|
|
17
|
+
return typeof value === "string" && STATUS_ROLES.has(value) ? value : null;
|
|
18
|
+
}
|
|
19
|
+
function truncate(value, width) {
|
|
20
|
+
return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
21
|
+
}
|
|
22
|
+
function pad(value, width) {
|
|
23
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
24
|
+
}
|
|
25
|
+
function colorForRole(role) {
|
|
26
|
+
switch (role) {
|
|
27
|
+
case "success":
|
|
28
|
+
return pc.green;
|
|
29
|
+
case "action-yellow":
|
|
30
|
+
return pc.yellow;
|
|
31
|
+
case "active-cyan":
|
|
32
|
+
return pc.cyan;
|
|
33
|
+
case "failure":
|
|
34
|
+
return pc.red;
|
|
35
|
+
case "muted":
|
|
36
|
+
case "neutral":
|
|
37
|
+
return pc.dim;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function inboxStatusRole(record) {
|
|
41
|
+
const projected = projectedStatusRole(record.statusRole);
|
|
42
|
+
if (projected)
|
|
43
|
+
return projected;
|
|
44
|
+
return record.status === "pending" ? "active-cyan" : "neutral";
|
|
45
|
+
}
|
|
46
|
+
function compactDate(value) {
|
|
47
|
+
const parsed = Date.parse(value);
|
|
48
|
+
if (!Number.isFinite(parsed))
|
|
49
|
+
return value;
|
|
50
|
+
return new Date(parsed).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
|
|
51
|
+
}
|
|
52
|
+
function firstString(record, keys, fallback = "") {
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
const value = record[key];
|
|
55
|
+
if (typeof value === "string" && value.trim())
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return fallback;
|
|
59
|
+
}
|
|
60
|
+
function runIdOf(run) {
|
|
61
|
+
return firstString(run, ["runId", "id"], "(unknown-run)");
|
|
62
|
+
}
|
|
63
|
+
function taskIdOf(run) {
|
|
64
|
+
return firstString(run, ["taskId", "task", "task_id"]);
|
|
65
|
+
}
|
|
66
|
+
function runTitleOf(run) {
|
|
67
|
+
return firstString(run, ["title", "summary", "name"], taskIdOf(run) || "(untitled)");
|
|
68
|
+
}
|
|
69
|
+
function requestIdOf(entry) {
|
|
70
|
+
return firstString(entry, ["requestId", "id", "approvalId", "inputId"], "(unknown-request)");
|
|
71
|
+
}
|
|
72
|
+
function runLikeStatusText(run) {
|
|
73
|
+
const record = run;
|
|
74
|
+
const status = typeof record.status === "string" ? record.status.trim() : "";
|
|
75
|
+
return status || "unclassified";
|
|
76
|
+
}
|
|
77
|
+
function runLikeStatusColor(run) {
|
|
78
|
+
const record = run;
|
|
79
|
+
return colorForRole(projectedStatusRole(record.statusRole) ?? "neutral");
|
|
80
|
+
}
|
|
81
|
+
function formatStatusPill(status, role) {
|
|
82
|
+
const label = status || "unclassified";
|
|
83
|
+
return colorForRole(role ?? "neutral")(`\u25CF ${label}`);
|
|
84
|
+
}
|
|
85
|
+
function formatLegacyAutomationSurface() {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
function formatRunList(runs, options = {}) {
|
|
89
|
+
if (runs.length === 0) {
|
|
90
|
+
return [
|
|
91
|
+
formatSection("Runs", "none recorded"),
|
|
92
|
+
options.source === "server" ? dim("No runs recorded on the selected server.") : dim("No runs recorded in local state."),
|
|
93
|
+
"",
|
|
94
|
+
...formatLegacyAutomationSurface()
|
|
95
|
+
].join(`
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
const body = runs.map((run) => {
|
|
99
|
+
const row = run;
|
|
100
|
+
const runId = runIdOf(row);
|
|
101
|
+
const status = runLikeStatusText(run);
|
|
102
|
+
const runtime = firstString(row, ["runtime", "runtimeAdapter"], "");
|
|
103
|
+
return [
|
|
104
|
+
pc.bold(runId),
|
|
105
|
+
runLikeStatusColor(run)(pad(truncate(status, 12), 12)),
|
|
106
|
+
`${runTitleOf(row)}${runtime ? dim(` ${runtime}`) : ""}`
|
|
107
|
+
].join(" ");
|
|
108
|
+
});
|
|
109
|
+
return [formatSection("Runs", options.source === "server" ? "selected server" : "local state"), ...body, "", ...formatLegacyAutomationSurface()].join(`
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
function formatSubmittedRun(input) {
|
|
113
|
+
return formatSuccessCard("Run submitted", [
|
|
114
|
+
["run", input.runId],
|
|
115
|
+
["task", input.taskId ?? undefined],
|
|
116
|
+
["title", input.title ?? undefined],
|
|
117
|
+
["queue", input.queuePosition ?? undefined]
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
function formatRunCard(run, options = {}) {
|
|
121
|
+
const record = run;
|
|
122
|
+
const lines = [formatSection(options.title ?? "Run")];
|
|
123
|
+
for (const [label, value] of [
|
|
124
|
+
["run", runIdOf(record)],
|
|
125
|
+
["task", taskIdOf(record)],
|
|
126
|
+
["title", runTitleOf(record)],
|
|
127
|
+
["status", runLikeStatusText(run)],
|
|
128
|
+
["runtime", firstString(record, ["runtime", "runtimeAdapter"], "")],
|
|
129
|
+
["created", firstString(record, ["createdAt"], "")],
|
|
130
|
+
["updated", firstString(record, ["updatedAt"], "")]
|
|
131
|
+
]) {
|
|
132
|
+
if (value)
|
|
133
|
+
lines.push(`${faintBar} ${dim(label.padEnd(12))} ${label === "status" ? runLikeStatusColor(run)(value) : label === "created" || label === "updated" ? compactDate(value) : value}`);
|
|
134
|
+
}
|
|
135
|
+
const errorSummary = firstString(record, ["errorSummary"]);
|
|
136
|
+
if (errorSummary)
|
|
137
|
+
lines.push(`${faintBar} Error: ${errorSummary}`);
|
|
138
|
+
return lines.join(`
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
function formatRunStatus(summary, options = {}) {
|
|
142
|
+
const activeRuns = summary.activeRuns ?? [];
|
|
143
|
+
const recentRuns = summary.recentRuns ?? [];
|
|
144
|
+
const lines = [formatSection("Run status", options.source === "server" ? "selected server" : "local state")];
|
|
145
|
+
lines.push(`Active runs (${activeRuns.length})`);
|
|
146
|
+
if (activeRuns.length === 0)
|
|
147
|
+
lines.push(dim("No active runs."));
|
|
148
|
+
else
|
|
149
|
+
for (const run of activeRuns)
|
|
150
|
+
lines.push(formatRunSummaryLine(run));
|
|
151
|
+
lines.push("", `Recent runs (${recentRuns.length})`);
|
|
152
|
+
if (recentRuns.length === 0)
|
|
153
|
+
lines.push(dim("No recent terminal runs."));
|
|
154
|
+
else
|
|
155
|
+
for (const run of recentRuns.slice(0, 10))
|
|
156
|
+
lines.push(formatRunSummaryLine(run));
|
|
157
|
+
lines.push("", ...formatLegacyAutomationSurface());
|
|
158
|
+
return lines.join(`
|
|
159
|
+
`);
|
|
160
|
+
}
|
|
161
|
+
function formatRunSummaryLine(run) {
|
|
162
|
+
const record = run;
|
|
163
|
+
const runId = runIdOf(record);
|
|
164
|
+
const taskId = taskIdOf(record);
|
|
165
|
+
const title = runTitleOf(record);
|
|
166
|
+
const status = runLikeStatusText(run);
|
|
167
|
+
const runtime = firstString(record, ["runtime", "runtimeAdapter"], "");
|
|
168
|
+
const descriptor = [taskId, title].filter(Boolean).join(" \xB7 ");
|
|
169
|
+
return `${faintBar} ${pc.bold(runId)} ${runLikeStatusColor(run)(`\u25CF ${status}`)} ${descriptor}${runtime ? dim(` ${runtime}`) : ""}`;
|
|
170
|
+
}
|
|
171
|
+
function formatInboxList(kind, entries) {
|
|
172
|
+
const title = kind === "approvals" ? "Pending approvals" : "Pending user input";
|
|
173
|
+
if (entries.length === 0) {
|
|
174
|
+
return [
|
|
175
|
+
formatSection(title, "empty"),
|
|
176
|
+
dim(kind === "approvals" ? "No pending approvals." : "No pending user-input requests."),
|
|
177
|
+
"",
|
|
178
|
+
...formatLegacyAutomationSurface()
|
|
179
|
+
].join(`
|
|
180
|
+
`);
|
|
181
|
+
}
|
|
182
|
+
const lines = [formatSection(title, `${entries.length}`)];
|
|
183
|
+
for (const record of entries) {
|
|
184
|
+
const runId = firstString(record, ["runId"], "(unknown-run)");
|
|
185
|
+
const taskId = firstString(record, ["taskId"], "");
|
|
186
|
+
const status = firstString(record, ["status"], "pending");
|
|
187
|
+
const prompt = firstString(record, ["prompt", "message", "reason", "title", "summary"], kind === "approvals" ? "Approval requested" : "Input requested");
|
|
188
|
+
lines.push(`${faintBar} ${pc.bold(requestIdOf(record))} ${formatStatusPill(status, inboxStatusRole(record))} ${prompt}`);
|
|
189
|
+
lines.push(`${faintBar} ${dim("run".padEnd(12))} ${runId}${taskId ? dim(` task ${taskId}`) : ""}`);
|
|
190
|
+
}
|
|
191
|
+
lines.push("", ...formatLegacyAutomationSurface(), ...formatNextSteps(kind === "approvals" ? ["Resolve: `rig inbox approve --run <run-id> --request <request-id> --decision approve|reject`"] : ["Reply: `rig inbox answer --run <run-id> --request <request-id> --text ...`"]));
|
|
192
|
+
return lines.join(`
|
|
193
|
+
`);
|
|
194
|
+
}
|
|
195
|
+
export {
|
|
196
|
+
formatSubmittedRun,
|
|
197
|
+
formatStatusPill,
|
|
198
|
+
formatRunStatus,
|
|
199
|
+
formatRunList,
|
|
200
|
+
formatRunCard,
|
|
201
|
+
formatInboxList
|
|
202
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CockpitBoardView, CockpitBoardViewHost } from "@rig/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* Width-truncation is injected: the real implementation is pi-tui's native
|
|
4
|
+
* `truncateToWidth` (ANSI/wide-char aware), whose `@oh-my-pi/pi-natives` loader
|
|
5
|
+
* is Bun-only (`import.meta.dir`) and unimportable under vitest. The panel's
|
|
6
|
+
* `createView` passes the real one; tests pass a plain slicer.
|
|
7
|
+
*/
|
|
8
|
+
export type WidthTruncate = (text: string, width: number) => string;
|
|
9
|
+
/** pi-tui key matcher (handles the terminal escape encodings); injected for the same reason. */
|
|
10
|
+
export type KeyMatcher = (data: string, key: "up" | "down" | "enter" | "return" | "escape") => boolean;
|
|
11
|
+
export declare function createRunsBoardView(host: CockpitBoardViewHost, deps: {
|
|
12
|
+
truncateToWidth: WidthTruncate;
|
|
13
|
+
matchesKey: KeyMatcher;
|
|
14
|
+
}): CockpitBoardView;
|