@h-rig/omp-extension-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/cockpit/board-views.d.ts +100 -0
- package/dist/src/cockpit/board-views.js +298 -0
- package/dist/src/cockpit/cockpit-product.d.ts +12 -0
- package/dist/src/cockpit/cockpit-product.js +55 -0
- package/dist/src/cockpit/drone-preloader.d.ts +11 -0
- package/dist/src/cockpit/drone-preloader.js +220 -0
- package/dist/src/cockpit/extension.d.ts +85 -0
- package/dist/src/cockpit/extension.js +2222 -0
- package/dist/src/cockpit/plugin.d.ts +14 -0
- package/dist/src/cockpit/plugin.js +2277 -0
- package/dist/src/cockpit/screen-catalog.d.ts +55 -0
- package/dist/src/cockpit/screen-catalog.js +65 -0
- package/dist/src/plugin.d.ts +4 -0
- package/dist/src/plugin.js +2596 -0
- package/dist/src/product-entrypoint/contributions.d.ts +3 -0
- package/dist/src/product-entrypoint/contributions.js +215 -0
- package/dist/src/product-entrypoint/metadata.d.ts +17 -0
- package/dist/src/product-entrypoint/metadata.js +29 -0
- package/dist/src/product-entrypoint/plugin.d.ts +4 -0
- package/dist/src/product-entrypoint/plugin.js +300 -0
- package/dist/src/product-entrypoint/product-entrypoint.d.ts +14 -0
- package/dist/src/product-entrypoint/product-entrypoint.js +136 -0
- package/dist/src/product-entrypoint/product-help.d.ts +6 -0
- package/dist/src/product-entrypoint/product-help.js +57 -0
- package/package.json +39 -0
|
@@ -0,0 +1,2277 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// packages/omp-extension-plugin/src/cockpit/board-views.ts
|
|
19
|
+
import { truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
20
|
+
import {
|
|
21
|
+
accent,
|
|
22
|
+
accentDim,
|
|
23
|
+
bold,
|
|
24
|
+
cyan,
|
|
25
|
+
ink,
|
|
26
|
+
ink2,
|
|
27
|
+
ink3,
|
|
28
|
+
ink4,
|
|
29
|
+
red,
|
|
30
|
+
DRONE_WIDTH,
|
|
31
|
+
renderDroneFrame,
|
|
32
|
+
renderMicroDroneFrame,
|
|
33
|
+
RIG_SPINNER_FRAMES,
|
|
34
|
+
screenGlyph,
|
|
35
|
+
sectionDivider,
|
|
36
|
+
statusColor,
|
|
37
|
+
statusGlyph,
|
|
38
|
+
statusPill,
|
|
39
|
+
wrapText,
|
|
40
|
+
yellow
|
|
41
|
+
} from "@rig/std-shared/board-theme";
|
|
42
|
+
function hairline(width) {
|
|
43
|
+
return ink4("\u2500".repeat(Math.max(0, width)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class BoardSettingsList {
|
|
47
|
+
items = [];
|
|
48
|
+
selected = 0;
|
|
49
|
+
title = "";
|
|
50
|
+
emptyMessage = "nothing to show";
|
|
51
|
+
setItems(items) {
|
|
52
|
+
this.items = [...items];
|
|
53
|
+
if (!this.selectableAt(this.selected))
|
|
54
|
+
this.selected = this.firstSelectable();
|
|
55
|
+
}
|
|
56
|
+
setTitle(title) {
|
|
57
|
+
this.title = title;
|
|
58
|
+
}
|
|
59
|
+
selectableAt(index) {
|
|
60
|
+
const item = this.items[index];
|
|
61
|
+
return !!item && item.heading !== true;
|
|
62
|
+
}
|
|
63
|
+
firstSelectable() {
|
|
64
|
+
const i = this.items.findIndex((_, idx) => this.selectableAt(idx));
|
|
65
|
+
return i >= 0 ? i : 0;
|
|
66
|
+
}
|
|
67
|
+
moveSelection(delta) {
|
|
68
|
+
if (this.items.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
const step = delta < 0 ? -1 : 1;
|
|
71
|
+
for (let i = this.selected + step;i >= 0 && i < this.items.length; i += step) {
|
|
72
|
+
if (this.selectableAt(i)) {
|
|
73
|
+
this.selected = i;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
selectedItem() {
|
|
79
|
+
return this.selectableAt(this.selected) ? this.items[this.selected] : null;
|
|
80
|
+
}
|
|
81
|
+
invalidate() {}
|
|
82
|
+
render(width) {
|
|
83
|
+
return this.renderLines(width).map((line) => truncateToWidth(line, Math.max(10, width - 1)));
|
|
84
|
+
}
|
|
85
|
+
renderLines(width) {
|
|
86
|
+
if (this.items.length === 0) {
|
|
87
|
+
return ["", ` ${ink2(this.emptyMessage)}`, ""];
|
|
88
|
+
}
|
|
89
|
+
const maxVisible = Math.max(8, Math.min(16, (process.stdout.rows ?? 30) - 10));
|
|
90
|
+
const start = Math.max(0, Math.min(this.selected - Math.floor(maxVisible / 2), this.items.length - maxVisible));
|
|
91
|
+
const window = this.items.slice(start, start + maxVisible);
|
|
92
|
+
const valueWidth = Math.min(24, Math.max(10, Math.floor(width * 0.24)));
|
|
93
|
+
const labelWidth = Math.min(24, Math.max(10, Math.floor(width * 0.26)));
|
|
94
|
+
const lines = window.flatMap((item, index) => {
|
|
95
|
+
const absolute = start + index;
|
|
96
|
+
const isSelected = absolute === this.selected;
|
|
97
|
+
const marker = isSelected ? accent("\u258C") : " ";
|
|
98
|
+
const navGlyph = item.id.startsWith("to:") ? `${screenGlyph(item.id.slice(3))} ` : "";
|
|
99
|
+
const labelText = truncateToWidth(`${navGlyph}${item.label}`, labelWidth).padEnd(labelWidth);
|
|
100
|
+
const isTitle = item.id === "title";
|
|
101
|
+
const isStatus = item.id.endsWith(":status");
|
|
102
|
+
const label = isTitle ? accent(labelText) : isSelected ? ink(labelText) : item.heading ? ink3(labelText) : ink2(labelText);
|
|
103
|
+
const value = !item.currentValue ? ink4("".padEnd(valueWidth)) : isStatus ? statusColor(item.currentValue)(truncateToWidth(`${statusGlyph(item.currentValue)} ${item.currentValue}`, valueWidth).padEnd(valueWidth)) : item.heading ? ink2(truncateToWidth(item.currentValue, valueWidth).padEnd(valueWidth)) : ink3(truncateToWidth(item.currentValue, valueWidth).padEnd(valueWidth));
|
|
104
|
+
const action = item.values?.[0] && item.values[0] !== item.currentValue ? isSelected ? accent(item.values[0]) : accentDim(item.values[0]) : "";
|
|
105
|
+
const row = `${marker} ${label} ${value}${action ? ` ${action}` : ""}`;
|
|
106
|
+
if (isSelected && item.description) {
|
|
107
|
+
return [row, ` ${ink4(truncateToWidth(item.description, Math.max(12, width - 5)))}`];
|
|
108
|
+
}
|
|
109
|
+
return [row];
|
|
110
|
+
});
|
|
111
|
+
const meta = [];
|
|
112
|
+
if (this.items.length > maxVisible)
|
|
113
|
+
meta.push(`${this.selected + 1}/${this.items.length}`);
|
|
114
|
+
if (this.title)
|
|
115
|
+
meta.push(this.title);
|
|
116
|
+
if (meta.length > 0)
|
|
117
|
+
lines.push(ink4(` ${meta.join(" \xB7 ")}`));
|
|
118
|
+
return ["", ...lines, ""];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class HelpView {
|
|
123
|
+
catalog;
|
|
124
|
+
group;
|
|
125
|
+
offset = 0;
|
|
126
|
+
#built = null;
|
|
127
|
+
constructor(catalog, group) {
|
|
128
|
+
this.catalog = catalog;
|
|
129
|
+
this.group = group;
|
|
130
|
+
}
|
|
131
|
+
#build(width) {
|
|
132
|
+
if (this.#built)
|
|
133
|
+
return this.#built;
|
|
134
|
+
const { sections, groups } = this.catalog;
|
|
135
|
+
const lines = [];
|
|
136
|
+
const groupAnchors = new Map;
|
|
137
|
+
const columnWidth = Math.min(38, Math.max(24, Math.floor(width * 0.4)));
|
|
138
|
+
const row = (command, description) => {
|
|
139
|
+
const left = command.length >= columnWidth ? `${command} ` : command.padEnd(columnWidth);
|
|
140
|
+
return ` ${bold(ink(left))} ${ink2(truncateToWidth(description, Math.max(16, width - columnWidth - 4)))}`;
|
|
141
|
+
};
|
|
142
|
+
for (const section of sections) {
|
|
143
|
+
lines.push(`${accent("\u25C7")} ${bold(ink(section.title))} ${ink3(`\u2014 ${section.subtitle}`)}`);
|
|
144
|
+
for (const command of section.commands)
|
|
145
|
+
lines.push(row(command.command, command.description));
|
|
146
|
+
lines.push("");
|
|
147
|
+
}
|
|
148
|
+
lines.push(`${accent("\u25C7")} ${bold(ink("command groups"))} ${ink3("\u2014 every `rig <group>` surface")}`);
|
|
149
|
+
lines.push("");
|
|
150
|
+
for (const group of groups) {
|
|
151
|
+
groupAnchors.set(group.name, lines.length);
|
|
152
|
+
lines.push(`${accentDim("\u258D")}${bold(ink(`rig ${group.name}`))} ${ink3(`\u2014 ${group.summary}`)}`);
|
|
153
|
+
for (const usage of group.usage)
|
|
154
|
+
lines.push(` ${cyan(usage)}`);
|
|
155
|
+
for (const command of group.commands)
|
|
156
|
+
lines.push(row(command.command, command.description));
|
|
157
|
+
if (group.examples?.length) {
|
|
158
|
+
for (const example of group.examples)
|
|
159
|
+
lines.push(` ${ink4("$")} ${ink2(example)}`);
|
|
160
|
+
}
|
|
161
|
+
if (group.next?.length) {
|
|
162
|
+
for (const next of group.next)
|
|
163
|
+
lines.push(` ${accent("\u203A")} ${ink3(next)}`);
|
|
164
|
+
}
|
|
165
|
+
lines.push("");
|
|
166
|
+
}
|
|
167
|
+
this.#built = { lines, groupAnchors };
|
|
168
|
+
if (this.group) {
|
|
169
|
+
const anchor = groupAnchors.get(this.group);
|
|
170
|
+
if (anchor !== undefined)
|
|
171
|
+
this.offset = anchor;
|
|
172
|
+
}
|
|
173
|
+
return this.#built;
|
|
174
|
+
}
|
|
175
|
+
scroll(delta) {
|
|
176
|
+
if (!this.#built)
|
|
177
|
+
return;
|
|
178
|
+
const max = Math.max(0, this.#built.lines.length - 10);
|
|
179
|
+
this.offset = Math.max(0, Math.min(max, this.offset + delta));
|
|
180
|
+
}
|
|
181
|
+
invalidate() {
|
|
182
|
+
this.#built = null;
|
|
183
|
+
}
|
|
184
|
+
render(width) {
|
|
185
|
+
const { lines } = this.#build(width);
|
|
186
|
+
const viewport = Math.max(10, (process.stdout.rows ?? 30) - 8);
|
|
187
|
+
const slice = lines.slice(this.offset, this.offset + viewport);
|
|
188
|
+
const more = this.offset + viewport < lines.length;
|
|
189
|
+
return [
|
|
190
|
+
"",
|
|
191
|
+
...slice.map((line) => truncateToWidth(` ${line}`, Math.max(10, width - 1))),
|
|
192
|
+
more ? ink4(` \u2026 ${lines.length - this.offset - viewport} more (\u2193)`) : ""
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class DetailComposer {
|
|
198
|
+
model = EMPTY_DETAIL;
|
|
199
|
+
selected = 0;
|
|
200
|
+
tick = 0;
|
|
201
|
+
bodyScroll = 0;
|
|
202
|
+
setModel(model) {
|
|
203
|
+
const prevId = this.enabledActions()[this.selected]?.id;
|
|
204
|
+
this.model = model;
|
|
205
|
+
const actions = this.enabledActions();
|
|
206
|
+
const idx = prevId ? actions.findIndex((action) => action.id === prevId) : -1;
|
|
207
|
+
this.selected = idx >= 0 ? idx : 0;
|
|
208
|
+
this.clamp();
|
|
209
|
+
}
|
|
210
|
+
enabledActions() {
|
|
211
|
+
return this.model.actions.filter((action) => action.enabled !== false);
|
|
212
|
+
}
|
|
213
|
+
clamp() {
|
|
214
|
+
const count = this.enabledActions().length;
|
|
215
|
+
this.selected = count === 0 ? 0 : Math.max(0, Math.min(this.selected, count - 1));
|
|
216
|
+
}
|
|
217
|
+
moveSelection(delta) {
|
|
218
|
+
const count = this.enabledActions().length;
|
|
219
|
+
if (count === 0)
|
|
220
|
+
return;
|
|
221
|
+
this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));
|
|
222
|
+
}
|
|
223
|
+
selectedAction() {
|
|
224
|
+
return this.enabledActions()[this.selected] ?? null;
|
|
225
|
+
}
|
|
226
|
+
scrollBody(delta) {
|
|
227
|
+
this.bodyScroll = Math.max(0, this.bodyScroll + delta);
|
|
228
|
+
}
|
|
229
|
+
resetScroll() {
|
|
230
|
+
this.bodyScroll = 0;
|
|
231
|
+
}
|
|
232
|
+
invalidate() {}
|
|
233
|
+
render(width) {
|
|
234
|
+
const model = this.model;
|
|
235
|
+
const inner = Math.max(12, width - 2);
|
|
236
|
+
const budget = Math.max(8, (process.stdout.rows ?? 40) - 5);
|
|
237
|
+
const top = [""];
|
|
238
|
+
top.push(` ${accent(model.icon)} ${accent(model.ref)}${model.status ? ` ${statusPill(model.status)}` : ""}`);
|
|
239
|
+
for (const line of wrapText(model.title, inner - 1))
|
|
240
|
+
top.push(` ${ink(line)}`);
|
|
241
|
+
top.push("");
|
|
242
|
+
const bottom = [];
|
|
243
|
+
if (model.facts.length > 0) {
|
|
244
|
+
bottom.push(sectionDivider("details", width));
|
|
245
|
+
const labelWidth = Math.min(16, Math.max(...model.facts.map((fact) => fact.label.length)) + 1);
|
|
246
|
+
for (const fact of model.facts) {
|
|
247
|
+
const label = ink3(fact.label.toUpperCase().padEnd(labelWidth));
|
|
248
|
+
const value = fact.status ? statusColor(fact.status)(`${statusGlyph(fact.status)} ${fact.value}`) : fact.tone === "link" ? cyan(truncateToWidth(fact.value, inner - labelWidth - 3)) : fact.tone === "warn" ? yellow(fact.value) : ink2(truncateToWidth(fact.value, inner - labelWidth - 3));
|
|
249
|
+
bottom.push(` ${label} ${value}`);
|
|
250
|
+
}
|
|
251
|
+
bottom.push("");
|
|
252
|
+
}
|
|
253
|
+
if (model.stages && model.stages.length > 0) {
|
|
254
|
+
bottom.push(sectionDivider("pipeline", width));
|
|
255
|
+
const parts = model.stages.map((stage) => `${statusColor(stage.status)(statusGlyph(stage.status))} ${ink3(stage.label)}`);
|
|
256
|
+
bottom.push(truncateToWidth(` ${parts.join(ink4(" \u2192 "))}`, inner));
|
|
257
|
+
bottom.push("");
|
|
258
|
+
}
|
|
259
|
+
if (model.actions.length > 0) {
|
|
260
|
+
bottom.push(sectionDivider("actions", width));
|
|
261
|
+
const selId = this.selectedAction()?.id;
|
|
262
|
+
for (const action of model.actions) {
|
|
263
|
+
const on = action.enabled !== false;
|
|
264
|
+
const isSel = on && action.id === selId;
|
|
265
|
+
const rail = isSel ? accent("\u258C") : " ";
|
|
266
|
+
const labelText = `${action.glyph ? `${action.glyph} ` : ""}${action.label}`;
|
|
267
|
+
const label = !on ? ink4(labelText) : action.danger ? red(labelText) : isSel ? accent(labelText) : ink2(labelText);
|
|
268
|
+
const tail = !on ? ink4(action.value ?? "unavailable") : isSel ? ink3(action.hint ?? action.value ?? "") : ink3(action.value ?? "");
|
|
269
|
+
bottom.push(`${rail} ${label}${tail ? ` ${tail}` : ""}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const body = [];
|
|
273
|
+
if (model.body && model.body.trim()) {
|
|
274
|
+
const bodyCap = budget - top.length - bottom.length;
|
|
275
|
+
if (bodyCap >= 3) {
|
|
276
|
+
const bodyLines = wrapText(model.body, inner - 3);
|
|
277
|
+
const contentCap = bodyCap - 2;
|
|
278
|
+
const needScroll = bodyLines.length > contentCap;
|
|
279
|
+
const shownCount = needScroll ? Math.max(1, contentCap - 1) : bodyLines.length;
|
|
280
|
+
const maxScroll = Math.max(0, bodyLines.length - shownCount);
|
|
281
|
+
const scroll = Math.min(this.bodyScroll, maxScroll);
|
|
282
|
+
body.push(sectionDivider(model.bodyLabel ?? "body", width));
|
|
283
|
+
for (const line of bodyLines.slice(scroll, scroll + shownCount))
|
|
284
|
+
body.push(` ${ink4("\u2502")} ${ink2(line)}`);
|
|
285
|
+
if (needScroll) {
|
|
286
|
+
const up = scroll > 0 ? "\u2191 " : "";
|
|
287
|
+
const down = scroll < maxScroll ? `\u2193 ${maxScroll - scroll} more` : "";
|
|
288
|
+
body.push(` ${ink4("\u2502")} ${accentDim(`${up}${down}`.trim())}`);
|
|
289
|
+
}
|
|
290
|
+
body.push("");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const out = [...top, ...body, ...bottom];
|
|
294
|
+
if (out.length > budget) {
|
|
295
|
+
const keepTail = Math.min(bottom.length, budget - top.length);
|
|
296
|
+
return [...top, ...out.slice(out.length - keepTail)].map((line) => truncateToWidth(line, Math.max(10, width - 1)));
|
|
297
|
+
}
|
|
298
|
+
return out.map((line) => truncateToWidth(line, Math.max(10, width - 1)));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
var EMPTY_DETAIL;
|
|
302
|
+
var init_board_views = __esm(() => {
|
|
303
|
+
EMPTY_DETAIL = { screen: "task-detail", icon: "", ref: "", title: "", facts: [], actions: [] };
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// packages/omp-extension-plugin/src/cockpit/drone-preloader.ts
|
|
307
|
+
function classOf(ch) {
|
|
308
|
+
if (ch === "$" || ch === "@")
|
|
309
|
+
return "d";
|
|
310
|
+
if (ch === "=" || ch === "%")
|
|
311
|
+
return "m";
|
|
312
|
+
if (ch === "\\" || ch === "/")
|
|
313
|
+
return "r";
|
|
314
|
+
if (ch === "(" || ch === ")" || ch === "-" || ch === "|" || ch === "'" || ch === ".")
|
|
315
|
+
return "c";
|
|
316
|
+
return "b";
|
|
317
|
+
}
|
|
318
|
+
function colorFor(key) {
|
|
319
|
+
switch (key) {
|
|
320
|
+
case "d":
|
|
321
|
+
return bolden(COL.d);
|
|
322
|
+
case "m":
|
|
323
|
+
return COL.m;
|
|
324
|
+
case "c":
|
|
325
|
+
return COL.c;
|
|
326
|
+
case "r":
|
|
327
|
+
return COL.r;
|
|
328
|
+
case "rb":
|
|
329
|
+
return bolden(COL.rb);
|
|
330
|
+
case "eye":
|
|
331
|
+
return bolden(COL.eye);
|
|
332
|
+
default:
|
|
333
|
+
return (text) => text;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function composeGrid(t) {
|
|
337
|
+
const phase = Math.floor(t / 4);
|
|
338
|
+
const blade = BL[phase % 4];
|
|
339
|
+
const p = Math.sin(t * 0.07);
|
|
340
|
+
const core = p > 0.45 ? "@" : p > -0.1 ? "o" : ".";
|
|
341
|
+
const grid = Array.from({ length: GH }, () => Array.from({ length: GW }, () => " "));
|
|
342
|
+
for (const d of FLEET) {
|
|
343
|
+
const oy = d.y + Math.round(d.amp * Math.sin(t * d.sp + d.ph));
|
|
344
|
+
const ox = d.x + Math.round(d.dx * Math.sin(t * d.dsp + d.ph));
|
|
345
|
+
for (let i = 0;i < d.s.length; i++) {
|
|
346
|
+
const ln = d.s[i];
|
|
347
|
+
const ry = oy + i;
|
|
348
|
+
if (ry < 0 || ry >= GH)
|
|
349
|
+
continue;
|
|
350
|
+
for (let j = 0;j < ln.length; j++) {
|
|
351
|
+
const ch = ln[j];
|
|
352
|
+
if (ch === " ")
|
|
353
|
+
continue;
|
|
354
|
+
const rx = ox + j;
|
|
355
|
+
if (rx < 0 || rx >= GW)
|
|
356
|
+
continue;
|
|
357
|
+
grid[ry][rx] = ch;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const eyes = [];
|
|
362
|
+
const rows = grid.map((row, r) => {
|
|
363
|
+
const line = row.join("");
|
|
364
|
+
const cells = new Array(GW);
|
|
365
|
+
let k = 0;
|
|
366
|
+
while (k < GW) {
|
|
367
|
+
if (line.substr(k, 3) === "!!!") {
|
|
368
|
+
cells[k] = { ch: blade[0], key: "rb" };
|
|
369
|
+
cells[k + 1] = { ch: blade[1], key: "rb" };
|
|
370
|
+
cells[k + 2] = { ch: blade[2], key: "rb" };
|
|
371
|
+
k += 3;
|
|
372
|
+
} else if (line[k] === "?") {
|
|
373
|
+
eyes.push([r, k]);
|
|
374
|
+
cells[k] = { ch: core, key: "eye" };
|
|
375
|
+
k += 1;
|
|
376
|
+
} else {
|
|
377
|
+
cells[k] = { ch: line[k], key: classOf(line[k]) };
|
|
378
|
+
k += 1;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return cells;
|
|
382
|
+
});
|
|
383
|
+
return { rows, eyes };
|
|
384
|
+
}
|
|
385
|
+
function buildGlow(eyes) {
|
|
386
|
+
const glow = Array.from({ length: GH }, () => new Array(GW).fill(0));
|
|
387
|
+
for (const [er, ec] of eyes) {
|
|
388
|
+
for (let r = Math.max(0, er - GLOW_RADIUS);r <= Math.min(GH - 1, er + GLOW_RADIUS); r++) {
|
|
389
|
+
for (let c = Math.max(0, ec - GLOW_RADIUS);c <= Math.min(GW - 1, ec + GLOW_RADIUS); c++) {
|
|
390
|
+
const d2 = (r - er) * (r - er) + (c - ec) * (c - ec);
|
|
391
|
+
if (d2 > GLOW_RADIUS * GLOW_RADIUS)
|
|
392
|
+
continue;
|
|
393
|
+
const v = Math.exp(-d2 / GLOW_SIGMA2);
|
|
394
|
+
if (v > glow[r][c])
|
|
395
|
+
glow[r][c] = v;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return glow;
|
|
400
|
+
}
|
|
401
|
+
function bgLevel(intensity) {
|
|
402
|
+
return Math.round(Math.min(1, Math.max(0, intensity)) * GLOW_LEVELS);
|
|
403
|
+
}
|
|
404
|
+
function bgOpen(level) {
|
|
405
|
+
const f = level / GLOW_LEVELS * GLOW_MAX_BG;
|
|
406
|
+
return `\x1B[48;2;${Math.round(GLOW_RGB[0] * f)};${Math.round(GLOW_RGB[1] * f)};${Math.round(GLOW_RGB[2] * f)}m`;
|
|
407
|
+
}
|
|
408
|
+
function renderRow(cells, glowRow) {
|
|
409
|
+
const outs = [];
|
|
410
|
+
for (let c = 0;c < GW; c++) {
|
|
411
|
+
outs.push({ ch: cells[c].ch, key: cells[c].key, bg: bgLevel(glowRow[c]) });
|
|
412
|
+
if (GAP && c < GW - 1)
|
|
413
|
+
outs.push({ ch: GAP, key: "b", bg: bgLevel(Math.max(glowRow[c], glowRow[c + 1])) });
|
|
414
|
+
}
|
|
415
|
+
let out = "";
|
|
416
|
+
let i = 0;
|
|
417
|
+
while (i < outs.length) {
|
|
418
|
+
const { key, bg } = outs[i];
|
|
419
|
+
let chunk = "";
|
|
420
|
+
while (i < outs.length && outs[i].key === key && outs[i].bg === bg) {
|
|
421
|
+
chunk += outs[i].ch;
|
|
422
|
+
i += 1;
|
|
423
|
+
}
|
|
424
|
+
out += bg > 0 ? bgOpen(bg) + colorFor(key)(chunk) + BG_CLOSE : colorFor(key)(chunk);
|
|
425
|
+
}
|
|
426
|
+
return out;
|
|
427
|
+
}
|
|
428
|
+
function composeFleet(t) {
|
|
429
|
+
const { rows, eyes } = composeGrid(t);
|
|
430
|
+
const glow = buildGlow(eyes);
|
|
431
|
+
return rows.map((cells, r) => renderRow(cells, glow[r]));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
class DronePreloaderComponent {
|
|
435
|
+
tui;
|
|
436
|
+
#start = Date.now();
|
|
437
|
+
#timer;
|
|
438
|
+
constructor(tui) {
|
|
439
|
+
this.tui = tui;
|
|
440
|
+
this.#timer = setInterval(() => this.tui.requestRender(), Math.round(RENDER_MS));
|
|
441
|
+
}
|
|
442
|
+
render(width) {
|
|
443
|
+
const t = Math.round((Date.now() - this.#start) * ANIM_HZ / 1000);
|
|
444
|
+
const sprite = composeFleet(t);
|
|
445
|
+
const left = Math.max(0, Math.floor((width - SPACED_WIDTH) / 2));
|
|
446
|
+
const indent = " ".repeat(left);
|
|
447
|
+
const rows = typeof process.stdout.rows === "number" && process.stdout.rows > 0 ? process.stdout.rows : 0;
|
|
448
|
+
const topPad = rows > sprite.length ? Math.max(0, Math.floor((rows - sprite.length) / 2)) : 0;
|
|
449
|
+
const lines = [];
|
|
450
|
+
for (let i = 0;i < topPad; i++)
|
|
451
|
+
lines.push("");
|
|
452
|
+
for (const row of sprite)
|
|
453
|
+
lines.push(indent + row);
|
|
454
|
+
return lines;
|
|
455
|
+
}
|
|
456
|
+
handleInput(_data) {}
|
|
457
|
+
invalidate() {}
|
|
458
|
+
dispose() {
|
|
459
|
+
if (this.#timer) {
|
|
460
|
+
clearInterval(this.#timer);
|
|
461
|
+
this.#timer = undefined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
var ART, MINI, BL, GW = 52, GH = 27, FLEET, fg = (r, g, b) => (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`, COL, GAP = "", SPACED_WIDTH, bolden = (f) => (t) => `\x1B[1m${f(t)}\x1B[22m`, GLOW_RGB, GLOW_RADIUS = 2, GLOW_SIGMA2, GLOW_MAX_BG = 0.15, GLOW_LEVELS = 10, BG_CLOSE = "\x1B[49m", ANIM_HZ = 120, RENDER_MS;
|
|
466
|
+
var init_drone_preloader = __esm(() => {
|
|
467
|
+
ART = [
|
|
468
|
+
" .-=-. .-=-. ",
|
|
469
|
+
" ( !!! ) ( !!! ) ",
|
|
470
|
+
" '-=-'._ _.'-=-' ",
|
|
471
|
+
" '._ _.' ",
|
|
472
|
+
" '=$$$$$$$=.' ",
|
|
473
|
+
" =$$$$$$$$$$$= ",
|
|
474
|
+
" $$$@@@@@@@@@@$$$ ",
|
|
475
|
+
" $$$@@ @@$$$ ",
|
|
476
|
+
" $$@ ? @$$$ ",
|
|
477
|
+
" $$$@ '-' @$$$ ",
|
|
478
|
+
" $$$@@ @@$$$ ",
|
|
479
|
+
" $$$@@@@@@@@@@$$$ ",
|
|
480
|
+
" =$$$$$$$$$$$= ",
|
|
481
|
+
" '=$$$$$$$=.' ",
|
|
482
|
+
" _.' '._ ",
|
|
483
|
+
" .-=-.' '.-=-. ",
|
|
484
|
+
" ( !!! ) ( !!! ) ",
|
|
485
|
+
" '-=-' '-=-' "
|
|
486
|
+
];
|
|
487
|
+
MINI = [
|
|
488
|
+
"(!!!) (!!!)",
|
|
489
|
+
" \\%==%/ ",
|
|
490
|
+
" %%?%% ",
|
|
491
|
+
" /%==%\\ ",
|
|
492
|
+
"(!!!) (!!!)"
|
|
493
|
+
];
|
|
494
|
+
BL = ["---", "\\\\\\", "|||", "///"];
|
|
495
|
+
FLEET = [
|
|
496
|
+
{ s: MINI, x: 0, y: 2, amp: 1, sp: 0.05, ph: 0, dx: 1, dsp: 0.02 },
|
|
497
|
+
{ s: MINI, x: 39, y: 2, amp: 1, sp: 0.044, ph: 2.1, dx: 1, dsp: 0.017 },
|
|
498
|
+
{ s: MINI, x: 1, y: 20, amp: 1, sp: 0.048, ph: 4.2, dx: 1, dsp: 0.023 },
|
|
499
|
+
{ s: MINI, x: 38, y: 20, amp: 1, sp: 0.041, ph: 1.3, dx: 1, dsp: 0.015 },
|
|
500
|
+
{ s: ART, x: 10, y: 5, amp: 2, sp: 0.04, ph: 0, dx: 0, dsp: 0.011 }
|
|
501
|
+
];
|
|
502
|
+
COL = {
|
|
503
|
+
d: fg(204, 255, 77),
|
|
504
|
+
m: fg(169, 214, 63),
|
|
505
|
+
c: fg(86, 216, 255),
|
|
506
|
+
r: fg(95, 107, 58),
|
|
507
|
+
rb: fg(125, 158, 52),
|
|
508
|
+
eye: fg(234, 255, 176)
|
|
509
|
+
};
|
|
510
|
+
SPACED_WIDTH = GW + GAP.length * (GW - 1);
|
|
511
|
+
GLOW_RGB = [204, 255, 77];
|
|
512
|
+
GLOW_SIGMA2 = 2 * 1.05 * 1.05;
|
|
513
|
+
RENDER_MS = 1000 / 60;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// packages/omp-extension-plugin/src/cockpit/screen-catalog.ts
|
|
517
|
+
function buildScreenFromPanel(panel) {
|
|
518
|
+
const meta = panel.screen ?? {};
|
|
519
|
+
const id = panel.id;
|
|
520
|
+
return {
|
|
521
|
+
id,
|
|
522
|
+
title: meta.title || panel.title || id,
|
|
523
|
+
...meta.label !== undefined ? { label: meta.label } : {},
|
|
524
|
+
...meta.shortcutKey ? { shortcut: { key: meta.shortcutKey, description: meta.shortcutDescription ?? panel.title } } : {},
|
|
525
|
+
...meta.navOrder !== undefined ? {
|
|
526
|
+
nav: {
|
|
527
|
+
order: meta.navOrder,
|
|
528
|
+
label: meta.navLabel ?? panel.title.toUpperCase(),
|
|
529
|
+
description: meta.navDescription ?? panel.description ?? ""
|
|
530
|
+
}
|
|
531
|
+
} : {},
|
|
532
|
+
...panel.disabled !== undefined ? { disabled: panel.disabled } : {}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function resolveScreenCatalog(panels = []) {
|
|
536
|
+
const contributed = panels.filter((panel) => panel.slot === "cockpit" && panel.screen).map(buildScreenFromPanel);
|
|
537
|
+
const seen = new Set(DEFAULT_SCREEN_CATALOG.map((screen) => screen.id));
|
|
538
|
+
const merged = [...DEFAULT_SCREEN_CATALOG];
|
|
539
|
+
for (const screen of contributed) {
|
|
540
|
+
if (seen.has(screen.id))
|
|
541
|
+
continue;
|
|
542
|
+
seen.add(screen.id);
|
|
543
|
+
merged.push(screen);
|
|
544
|
+
}
|
|
545
|
+
return merged;
|
|
546
|
+
}
|
|
547
|
+
function findScreen(catalog, screen) {
|
|
548
|
+
return catalog.find((entry) => entry.id === screen);
|
|
549
|
+
}
|
|
550
|
+
function isRigScreen(value, catalog = DEFAULT_SCREEN_CATALOG) {
|
|
551
|
+
return typeof value === "string" && catalog.some((screen) => screen.id === value);
|
|
552
|
+
}
|
|
553
|
+
function screenTitle(screen, catalog = DEFAULT_SCREEN_CATALOG) {
|
|
554
|
+
return findScreen(catalog, screen)?.title ?? screen;
|
|
555
|
+
}
|
|
556
|
+
function screenLabel(screen, catalog = DEFAULT_SCREEN_CATALOG) {
|
|
557
|
+
const descriptor = findScreen(catalog, screen);
|
|
558
|
+
return descriptor?.label ?? descriptor?.id ?? screen;
|
|
559
|
+
}
|
|
560
|
+
function screenShortcuts(catalog = DEFAULT_SCREEN_CATALOG) {
|
|
561
|
+
return catalog.flatMap((entry) => entry.shortcut ? [{ screen: entry.id, shortcut: entry.shortcut }] : []);
|
|
562
|
+
}
|
|
563
|
+
function cockpitNavScreens(catalog = DEFAULT_SCREEN_CATALOG) {
|
|
564
|
+
return catalog.flatMap((entry) => entry.nav && !entry.disabled ? [{ screen: entry.id, nav: entry.nav }] : []).sort((left, right) => left.nav.order - right.nav.order);
|
|
565
|
+
}
|
|
566
|
+
var DEFAULT_SCREEN_CATALOG;
|
|
567
|
+
var init_screen_catalog = __esm(() => {
|
|
568
|
+
DEFAULT_SCREEN_CATALOG = [
|
|
569
|
+
{ id: "cockpit", title: "Project Cockpit", shortcut: { key: "g", description: "Rig cockpit" } },
|
|
570
|
+
{ id: "task-detail", title: "Task Detail" },
|
|
571
|
+
{ id: "run-detail", title: "Run Detail", label: "run detail" }
|
|
572
|
+
];
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// packages/omp-extension-plugin/src/cockpit/cockpit-product.ts
|
|
576
|
+
import {
|
|
577
|
+
DISPATCH_OUTCOME_CAPABILITY_ID,
|
|
578
|
+
TASK_PROJECTION_CAPABILITY_ID
|
|
579
|
+
} from "@rig/contracts";
|
|
580
|
+
import { defineServiceCapability } from "@rig/core";
|
|
581
|
+
function isRecord(value) {
|
|
582
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
583
|
+
}
|
|
584
|
+
async function resolveCockpitCapabilities(host) {
|
|
585
|
+
if (!host)
|
|
586
|
+
return defaultCockpitCapabilities;
|
|
587
|
+
const [taskProjection, dispatchOutcome] = await Promise.all([
|
|
588
|
+
TaskProjectionCapability.requireService(host),
|
|
589
|
+
DispatchOutcomeCapability.resolveService(host)
|
|
590
|
+
]);
|
|
591
|
+
return {
|
|
592
|
+
taskProjection,
|
|
593
|
+
dispatchOutcome: dispatchOutcome ?? defaultCockpitCapabilities.dispatchOutcome
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
var unavailableTaskProjection, unavailableDispatchOutcome, defaultCockpitCapabilities, TaskProjectionCapability, DispatchOutcomeCapability;
|
|
597
|
+
var init_cockpit_product = __esm(() => {
|
|
598
|
+
unavailableTaskProjection = {
|
|
599
|
+
normalizeStatus: () => "unknown",
|
|
600
|
+
normalizePriority: () => null,
|
|
601
|
+
project: (task) => ({
|
|
602
|
+
id: task.id,
|
|
603
|
+
title: "",
|
|
604
|
+
status: "unknown",
|
|
605
|
+
priority: null,
|
|
606
|
+
metadata: { projection: "unavailable" }
|
|
607
|
+
}),
|
|
608
|
+
assigneeText: () => "",
|
|
609
|
+
title: () => "",
|
|
610
|
+
id: () => null,
|
|
611
|
+
status: () => "unknown",
|
|
612
|
+
description: () => "",
|
|
613
|
+
listRows: () => [],
|
|
614
|
+
boardTasks: () => [],
|
|
615
|
+
stats: () => null
|
|
616
|
+
};
|
|
617
|
+
unavailableDispatchOutcome = {
|
|
618
|
+
humanize: () => null
|
|
619
|
+
};
|
|
620
|
+
defaultCockpitCapabilities = {
|
|
621
|
+
taskProjection: unavailableTaskProjection,
|
|
622
|
+
dispatchOutcome: unavailableDispatchOutcome
|
|
623
|
+
};
|
|
624
|
+
TaskProjectionCapability = defineServiceCapability(TASK_PROJECTION_CAPABILITY_ID);
|
|
625
|
+
DispatchOutcomeCapability = defineServiceCapability(DISPATCH_OUTCOME_CAPABILITY_ID);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// packages/omp-extension-plugin/src/cockpit/extension.ts
|
|
629
|
+
var exports_extension = {};
|
|
630
|
+
__export(exports_extension, {
|
|
631
|
+
default: () => rigExtension,
|
|
632
|
+
__rigExtensionTest: () => __rigExtensionTest
|
|
633
|
+
});
|
|
634
|
+
import { postmortem } from "@oh-my-pi/pi-utils";
|
|
635
|
+
import {
|
|
636
|
+
HELP_CATALOG,
|
|
637
|
+
PLACEMENT_SERVICE,
|
|
638
|
+
RIG_WORKFLOW_JOURNAL_WRITER,
|
|
639
|
+
RIG_WORKFLOW_TASK_SELECTED,
|
|
640
|
+
RIG_WORKFLOW_TARGET_SELECTED,
|
|
641
|
+
RIG_WORKFLOW_STATUS_CHANGED,
|
|
642
|
+
RIG_WORKFLOW_STARTED,
|
|
643
|
+
RIG_WORKFLOW_OPERATOR_NOTE,
|
|
644
|
+
RUN_CONTROL,
|
|
645
|
+
RUN_IDENTITY_ENV,
|
|
646
|
+
RUN_READ_MODEL,
|
|
647
|
+
TASK_IO_SERVICE_CAPABILITY,
|
|
648
|
+
PROJECT_SETUP
|
|
649
|
+
} from "@rig/contracts";
|
|
650
|
+
import { RUN_DISPATCH, RUN_DISCOVERY } from "@rig/contracts";
|
|
651
|
+
import { DOCTOR } from "@rig/contracts";
|
|
652
|
+
import { RIG_COCKPIT_PANEL_SLOT } from "@rig/contracts";
|
|
653
|
+
import { defineCapability } from "@rig/core/capability";
|
|
654
|
+
import { loadCapabilityForRoot, requireCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
655
|
+
import { isKeyRelease, matchesKey } from "@oh-my-pi/pi-tui";
|
|
656
|
+
import { Duration, Effect, Fiber, Stream } from "effect";
|
|
657
|
+
import { accent as accent2, accentDim as accentDim2, bold as bold2, cyan as cyan2, ink as ink5, ink2 as ink22, ink3 as ink32, ink4 as ink42, red as red2, RIG_SPINNER_FRAMES as RIG_SPINNER_FRAMES2, screenGlyph as screenGlyph2 } from "@rig/std-shared/board-theme";
|
|
658
|
+
import { resolvePluginHost } from "@rig/core/project-plugins";
|
|
659
|
+
import { rigProjectRootFromEnv as rigProjectRoot } from "@rig/core/root-resolver";
|
|
660
|
+
function isProjectedRunClassification(value) {
|
|
661
|
+
return Boolean(value && typeof value === "object" && "status" in value && "phase" in value && "rank" in value && "role" in value);
|
|
662
|
+
}
|
|
663
|
+
function runClassificationFor(run, source) {
|
|
664
|
+
if (isProjectedRunClassification(source))
|
|
665
|
+
return source;
|
|
666
|
+
return source?.runClassifications?.get(run.runId) ?? UNCLASSIFIED_RUN_CLASSIFICATION;
|
|
667
|
+
}
|
|
668
|
+
function runActionAffordanceFor(run, source) {
|
|
669
|
+
return isProjectedRunClassification(source) ? null : source?.runActionAffordances?.get(run.runId) ?? null;
|
|
670
|
+
}
|
|
671
|
+
function runStageRowsFor(run, source) {
|
|
672
|
+
return isProjectedRunClassification(source) ? [] : source?.runStageRows?.get(run.runId) ?? [];
|
|
673
|
+
}
|
|
674
|
+
function canSteer(run, source) {
|
|
675
|
+
const affordance = runActionAffordanceFor(run, source);
|
|
676
|
+
if (affordance)
|
|
677
|
+
return affordance.steer;
|
|
678
|
+
if (isProjectedRunClassification(source))
|
|
679
|
+
return false;
|
|
680
|
+
const classification = runClassificationFor(run, source);
|
|
681
|
+
return classification.phase === "active" || classification.phase === "starting";
|
|
682
|
+
}
|
|
683
|
+
function canStop(run, source) {
|
|
684
|
+
const affordance = runActionAffordanceFor(run, source);
|
|
685
|
+
if (affordance)
|
|
686
|
+
return affordance.stop;
|
|
687
|
+
if (isProjectedRunClassification(source))
|
|
688
|
+
return false;
|
|
689
|
+
const classification = runClassificationFor(run, source);
|
|
690
|
+
return classification.isActive && !classification.isTerminal;
|
|
691
|
+
}
|
|
692
|
+
function canPause(run, source) {
|
|
693
|
+
const affordance = runActionAffordanceFor(run, source);
|
|
694
|
+
if (affordance)
|
|
695
|
+
return affordance.pause;
|
|
696
|
+
if (isProjectedRunClassification(source))
|
|
697
|
+
return false;
|
|
698
|
+
const phase = runClassificationFor(run, source).phase;
|
|
699
|
+
return phase === "active" || phase === "starting";
|
|
700
|
+
}
|
|
701
|
+
function canResume(run, source) {
|
|
702
|
+
const affordance = runActionAffordanceFor(run, source);
|
|
703
|
+
if (affordance)
|
|
704
|
+
return affordance.resume;
|
|
705
|
+
if (isProjectedRunClassification(source))
|
|
706
|
+
return false;
|
|
707
|
+
return runClassificationFor(run, source).phase === "paused";
|
|
708
|
+
}
|
|
709
|
+
function workflowTargetForPlacement(placement) {
|
|
710
|
+
return placement?.kind === "remote" ? "remote" : "local";
|
|
711
|
+
}
|
|
712
|
+
async function loadRunIdentityEnv(projectRoot) {
|
|
713
|
+
return loadCapabilityForRoot(projectRoot, RunIdentityEnvCap);
|
|
714
|
+
}
|
|
715
|
+
async function loadWorkflowJournalWriter(projectRoot) {
|
|
716
|
+
return loadCapabilityForRoot(projectRoot, WorkflowJournalWriterCap);
|
|
717
|
+
}
|
|
718
|
+
async function identityFilterForContext(ctx) {
|
|
719
|
+
return (await loadRunIdentityEnv(projectRootFromContext(ctx)))?.resolveRigIdentityFilter(ctx) ?? {};
|
|
720
|
+
}
|
|
721
|
+
function projectRootFromContext(ctx) {
|
|
722
|
+
return ctx.cwd || ctx.sessionManager.getCwd?.() || process.cwd();
|
|
723
|
+
}
|
|
724
|
+
async function requireProjectSetupService(projectRoot) {
|
|
725
|
+
return requireCapabilityForRoot(projectRoot, ProjectSetupCap, "Project setup capability unavailable: load @rig/init-plugin before running setup.");
|
|
726
|
+
}
|
|
727
|
+
function withPlacementDetail(target, projectRoot, service) {
|
|
728
|
+
const status = target.status ?? "ready";
|
|
729
|
+
return {
|
|
730
|
+
...target,
|
|
731
|
+
projectRoot: target.projectRoot ?? projectRoot,
|
|
732
|
+
status,
|
|
733
|
+
taskSource: target.taskSource ?? "configured source",
|
|
734
|
+
detail: target.kind === "remote" ? target.baseUrl ?? target.host ?? "saved remote alias" : "this checkout",
|
|
735
|
+
dispatchReady: service.isDispatchReady({ status })
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
async function readCurrentPlacement(ctx) {
|
|
739
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
740
|
+
const service = await loadPlacementService(projectRoot);
|
|
741
|
+
return withPlacementDetail(await service.readPlacement({ projectRoot }), projectRoot, service);
|
|
742
|
+
}
|
|
743
|
+
async function placementChoices(ctx) {
|
|
744
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
745
|
+
const service = await loadPlacementService(projectRoot);
|
|
746
|
+
return [...await service.listPlacements({ projectRoot })].map((target) => withPlacementDetail(target, projectRoot, service));
|
|
747
|
+
}
|
|
748
|
+
async function promptForNewRemote(ctx) {
|
|
749
|
+
const alias = (await ctx.ui.editor("Rig remote alias", "prod"))?.trim();
|
|
750
|
+
if (!alias)
|
|
751
|
+
return null;
|
|
752
|
+
const host = (await ctx.ui.editor("Rig remote host", "where.rig-does.work"))?.trim();
|
|
753
|
+
if (!host)
|
|
754
|
+
return null;
|
|
755
|
+
const portRaw = (await ctx.ui.editor("Rig remote port", "22"))?.trim() || "22";
|
|
756
|
+
const port = Number.parseInt(portRaw, 10);
|
|
757
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535)
|
|
758
|
+
throw new Error(`Invalid Rig remote port: ${portRaw}`);
|
|
759
|
+
const token = (await ctx.ui.editor("Rig remote token (optional)", ""))?.trim() || null;
|
|
760
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
761
|
+
const service = await loadPlacementService(projectRoot);
|
|
762
|
+
return withPlacementDetail(await service.addPlacement({ projectRoot, placement: { alias, host, port, token, select: true } }), projectRoot, service);
|
|
763
|
+
}
|
|
764
|
+
async function chooseSetupPlacement(ctx) {
|
|
765
|
+
const targets = await placementChoices(ctx);
|
|
766
|
+
const options = [
|
|
767
|
+
...targets.map((target2) => `${target2.alias} (${target2.kind})`),
|
|
768
|
+
"Add saved remote alias"
|
|
769
|
+
];
|
|
770
|
+
const selected = await ctx.ui.select("Rig execution placement", options);
|
|
771
|
+
if (!selected)
|
|
772
|
+
throw new Error("Setup cancelled during placement selection.");
|
|
773
|
+
if (selected === "Add saved remote alias") {
|
|
774
|
+
const target2 = await promptForNewRemote(ctx);
|
|
775
|
+
if (!target2)
|
|
776
|
+
throw new Error("Remote placement was not saved.");
|
|
777
|
+
return { alias: target2.alias, kind: target2.kind, ...target2.host ? { host: target2.host } : {}, ...target2.port ? { port: target2.port } : {} };
|
|
778
|
+
}
|
|
779
|
+
const alias = selected.replace(/\s+\((local|remote)\)$/, "");
|
|
780
|
+
const target = targets.find((entry) => entry.alias === alias);
|
|
781
|
+
if (!target)
|
|
782
|
+
throw new Error(`Unknown placement selected: ${selected}`);
|
|
783
|
+
return { alias: target.alias, kind: target.kind, ...target.host ? { host: target.host } : {}, ...target.port ? { port: target.port } : {} };
|
|
784
|
+
}
|
|
785
|
+
function setupPlacementFromTarget(target) {
|
|
786
|
+
return {
|
|
787
|
+
alias: target.alias,
|
|
788
|
+
kind: target.kind,
|
|
789
|
+
...target.host ? { host: target.host } : {},
|
|
790
|
+
...target.port ? { port: target.port } : {}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
async function chooseAutomaticSetupPlacement(ctx) {
|
|
794
|
+
const current = await readCurrentPlacement(ctx).catch(() => null);
|
|
795
|
+
if (current)
|
|
796
|
+
return setupPlacementFromTarget(current);
|
|
797
|
+
const targets = await placementChoices(ctx);
|
|
798
|
+
const remote = targets.find((target) => target.kind === "remote");
|
|
799
|
+
const fallback = remote ?? targets[0] ?? { alias: "local", kind: "local" };
|
|
800
|
+
return setupPlacementFromTarget(fallback);
|
|
801
|
+
}
|
|
802
|
+
function hasInteractiveSetupTerminal(ctx) {
|
|
803
|
+
return Boolean(ctx.hasUI && process.stdin.isTTY && process.stdout.isTTY);
|
|
804
|
+
}
|
|
805
|
+
async function detectRigStartupStatus(ctx) {
|
|
806
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
807
|
+
const setup = await requireProjectSetupService(projectRoot);
|
|
808
|
+
return setup.detectStartupStatus({ projectRoot });
|
|
809
|
+
}
|
|
810
|
+
async function promptSetupAuth(ctx, status) {
|
|
811
|
+
const choices = [
|
|
812
|
+
...status.auth.ok ? ["Use current GitHub auth"] : [],
|
|
813
|
+
"Import token from gh",
|
|
814
|
+
"Paste token"
|
|
815
|
+
];
|
|
816
|
+
const choice = await ctx.ui.select("GitHub auth", choices);
|
|
817
|
+
if (!choice)
|
|
818
|
+
throw new Error("Setup cancelled during GitHub auth.");
|
|
819
|
+
if (choice === "Use current GitHub auth")
|
|
820
|
+
return {};
|
|
821
|
+
if (choice === "Import token from gh")
|
|
822
|
+
return { importGhToken: true };
|
|
823
|
+
const token = (await ctx.ui.editor("GitHub token", ""))?.trim();
|
|
824
|
+
if (!token)
|
|
825
|
+
throw new Error("GitHub token is required.");
|
|
826
|
+
return { githubToken: token };
|
|
827
|
+
}
|
|
828
|
+
async function runRigSetupFlow(api, ctx) {
|
|
829
|
+
if (!hasInteractiveSetupTerminal(ctx))
|
|
830
|
+
throw new Error("Rig Setup requires an interactive OMP cockpit terminal.");
|
|
831
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
832
|
+
const setup = await requireProjectSetupService(projectRoot);
|
|
833
|
+
const status = await setup.detectStartupStatus({ projectRoot });
|
|
834
|
+
const existingConfig = status.config;
|
|
835
|
+
let configAction = existingConfig.exists ? "repair" : "reconfigure";
|
|
836
|
+
if (existingConfig.exists) {
|
|
837
|
+
const choice = await ctx.ui.select("Rig config already exists", [
|
|
838
|
+
"Repair generated config",
|
|
839
|
+
"Reconfigure and rewrite config",
|
|
840
|
+
"Leave config unchanged; update private state only",
|
|
841
|
+
"Cancel"
|
|
842
|
+
]);
|
|
843
|
+
if (!choice || choice === "Cancel")
|
|
844
|
+
throw new Error("Setup cancelled.");
|
|
845
|
+
if (choice === "Reconfigure and rewrite config")
|
|
846
|
+
configAction = "reconfigure";
|
|
847
|
+
else if (choice === "Leave config unchanged; update private state only")
|
|
848
|
+
configAction = "private-state-only";
|
|
849
|
+
else
|
|
850
|
+
configAction = "repair";
|
|
851
|
+
}
|
|
852
|
+
const slugInput = (await ctx.ui.editor("GitHub repo slug", status.slug ?? ""))?.trim();
|
|
853
|
+
if (!slugInput)
|
|
854
|
+
throw new Error("GitHub repo slug is required.");
|
|
855
|
+
const repo = setup.parseRepoSlug(slugInput);
|
|
856
|
+
const placement = await chooseSetupPlacement(ctx);
|
|
857
|
+
const auth = await promptSetupAuth(ctx, status);
|
|
858
|
+
const rewriteConfig = configAction === "reconfigure" || configAction === "repair" && (!existingConfig.valid || existingConfig.slug !== repo.slug);
|
|
859
|
+
const result = await setup.runSetup({
|
|
860
|
+
projectRoot,
|
|
861
|
+
slug: repo.slug,
|
|
862
|
+
placement,
|
|
863
|
+
rewriteConfig,
|
|
864
|
+
...auth
|
|
865
|
+
});
|
|
866
|
+
await appendOperatorNote(api, projectRoot, `Rig Setup completed for ${repo.slug} (${placement.alias})`);
|
|
867
|
+
const labelsPayload = result.labels;
|
|
868
|
+
const labelsReady = Boolean(labelsPayload && typeof labelsPayload === "object" && "labels" in labelsPayload && Array.isArray(labelsPayload.labels));
|
|
869
|
+
ctx.ui.notify(`Rig Setup complete for ${repo.slug}: ${result.placement}, labels ${labelsReady ? "ready" : "checked"}, Pi ready.`, "info");
|
|
870
|
+
}
|
|
871
|
+
async function runRigAutomaticSetup(api, ctx, status) {
|
|
872
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
873
|
+
const setup = await requireProjectSetupService(projectRoot);
|
|
874
|
+
const slugInput = status.slug?.trim();
|
|
875
|
+
if (!slugInput)
|
|
876
|
+
return false;
|
|
877
|
+
const repo = setup.parseRepoSlug(slugInput);
|
|
878
|
+
const placement = await chooseAutomaticSetupPlacement(ctx);
|
|
879
|
+
const rewriteConfig = !status.config.exists || !status.config.valid;
|
|
880
|
+
try {
|
|
881
|
+
const result = await setup.runSetup({
|
|
882
|
+
projectRoot,
|
|
883
|
+
slug: repo.slug,
|
|
884
|
+
placement,
|
|
885
|
+
rewriteConfig,
|
|
886
|
+
importGhToken: status.auth.source === "gh"
|
|
887
|
+
});
|
|
888
|
+
await appendOperatorNote(api, projectRoot, `Rig auto-setup completed for ${repo.slug} (${placement.alias})`);
|
|
889
|
+
ctx.ui.notify(`Rig auto-setup ready: ${repo.slug} on ${result.placement}.`, "info");
|
|
890
|
+
return true;
|
|
891
|
+
} catch (error) {
|
|
892
|
+
ctx.ui.notify(`Rig auto-setup needs attention: ${notificationMessage(error)}`, "warning");
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function decodeActionValue(value) {
|
|
897
|
+
try {
|
|
898
|
+
return decodeURIComponent(value);
|
|
899
|
+
} catch {
|
|
900
|
+
return value;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function selectedTaskIdFromDispatchAction(id) {
|
|
904
|
+
const prefix = "task-detail:dispatch:";
|
|
905
|
+
if (!id.startsWith(prefix))
|
|
906
|
+
return null;
|
|
907
|
+
const decoded = decodeActionValue(id.slice(prefix.length)).trim();
|
|
908
|
+
return decoded.length > 0 ? decoded : null;
|
|
909
|
+
}
|
|
910
|
+
async function appendTaskSelected(api, projectRoot, task, projection) {
|
|
911
|
+
const id = projection.id(task);
|
|
912
|
+
if (!id)
|
|
913
|
+
return;
|
|
914
|
+
try {
|
|
915
|
+
(await loadWorkflowJournalWriter(projectRoot))?.appendTaskSelected(api, {
|
|
916
|
+
taskId: id,
|
|
917
|
+
title: projection.title(task)
|
|
918
|
+
});
|
|
919
|
+
} catch {}
|
|
920
|
+
}
|
|
921
|
+
function notificationMessage(error) {
|
|
922
|
+
return error instanceof Error ? error.message : String(error);
|
|
923
|
+
}
|
|
924
|
+
async function loadPlacementService(projectRoot) {
|
|
925
|
+
return requireCapabilityForRoot(projectRoot, defineCapability(PLACEMENT_SERVICE), "No placement service plugin provides local/remote placement for this project root.");
|
|
926
|
+
}
|
|
927
|
+
async function loadTaskIo(projectRoot) {
|
|
928
|
+
return requireCapabilityForRoot(projectRoot, defineCapability(TASK_IO_SERVICE_CAPABILITY), "No task-io plugin provides task IO for this project root.");
|
|
929
|
+
}
|
|
930
|
+
async function loadRunControl(projectRoot) {
|
|
931
|
+
return requireCapabilityForRoot(projectRoot, defineCapability(RUN_CONTROL), "No run-control plugin provides run control for this project root.");
|
|
932
|
+
}
|
|
933
|
+
async function loadRunReadModel(projectRoot) {
|
|
934
|
+
return requireCapabilityForRoot(projectRoot, defineCapability(RUN_READ_MODEL), "No run-read-model plugin provides run read-model for this project root.");
|
|
935
|
+
}
|
|
936
|
+
async function dispatchSelectedTask(api, ctx, id, onProgress) {
|
|
937
|
+
const taskIdValue = selectedTaskIdFromDispatchAction(id);
|
|
938
|
+
if (!taskIdValue)
|
|
939
|
+
throw new Error("Task detail dispatch requires a selected task id.");
|
|
940
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
941
|
+
onProgress?.(`dispatch \xB7 loading task ${taskIdValue}\u2026`);
|
|
942
|
+
const task = await (await loadTaskIo(projectRoot)).getTask(projectRoot, taskIdValue);
|
|
943
|
+
if (!task)
|
|
944
|
+
throw new Error(`Selected task not found: ${taskIdValue}`);
|
|
945
|
+
const { caps } = await resolveCockpitRegistry(ctx);
|
|
946
|
+
await appendTaskSelected(api, projectRoot, task, caps.taskProjection);
|
|
947
|
+
onProgress?.("dispatch \xB7 provisioning target and waiting for OMP session\u2026");
|
|
948
|
+
const dispatcher = await requireCapabilityForRoot(projectRoot, defineCapability(RUN_DISPATCH), "No transport plugin provides run dispatch for this project root.");
|
|
949
|
+
const result = await dispatcher.dispatchRun({ projectRoot, taskId: taskIdValue, ...task.title ? { title: task.title } : {} });
|
|
950
|
+
onProgress?.(`dispatch \xB7 session ${result.runId.slice(0, 8)} online; attaching\u2026`);
|
|
951
|
+
const discovery = await loadCapabilityForRoot(projectRoot, defineCapability(RUN_DISCOVERY));
|
|
952
|
+
const attach = discovery ? await discovery.attachViaRelay({
|
|
953
|
+
projectRoot,
|
|
954
|
+
runId: result.runId,
|
|
955
|
+
taskId: taskIdValue,
|
|
956
|
+
identityFilter: await identityFilterForContext(ctx)
|
|
957
|
+
}) : null;
|
|
958
|
+
const joinLink = attach?.joinLink ?? null;
|
|
959
|
+
if (joinLink) {
|
|
960
|
+
await joinCollab(ctx, { joinLink });
|
|
961
|
+
onProgress?.(`dispatch \xB7 attached ${result.runId.slice(0, 8)}`);
|
|
962
|
+
ctx.ui.notify(`Attached ${result.runId.slice(0, 8)} through OMP collab.`, "info");
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const sessionPath = attach?.sessionPath ?? null;
|
|
966
|
+
const switchSession = ctx.switchSession;
|
|
967
|
+
if (sessionPath && switchSession) {
|
|
968
|
+
const switched = await switchSession(sessionPath);
|
|
969
|
+
if (!switched.cancelled) {
|
|
970
|
+
onProgress?.(`dispatch \xB7 attached ${result.runId.slice(0, 8)}`);
|
|
971
|
+
ctx.ui.notify(`Attached ${result.runId.slice(0, 8)} through OMP collab.`, "info");
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
onProgress?.(`dispatch \xB7 run ${result.runId.slice(0, 8)} online; attach from Runs`);
|
|
976
|
+
ctx.ui.notify(`Dispatched ${taskIdValue} as ${result.runId.slice(0, 8)} \u2014 open Runs to attach when it comes online.`, "info");
|
|
977
|
+
}
|
|
978
|
+
async function collectDoctorChecks(_api, ctx) {
|
|
979
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
980
|
+
const service = await loadCapabilityForRoot(projectRoot, defineCapability(DOCTOR));
|
|
981
|
+
if (!service)
|
|
982
|
+
return [];
|
|
983
|
+
return service.runDoctorChecks({ projectRoot, identityContext: ctx });
|
|
984
|
+
}
|
|
985
|
+
async function producePanelContent(projectRoot, panelId, context) {
|
|
986
|
+
const host = await resolvePluginHost(projectRoot).then((r) => r.host).catch(() => null);
|
|
987
|
+
const panel = host?.listExecutablePanels().find((p) => p.slot === RIG_COCKPIT_PANEL_SLOT && p.id === panelId);
|
|
988
|
+
if (!panel)
|
|
989
|
+
return { rows: [], board: undefined };
|
|
990
|
+
const rows = panel.produce ? await panel.produce(context) : [];
|
|
991
|
+
const board = panel.produceBoard ? await panel.produceBoard(context) : undefined;
|
|
992
|
+
return {
|
|
993
|
+
rows: Array.isArray(rows) ? rows : [],
|
|
994
|
+
board: board && typeof board === "object" && "kind" in board ? board : undefined
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
async function loadRigFlowState(ctx, screen, selectedTaskId, api, runs = [], actionRowsEnabled = true) {
|
|
998
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
999
|
+
try {
|
|
1000
|
+
const needsServer = screen === "cockpit" || screen === "runs" || screen === "run-detail" || screen === "server" || screen === "tasks" || screen === "task-detail";
|
|
1001
|
+
const needsRunSummaries = screen === "cockpit" || screen === "runs" || screen === "run-detail";
|
|
1002
|
+
const server = needsServer ? await readCurrentPlacement(ctx) : null;
|
|
1003
|
+
const taskIo = screen === "task-detail" && selectedTaskId ? await loadTaskIo(projectRoot) : null;
|
|
1004
|
+
const selectedTask = screen === "task-detail" && selectedTaskId ? await taskIo?.getTask(projectRoot, selectedTaskId) ?? null : null;
|
|
1005
|
+
const readModel = needsRunSummaries ? await loadRunReadModel(projectRoot) : null;
|
|
1006
|
+
const runControl = readModel ? await loadRunControl(projectRoot) : null;
|
|
1007
|
+
const readModelDiscoveryFilter = readModel ? await identityFilterForContext(ctx) : {};
|
|
1008
|
+
const runClassifications = readModel ? new Map(runs.map((run) => [run.runId, readModel.classifyRun(run)])) : undefined;
|
|
1009
|
+
const runStageRows = readModel ? new Map(runs.map((run) => [run.runId, readModel.projectRunStages(run)])) : undefined;
|
|
1010
|
+
const runActionAffordances = runControl ? new Map(await Promise.all(runs.map(async (run) => {
|
|
1011
|
+
const [attach, steer, stop, pause, resume] = await Promise.all([
|
|
1012
|
+
runControl.resolveControlTarget({ projectRoot, runId: run.runId, purpose: "attach", discoveryFilter: readModelDiscoveryFilter }),
|
|
1013
|
+
runControl.resolveControlTarget({ projectRoot, runId: run.runId, purpose: "steer", discoveryFilter: readModelDiscoveryFilter }),
|
|
1014
|
+
runControl.resolveControlTarget({ projectRoot, runId: run.runId, purpose: "stop", discoveryFilter: readModelDiscoveryFilter }),
|
|
1015
|
+
runControl.resolveControlTarget({ projectRoot, runId: run.runId, purpose: "pause", discoveryFilter: readModelDiscoveryFilter }),
|
|
1016
|
+
runControl.resolveControlTarget({ projectRoot, runId: run.runId, purpose: "resume", discoveryFilter: readModelDiscoveryFilter })
|
|
1017
|
+
]).catch(() => [null, null, null, null, null]);
|
|
1018
|
+
return [run.runId, { attach: attach?.canDeliver === true, steer: steer?.canDeliver === true, stop: stop?.canDeliver === true, pause: pause?.canDeliver === true, resume: resume?.canDeliver === true }];
|
|
1019
|
+
}))) : undefined;
|
|
1020
|
+
const { rows: panelRows, board } = await producePanelContent(projectRoot, screen, { projectRoot, identityContext: ctx, actionRowsEnabled, runs, ...runClassifications ? { runClassifications } : {} });
|
|
1021
|
+
return {
|
|
1022
|
+
projectRoot,
|
|
1023
|
+
...server ? { server } : {},
|
|
1024
|
+
...selectedTask ? { selectedTask, selectedTaskId } : selectedTaskId ? { selectedTaskId } : {},
|
|
1025
|
+
...panelRows.length ? { panelRows } : {},
|
|
1026
|
+
...board ? { board } : {},
|
|
1027
|
+
...runClassifications ? { runClassifications } : {},
|
|
1028
|
+
...runStageRows ? { runStageRows } : {},
|
|
1029
|
+
...runActionAffordances ? { runActionAffordances } : {}
|
|
1030
|
+
};
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
return { projectRoot, error: notificationMessage(error) };
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function isMissingRigConfigError(error) {
|
|
1036
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1037
|
+
return message.includes("no .rig/rig.config") || message.includes("no rig.config.{ts,mts,json}") || message.includes("no rig.config");
|
|
1038
|
+
}
|
|
1039
|
+
async function resolveCockpitRegistry(ctx) {
|
|
1040
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1041
|
+
const resolved = await (async () => {
|
|
1042
|
+
try {
|
|
1043
|
+
return await resolvePluginHost(projectRoot);
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
if (isMissingRigConfigError(error))
|
|
1046
|
+
return null;
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
})();
|
|
1050
|
+
if (!resolved)
|
|
1051
|
+
return { catalog: DEFAULT_SCREEN_CATALOG, caps: defaultCockpitCapabilities };
|
|
1052
|
+
const { host } = resolved;
|
|
1053
|
+
return { catalog: resolveScreenCatalog(host.listPanels()), caps: await resolveCockpitCapabilities(host) };
|
|
1054
|
+
}
|
|
1055
|
+
async function joinCollab(ctx, input) {
|
|
1056
|
+
const facade = ctx.collab;
|
|
1057
|
+
if (facade?.join) {
|
|
1058
|
+
await facade.join(input);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (facade?.joinCollabSession) {
|
|
1062
|
+
await facade.joinCollabSession(input);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
throw new Error("OMP collab facade is unavailable for this extension context.");
|
|
1066
|
+
}
|
|
1067
|
+
async function discoverRuns(ctx) {
|
|
1068
|
+
const projectRoot = ctx ? projectRootFromContext(ctx) : rigProjectRoot();
|
|
1069
|
+
const readModel = await loadRunReadModel(projectRoot);
|
|
1070
|
+
return [...await readModel.listRuns({ projectRoot, discoveryFilter: ctx ? await identityFilterForContext(ctx) : {} })];
|
|
1071
|
+
}
|
|
1072
|
+
async function joinRun(ctx, encodedRunId) {
|
|
1073
|
+
const runId = decodeURIComponent(encodedRunId);
|
|
1074
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1075
|
+
const runControl = await loadRunControl(projectRoot);
|
|
1076
|
+
const target = await runControl.resolveControlTarget({ projectRoot, runId, purpose: "attach", discoveryFilter: await identityFilterForContext(ctx) });
|
|
1077
|
+
if (!target?.joinLink || target.canDeliver !== true) {
|
|
1078
|
+
ctx.ui.notify(target?.reason ?? "Selected run is not currently available through collab.", "warning");
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
await joinCollab(ctx, { joinLink: target.joinLink, ...target.collabCwd != null ? { cwd: target.collabCwd } : {} });
|
|
1082
|
+
ctx.ui.notify(`Joined ${runId.slice(0, 8)} through OMP collab.`, "info");
|
|
1083
|
+
}
|
|
1084
|
+
async function steerRun(_api, ctx, encodedRunId, stop = false, input) {
|
|
1085
|
+
const runId = decodeURIComponent(encodedRunId);
|
|
1086
|
+
const message = stop ? input ?? await ctx.ui.editor("Stop reason (optional)", "operator requested stop") : (input ?? await ctx.ui.editor("Steer Rig run", ""))?.trim();
|
|
1087
|
+
if (!message)
|
|
1088
|
+
return;
|
|
1089
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1090
|
+
const runControl = await loadRunControl(projectRoot);
|
|
1091
|
+
const result = await runControl.deliverControl({ projectRoot, runId, control: stop ? { kind: "stop", reason: message } : { kind: "steer", message }, discoveryFilter: await identityFilterForContext(ctx) });
|
|
1092
|
+
if (!result.delivered)
|
|
1093
|
+
throw new Error(result.detail ?? `Could not ${result.kind} run ${runId}.`);
|
|
1094
|
+
ctx.ui.notify(stop ? `Stop requested for ${runId}.` : `Steered ${runId}.`, "info");
|
|
1095
|
+
}
|
|
1096
|
+
async function controlRun(ctx, encodedRunId, kind) {
|
|
1097
|
+
const runId = decodeURIComponent(encodedRunId);
|
|
1098
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1099
|
+
const runControl = await loadRunControl(projectRoot);
|
|
1100
|
+
const result = await runControl.deliverControl({ projectRoot, runId, control: { kind }, discoveryFilter: await identityFilterForContext(ctx) });
|
|
1101
|
+
if (!result.delivered)
|
|
1102
|
+
throw new Error(result.detail ?? `Could not ${kind} run ${runId}.`);
|
|
1103
|
+
ctx.ui.notify(kind === "pause" ? `Pause requested for ${runId}.` : `Resume requested for ${runId}.`, "info");
|
|
1104
|
+
}
|
|
1105
|
+
function inboxActionId(request, decision) {
|
|
1106
|
+
const base = `inbox:${encodeURIComponent(request.runId)}:${encodeURIComponent(request.requestId)}`;
|
|
1107
|
+
return decision ? `${base}:${decision}` : base;
|
|
1108
|
+
}
|
|
1109
|
+
function parseInboxActionTarget(id) {
|
|
1110
|
+
if (!id.startsWith("inbox:"))
|
|
1111
|
+
return null;
|
|
1112
|
+
const [encodedRunId, encodedRequestId, encodedDecision] = id.slice("inbox:".length).split(":");
|
|
1113
|
+
if (!encodedRunId || !encodedRequestId)
|
|
1114
|
+
return null;
|
|
1115
|
+
try {
|
|
1116
|
+
const runId = decodeURIComponent(encodedRunId);
|
|
1117
|
+
const requestId = decodeURIComponent(encodedRequestId);
|
|
1118
|
+
const decision = encodedDecision === "approved" || encodedDecision === "rejected" || encodedDecision === "answered" ? encodedDecision : null;
|
|
1119
|
+
return runId && requestId ? { runId, requestId, decision } : null;
|
|
1120
|
+
} catch {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
function inboxPrompt(request) {
|
|
1125
|
+
return request.body ? `${request.title}
|
|
1126
|
+
${request.body}` : request.title;
|
|
1127
|
+
}
|
|
1128
|
+
async function resolveInboxRequest(_api, ctx, id, answerInput) {
|
|
1129
|
+
const target = parseInboxActionTarget(id);
|
|
1130
|
+
if (!target) {
|
|
1131
|
+
ctx.ui.notify("Inbox action is malformed; refresh the Rig Inbox.", "warning");
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1135
|
+
const readModel = await loadRunReadModel(projectRoot);
|
|
1136
|
+
const runControl = await loadRunControl(projectRoot);
|
|
1137
|
+
const run = await readModel.getRun({ projectRoot, selector: { id: target.runId, kind: "run-id" }, discoveryFilter: await identityFilterForContext(ctx) });
|
|
1138
|
+
const request = run?.inbox.find((item) => item.requestId === target.requestId);
|
|
1139
|
+
if (!run || !request) {
|
|
1140
|
+
ctx.ui.notify("Inbox request is stale or already resolved; refresh the Rig Inbox.", "warning");
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (request.kind === "approval") {
|
|
1144
|
+
const decision = target.decision === "approved" || target.decision === "rejected" ? target.decision : await (async () => {
|
|
1145
|
+
const choice = await ctx.ui.select(inboxPrompt(request), ["Approve", "Reject"]);
|
|
1146
|
+
return choice === "Approve" ? "approved" : choice === "Reject" ? "rejected" : null;
|
|
1147
|
+
})();
|
|
1148
|
+
const approvalDecision = decision === "approved" ? "approve" : decision === "rejected" ? "reject" : null;
|
|
1149
|
+
if (!approvalDecision) {
|
|
1150
|
+
ctx.ui.notify("Inbox approval cancelled.", "info");
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const result2 = await runControl.resolveInboxRequest({ projectRoot, runId: run.runId, resolution: { kind: "approval", requestId: request.requestId, decision: approvalDecision }, discoveryFilter: await identityFilterForContext(ctx) });
|
|
1154
|
+
if (!result2.delivered)
|
|
1155
|
+
throw new Error(result2.detail ?? `Inbox request ${request.requestId} was not delivered.`);
|
|
1156
|
+
ctx.ui.notify(`Inbox request ${request.requestId} ${decision}.`, "info");
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const answer = answerInput !== undefined ? answerInput : request.options && request.options.length > 0 ? await ctx.ui.select(inboxPrompt(request), [...request.options]) : await ctx.ui.editor(`Rig input: ${request.title}`, request.body ?? "");
|
|
1160
|
+
if (answer === undefined) {
|
|
1161
|
+
ctx.ui.notify("Inbox input cancelled.", "info");
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const result = await runControl.resolveInboxRequest({ projectRoot, runId: run.runId, resolution: { kind: "input", requestId: request.requestId, answers: { answer: String(answer) } }, discoveryFilter: await identityFilterForContext(ctx) });
|
|
1165
|
+
if (!result.delivered)
|
|
1166
|
+
throw new Error(result.detail ?? `Inbox request ${request.requestId} was not delivered.`);
|
|
1167
|
+
ctx.ui.notify(`Inbox request ${request.requestId} answered.`, "info");
|
|
1168
|
+
}
|
|
1169
|
+
function defaultFlowActions(api, ctx) {
|
|
1170
|
+
return {
|
|
1171
|
+
async act(id, input, onProgress) {
|
|
1172
|
+
if (id === "setup:start") {
|
|
1173
|
+
await runRigSetupFlow(api, ctx);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (id === "setup:doctor") {
|
|
1177
|
+
const status = await detectRigStartupStatus(ctx);
|
|
1178
|
+
const checks = await collectDoctorChecks(api, ctx);
|
|
1179
|
+
notifyDoctor(ctx, "Setup doctor", checks);
|
|
1180
|
+
if (status.reasons.length > 0 && ctx.hasUI)
|
|
1181
|
+
ctx.ui.notify(`Setup needs attention: ${status.reasons.join("; ")}`, "warning");
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (id.startsWith("server:select:")) {
|
|
1185
|
+
const alias = decodeActionValue(id.slice("server:select:".length));
|
|
1186
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1187
|
+
const service = await loadPlacementService(projectRoot);
|
|
1188
|
+
await service.selectPlacement({ projectRoot, alias });
|
|
1189
|
+
await appendCurrentTargetSelected(api, service, projectRoot, `selected target ${alias}`);
|
|
1190
|
+
if (ctx.hasUI)
|
|
1191
|
+
ctx.ui.notify(`Selected Rig target ${alias}.`, "info");
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (id === "server:add-remote") {
|
|
1195
|
+
const target = await promptForNewRemote(ctx);
|
|
1196
|
+
if (target)
|
|
1197
|
+
await appendTargetSelected(api, projectRootFromContext(ctx), target, `added remote target ${target.alias}`);
|
|
1198
|
+
if (ctx.hasUI)
|
|
1199
|
+
ctx.ui.notify("Rig remote target saved and selected.", "info");
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (id.startsWith("remote:use:")) {
|
|
1203
|
+
const alias = decodeActionValue(id.slice("remote:use:".length));
|
|
1204
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1205
|
+
const service = await loadPlacementService(projectRoot);
|
|
1206
|
+
await service.selectPlacement({ projectRoot, alias });
|
|
1207
|
+
await appendCurrentTargetSelected(api, service, projectRoot, `selected remote endpoint ${alias}`);
|
|
1208
|
+
if (ctx.hasUI)
|
|
1209
|
+
ctx.ui.notify(`Selected Rig remote endpoint ${alias}.`, "info");
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (id === "remote:add") {
|
|
1213
|
+
const target = await promptForNewRemote(ctx);
|
|
1214
|
+
if (target)
|
|
1215
|
+
await appendTargetSelected(api, projectRootFromContext(ctx), target, `added remote endpoint ${target.alias}`);
|
|
1216
|
+
if (ctx.hasUI)
|
|
1217
|
+
ctx.ui.notify("Rig remote endpoint saved and selected.", "info");
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (id.startsWith("remote:remove:")) {
|
|
1221
|
+
const alias = decodeActionValue(id.slice("remote:remove:".length));
|
|
1222
|
+
const projectRoot = projectRootFromContext(ctx);
|
|
1223
|
+
const removed = (await (await loadPlacementService(projectRoot)).removePlacement({ projectRoot, alias })).removed;
|
|
1224
|
+
if (ctx.hasUI)
|
|
1225
|
+
ctx.ui.notify(removed ? `Removed Rig remote endpoint ${alias}.` : `Rig remote endpoint ${alias} was not found.`, removed ? "info" : "warning");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (id === "server:refresh" || id === "tasks:refresh") {
|
|
1229
|
+
if (ctx.hasUI)
|
|
1230
|
+
ctx.ui.notify("Rig Cockpit refreshed selected server/task state.", "info");
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (id.startsWith("task-detail:dispatch:")) {
|
|
1234
|
+
await dispatchSelectedTask(api, ctx, id, onProgress);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (id.startsWith("view-pr:")) {
|
|
1238
|
+
const url = decodeActionValue(id.slice("view-pr:".length));
|
|
1239
|
+
if (ctx.hasUI)
|
|
1240
|
+
ctx.ui.notify(`Pull request: ${url}`, "info");
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (id.startsWith("run:")) {
|
|
1244
|
+
await joinRun(ctx, id.slice("run:".length));
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (id.startsWith("run-steer:")) {
|
|
1248
|
+
await steerRun(api, ctx, id.slice("run-steer:".length), false, input);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
if (id.startsWith("run-stop:")) {
|
|
1252
|
+
await steerRun(api, ctx, id.slice("run-stop:".length), true, input);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
if (id.startsWith("run-pause:")) {
|
|
1256
|
+
await controlRun(ctx, id.slice("run-pause:".length), "pause");
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (id.startsWith("run-resume:")) {
|
|
1260
|
+
await controlRun(ctx, id.slice("run-resume:".length), "resume");
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (id.startsWith("inbox:")) {
|
|
1264
|
+
await resolveInboxRequest(api, ctx, id, input);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (id === "doctor:sessions") {
|
|
1268
|
+
await runSessionDoctor(api, ctx);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (id === "doctor:collab") {
|
|
1272
|
+
await runCollabDoctor(ctx);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (ctx.hasUI)
|
|
1276
|
+
ctx.ui.notify(`Unknown Rig action: ${id}`, "warning");
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function notifyDoctor(ctx, title, checks) {
|
|
1281
|
+
const failed = checks.filter((check) => check.level === "fail" || check.status === "fail");
|
|
1282
|
+
const summary = checks.map((check) => `${check.level ?? check.status ?? "warn"} ${check.label}: ${check.detail ?? ""}`).join(" \xB7 ");
|
|
1283
|
+
ctx.ui.notify(`${title}: ${failed.length === 0 ? "ok" : "failed"} \xB7 ${summary}`, failed.length === 0 ? "info" : "error");
|
|
1284
|
+
}
|
|
1285
|
+
async function appendTargetSelected(api, projectRoot, placement, reason) {
|
|
1286
|
+
try {
|
|
1287
|
+
(await loadWorkflowJournalWriter(projectRoot))?.appendTargetSelected(api, {
|
|
1288
|
+
target: workflowTargetForPlacement(placement),
|
|
1289
|
+
...reason ? { reason } : {}
|
|
1290
|
+
});
|
|
1291
|
+
} catch {}
|
|
1292
|
+
}
|
|
1293
|
+
async function appendCurrentTargetSelected(api, service, projectRoot, reason) {
|
|
1294
|
+
try {
|
|
1295
|
+
await appendTargetSelected(api, projectRoot, await service.readPlacement({ projectRoot }), reason);
|
|
1296
|
+
} catch {}
|
|
1297
|
+
}
|
|
1298
|
+
async function appendWorkflowStarted(api, ctx, state) {
|
|
1299
|
+
const projectRoot = state.projectRoot ?? projectRootFromContext(ctx);
|
|
1300
|
+
try {
|
|
1301
|
+
const identity = (await loadRunIdentityEnv(projectRoot))?.resolveRigIdentity(ctx) ?? null;
|
|
1302
|
+
if (!identity)
|
|
1303
|
+
return;
|
|
1304
|
+
(await loadWorkflowJournalWriter(projectRoot))?.appendStarted(api, {
|
|
1305
|
+
workflowId: ctx.sessionManager.getSessionId(),
|
|
1306
|
+
target: workflowTargetForPlacement(state.server),
|
|
1307
|
+
selectedRepo: identity.selectedRepo,
|
|
1308
|
+
owner: identity.owner
|
|
1309
|
+
});
|
|
1310
|
+
} catch {}
|
|
1311
|
+
}
|
|
1312
|
+
async function appendOperatorNote(api, projectRoot, note) {
|
|
1313
|
+
try {
|
|
1314
|
+
(await loadWorkflowJournalWriter(projectRoot))?.appendOperatorNote(api, { note });
|
|
1315
|
+
} catch {}
|
|
1316
|
+
}
|
|
1317
|
+
async function runSessionDoctor(api, ctx) {
|
|
1318
|
+
const checks = await collectDoctorChecks(api, ctx);
|
|
1319
|
+
notifyDoctor(ctx, "Sessions doctor", checks);
|
|
1320
|
+
}
|
|
1321
|
+
async function runCollabDoctor(ctx) {
|
|
1322
|
+
const runs = await discoverRuns(ctx);
|
|
1323
|
+
const identityFilter = (await loadRunIdentityEnv(projectRootFromContext(ctx)))?.resolveRigIdentityFilter(ctx) ?? null;
|
|
1324
|
+
notifyDoctor(ctx, "Collab doctor", [
|
|
1325
|
+
{ label: "identity-filter", level: identityFilter ? "ok" : "warn", detail: identityFilter ? "available" : "unavailable" },
|
|
1326
|
+
{ label: "run-discovery", level: "ok", detail: `${runs.length} run record(s)` },
|
|
1327
|
+
{ label: "join-links", level: runs.some((run) => run.joinLink) ? "ok" : "warn", detail: `${runs.filter((run) => run.joinLink).length} joinable run(s)` }
|
|
1328
|
+
]);
|
|
1329
|
+
}
|
|
1330
|
+
function sessionDescription(run) {
|
|
1331
|
+
const id = run.runId.slice(0, 8);
|
|
1332
|
+
const cwd = run.collabCwd || run.worktreePath || run.sessionPath || "(unknown cwd)";
|
|
1333
|
+
const link = collabLinks(run);
|
|
1334
|
+
return link ? `${id} \xB7 ${cwd} \xB7 ${link}` : `${id} \xB7 ${cwd}`;
|
|
1335
|
+
}
|
|
1336
|
+
function collabLinks(run) {
|
|
1337
|
+
const join = run.joinLink ? `joinLink ${run.joinLink}` : "";
|
|
1338
|
+
const web = run.webLink ? `webLink ${run.webLink}` : "";
|
|
1339
|
+
return join && web ? `${join} \xB7 ${web}` : join || web || "";
|
|
1340
|
+
}
|
|
1341
|
+
function findRunById(runs, runId) {
|
|
1342
|
+
if (!runId)
|
|
1343
|
+
return null;
|
|
1344
|
+
return runs.find((item) => item.runId === runId) ?? null;
|
|
1345
|
+
}
|
|
1346
|
+
function canAttachRun(run, source) {
|
|
1347
|
+
const affordance = runActionAffordanceFor(run, source);
|
|
1348
|
+
if (affordance)
|
|
1349
|
+
return affordance.attach;
|
|
1350
|
+
return Boolean(run.joinLink && !run.stale);
|
|
1351
|
+
}
|
|
1352
|
+
function canSteerFromCockpit(run, source) {
|
|
1353
|
+
return canSteer(run, source);
|
|
1354
|
+
}
|
|
1355
|
+
function runListActionHints(run, source) {
|
|
1356
|
+
const encodedRunId = encodeURIComponent(run.runId);
|
|
1357
|
+
return [
|
|
1358
|
+
...canAttachRun(run, source) ? [{ key: "a", label: "attach", id: `run:${encodedRunId}` }] : [],
|
|
1359
|
+
...canSteerFromCockpit(run, source) ? [{ key: "s", label: "steer", id: `run-steer:${encodedRunId}` }] : [],
|
|
1360
|
+
...canStop(run, source) ? [{ key: "x", label: "stop", id: `run-stop:${encodedRunId}` }] : [],
|
|
1361
|
+
...canPause(run, source) ? [{ key: "p", label: "pause", id: `run-pause:${encodedRunId}` }] : [],
|
|
1362
|
+
...canResume(run, source) ? [{ key: "u", label: "resume", id: `run-resume:${encodedRunId}` }] : []
|
|
1363
|
+
];
|
|
1364
|
+
}
|
|
1365
|
+
function serverLabel(server) {
|
|
1366
|
+
return server?.alias?.trim() || "unselected";
|
|
1367
|
+
}
|
|
1368
|
+
function nextStepHint(server, runs, state) {
|
|
1369
|
+
if (!server)
|
|
1370
|
+
return "next: Server";
|
|
1371
|
+
if (runs.some((run) => {
|
|
1372
|
+
const classification = state.runClassifications?.get(run.runId);
|
|
1373
|
+
return classification ? classification.isActive && !classification.isTerminal : false;
|
|
1374
|
+
}))
|
|
1375
|
+
return "next: Runs";
|
|
1376
|
+
return "next: Tasks";
|
|
1377
|
+
}
|
|
1378
|
+
function taskDetailModel(state, server, projection) {
|
|
1379
|
+
const task = state.selectedTask ?? null;
|
|
1380
|
+
const id = projection.id(task);
|
|
1381
|
+
if (!task || !id) {
|
|
1382
|
+
return {
|
|
1383
|
+
screen: "task-detail",
|
|
1384
|
+
icon: screenGlyph2("task-detail"),
|
|
1385
|
+
ref: "TASK",
|
|
1386
|
+
title: "No task selected",
|
|
1387
|
+
facts: [{ label: "hint", value: "Open a task row from Tasks before dispatch." }],
|
|
1388
|
+
actions: []
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
const placement = serverLabel(server);
|
|
1392
|
+
const dispatchReady = server ? server.dispatchReady !== false : true;
|
|
1393
|
+
const dispatchAction = dispatchReady ? [{
|
|
1394
|
+
id: `task-detail:dispatch:${encodeURIComponent(id)}`,
|
|
1395
|
+
label: "DISPATCH",
|
|
1396
|
+
glyph: "\uF0E7",
|
|
1397
|
+
value: `\u2192 ${placement}`,
|
|
1398
|
+
hint: "submit this run to the selected target, then attach"
|
|
1399
|
+
}] : [];
|
|
1400
|
+
return {
|
|
1401
|
+
screen: "task-detail",
|
|
1402
|
+
icon: screenGlyph2("task-detail"),
|
|
1403
|
+
ref: `TASK ${id}`,
|
|
1404
|
+
title: projection.title(task),
|
|
1405
|
+
status: projection.status(task),
|
|
1406
|
+
body: projection.description(task),
|
|
1407
|
+
bodyLabel: "task",
|
|
1408
|
+
facts: [
|
|
1409
|
+
...task.url ? [{ label: "url", value: task.url, tone: "link" }] : [],
|
|
1410
|
+
{ label: "source", value: task.source ?? "configured task source" },
|
|
1411
|
+
{ label: "placement", value: placement }
|
|
1412
|
+
],
|
|
1413
|
+
actions: dispatchAction
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
function runDetailModel(run, source) {
|
|
1417
|
+
const classification = runClassificationFor(run, source);
|
|
1418
|
+
const encodedRunId = encodeURIComponent(run.runId);
|
|
1419
|
+
const status = classification.status;
|
|
1420
|
+
const joinable = canAttachRun(run, source);
|
|
1421
|
+
const shortId = run.runId.slice(0, 8);
|
|
1422
|
+
const sourceTask = run.projection.record.sourceTask;
|
|
1423
|
+
const sourceTaskId = isRecord(sourceTask) && typeof sourceTask.id === "string" ? sourceTask.id : null;
|
|
1424
|
+
const recoveryTaskId = run.taskId || sourceTaskId;
|
|
1425
|
+
const issueUrl = isRecord(sourceTask) && typeof sourceTask.url === "string" ? sourceTask.url : null;
|
|
1426
|
+
const isFailed = classification.offersRecovery;
|
|
1427
|
+
const stages = runStageRowsFor(run, source).map((row) => ({ label: row.label, status: String(row.currentValue ?? "pending") }));
|
|
1428
|
+
return {
|
|
1429
|
+
screen: "run-detail",
|
|
1430
|
+
icon: screenGlyph2("run-detail"),
|
|
1431
|
+
ref: `RUN ${shortId}`,
|
|
1432
|
+
title: run.title || `Run ${shortId}`,
|
|
1433
|
+
status,
|
|
1434
|
+
body: run.errorSummary || sessionDescription(run),
|
|
1435
|
+
bodyLabel: "summary",
|
|
1436
|
+
facts: [
|
|
1437
|
+
...run.taskId ? [{ label: "task", value: run.taskId }] : [],
|
|
1438
|
+
{ label: "cwd", value: run.collabCwd || run.worktreePath || run.sessionPath || "(unknown cwd)" },
|
|
1439
|
+
...run.webLink ? [{ label: "web", value: run.webLink, tone: "link" }] : [],
|
|
1440
|
+
...run.prUrl ? [{ label: "pr", value: run.prUrl, tone: "link" }] : [],
|
|
1441
|
+
...issueUrl ? [{ label: "issue", value: issueUrl, tone: "link" }] : [],
|
|
1442
|
+
...run.inbox.length > 0 ? [{ label: "gates", value: `${run.inbox.length} waiting`, tone: "warn" }] : []
|
|
1443
|
+
],
|
|
1444
|
+
...stages.length > 0 ? { stages } : {},
|
|
1445
|
+
actions: [
|
|
1446
|
+
...run.inbox.map((request) => ({
|
|
1447
|
+
id: inboxActionId({ runId: run.runId, requestId: request.requestId }),
|
|
1448
|
+
label: request.kind === "approval" ? "GATE" : "INPUT",
|
|
1449
|
+
glyph: "\uF0E0",
|
|
1450
|
+
value: request.title.slice(0, 32) || request.requestId,
|
|
1451
|
+
hint: request.kind === "approval" ? "approve or reject this pending gate" : "answer this pending input"
|
|
1452
|
+
})),
|
|
1453
|
+
...joinable ? [{ id: "detail:join", label: "JOIN", glyph: "\uF090", value: "live", hint: "attach to this run's live OMP collab session" }] : [],
|
|
1454
|
+
...canSteerFromCockpit(run, source) ? [{ id: `run-steer:${encodedRunId}`, label: "STEER", glyph: "\uF1D8", value: "message", hint: classification.isNeedsAttention ? "nudge the stalled live agent with operator guidance" : "send a steering prompt to the running agent" }] : [],
|
|
1455
|
+
...canStop(run, source) ? [{ id: `run-stop:${encodedRunId}`, label: "STOP", glyph: "\uF04D", value: "graceful", hint: "stop this run (terminal)", danger: true }] : [],
|
|
1456
|
+
...canPause(run, source) ? [{ id: `run-pause:${encodedRunId}`, label: "PAUSE", glyph: "\uF04C", value: "park", hint: "park this run without finalizing" }] : [],
|
|
1457
|
+
...canResume(run, source) ? [{ id: `run-resume:${encodedRunId}`, label: "RESUME", glyph: "\uF04B", value: "re-engage", hint: "resume a paused run" }] : [],
|
|
1458
|
+
...isFailed && recoveryTaskId ? [{ id: `task-detail:${encodeURIComponent(recoveryTaskId)}`, label: "OPEN TASK", glyph: "\uF08E", value: recoveryTaskId, hint: "open the source task before choosing the next recovery step" }] : [],
|
|
1459
|
+
...isFailed && run.prUrl ? [{ id: `view-pr:${encodeURIComponent(run.prUrl)}`, label: "VIEW PR", glyph: "\uF09B", value: "url", hint: "show the pull request URL for recovery/review" }] : [],
|
|
1460
|
+
...isFailed && recoveryTaskId ? [{ id: `task-detail:dispatch:${encodeURIComponent(recoveryTaskId)}`, label: "REDISPATCH", glyph: "\uF0E7", value: "fresh run", hint: "launch a clean run for the same task" }] : []
|
|
1461
|
+
]
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function cockpitNavValue(screen, server, runs, state) {
|
|
1465
|
+
switch (screen) {
|
|
1466
|
+
case "server":
|
|
1467
|
+
return serverLabel(server);
|
|
1468
|
+
case "tasks":
|
|
1469
|
+
return server?.taskSource ?? "configured source";
|
|
1470
|
+
case "runs":
|
|
1471
|
+
return String(runs.length);
|
|
1472
|
+
default:
|
|
1473
|
+
return "";
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function cockpitNavRows(server, runs, state, catalog) {
|
|
1477
|
+
return cockpitNavScreens(catalog).map(({ screen, nav }) => ({
|
|
1478
|
+
id: `to:${screen}`,
|
|
1479
|
+
label: nav.label,
|
|
1480
|
+
currentValue: cockpitNavValue(screen, server, runs, state),
|
|
1481
|
+
values: ["\u203A"],
|
|
1482
|
+
description: nav.description
|
|
1483
|
+
}));
|
|
1484
|
+
}
|
|
1485
|
+
function itemsFor(screen, runs, actionRowsEnabled, _selectedRunId, state = {}, catalog = DEFAULT_SCREEN_CATALOG, projection = defaultCockpitCapabilities.taskProjection) {
|
|
1486
|
+
const server = state.server ?? null;
|
|
1487
|
+
const projectRoot = state.projectRoot ?? rigProjectRoot();
|
|
1488
|
+
const loadError = state.error?.trim();
|
|
1489
|
+
if (screen === "task-detail" || screen === "run-detail")
|
|
1490
|
+
return [];
|
|
1491
|
+
if (loadError) {
|
|
1492
|
+
return [
|
|
1493
|
+
{ id: "title", label: screenTitle(screen, catalog), currentValue: "load failed", heading: true },
|
|
1494
|
+
{ id: `${screen}:error`, label: "LOAD FAILED", currentValue: "error", heading: true, description: loadError }
|
|
1495
|
+
];
|
|
1496
|
+
}
|
|
1497
|
+
switch (screen) {
|
|
1498
|
+
case "cockpit":
|
|
1499
|
+
return [
|
|
1500
|
+
{ id: "title", label: screenTitle("cockpit", catalog), currentValue: "", heading: true },
|
|
1501
|
+
{ id: "next", label: "NEXT", currentValue: nextStepHint(server, runs, state), heading: true, description: "guided path: Server \u2192 Tasks \u2192 Detail \u2192 Dispatch \u2192 Run Detail" },
|
|
1502
|
+
...cockpitNavRows(server, runs, state, catalog)
|
|
1503
|
+
];
|
|
1504
|
+
default: {
|
|
1505
|
+
const producedRows = state.panelRows ?? [];
|
|
1506
|
+
if (producedRows.length > 0)
|
|
1507
|
+
return [...producedRows];
|
|
1508
|
+
return [
|
|
1509
|
+
{ id: "title", label: screenTitle(screen, catalog), currentValue: "unavailable", heading: true },
|
|
1510
|
+
{ id: `${screen}:unavailable`, label: "UNAVAILABLE", currentValue: "reload", values: ["reload"], heading: true, description: "This screen's content could not be loaded right now \u2014 press r to reload." }
|
|
1511
|
+
];
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
class RigFlowComponent {
|
|
1517
|
+
tui;
|
|
1518
|
+
api;
|
|
1519
|
+
done;
|
|
1520
|
+
onQuit;
|
|
1521
|
+
refreshRuns;
|
|
1522
|
+
refreshState;
|
|
1523
|
+
actions;
|
|
1524
|
+
actionRowsEnabled;
|
|
1525
|
+
catalog;
|
|
1526
|
+
caps;
|
|
1527
|
+
#view;
|
|
1528
|
+
#runs;
|
|
1529
|
+
#state;
|
|
1530
|
+
#error = "";
|
|
1531
|
+
#domainViews = new Map;
|
|
1532
|
+
#helpView;
|
|
1533
|
+
#genericView = new BoardSettingsList;
|
|
1534
|
+
#detailComposer = new DetailComposer;
|
|
1535
|
+
#serverLabel = "resolving target\u2026";
|
|
1536
|
+
#notice = null;
|
|
1537
|
+
#inputLine = null;
|
|
1538
|
+
#tick = 0;
|
|
1539
|
+
#runById = new Map;
|
|
1540
|
+
#screenStack = [];
|
|
1541
|
+
#selectedRunId = null;
|
|
1542
|
+
#selectedTaskId = null;
|
|
1543
|
+
#discoveryFiber;
|
|
1544
|
+
#refreshing = false;
|
|
1545
|
+
#busyDepth = 0;
|
|
1546
|
+
#animationTimer;
|
|
1547
|
+
constructor(tui, api, done, onQuit, screen, runs, state, refreshRuns, refreshState, actions, actionRowsEnabled, catalog = DEFAULT_SCREEN_CATALOG, caps = defaultCockpitCapabilities, helpCatalog) {
|
|
1548
|
+
this.tui = tui;
|
|
1549
|
+
this.api = api;
|
|
1550
|
+
this.done = done;
|
|
1551
|
+
this.onQuit = onQuit;
|
|
1552
|
+
this.refreshRuns = refreshRuns;
|
|
1553
|
+
this.refreshState = refreshState;
|
|
1554
|
+
this.actions = actions;
|
|
1555
|
+
this.actionRowsEnabled = actionRowsEnabled;
|
|
1556
|
+
this.catalog = catalog;
|
|
1557
|
+
this.caps = caps;
|
|
1558
|
+
this.#helpView = new HelpView(helpCatalog);
|
|
1559
|
+
this.#runs = runs;
|
|
1560
|
+
this.#state = state;
|
|
1561
|
+
this.#view = screen;
|
|
1562
|
+
this.#feedViews();
|
|
1563
|
+
this.#startDiscoveryStream();
|
|
1564
|
+
}
|
|
1565
|
+
#startDiscoveryStream() {
|
|
1566
|
+
const root = this.#state.projectRoot ?? rigProjectRoot();
|
|
1567
|
+
const onChange = () => this.#refreshFromEvent();
|
|
1568
|
+
(async () => {
|
|
1569
|
+
try {
|
|
1570
|
+
const discovery = await loadCapabilityForRoot(root, defineCapability(RUN_DISCOVERY));
|
|
1571
|
+
if (!discovery)
|
|
1572
|
+
return;
|
|
1573
|
+
this.#discoveryFiber = Effect.runFork(discovery.runDiscoveryStream(root).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.promise(onChange))));
|
|
1574
|
+
} catch {}
|
|
1575
|
+
})();
|
|
1576
|
+
}
|
|
1577
|
+
#beginBusy() {
|
|
1578
|
+
this.#busyDepth += 1;
|
|
1579
|
+
this.#tick += 1;
|
|
1580
|
+
this.#feedViews();
|
|
1581
|
+
this.tui.requestRender();
|
|
1582
|
+
if (!this.#animationTimer) {
|
|
1583
|
+
this.#animationTimer = setInterval(() => {
|
|
1584
|
+
if (this.#busyDepth <= 0)
|
|
1585
|
+
return;
|
|
1586
|
+
this.#tick += 1;
|
|
1587
|
+
this.#feedViews();
|
|
1588
|
+
this.tui.requestRender();
|
|
1589
|
+
}, 100);
|
|
1590
|
+
this.#animationTimer.unref?.();
|
|
1591
|
+
}
|
|
1592
|
+
let ended = false;
|
|
1593
|
+
return () => {
|
|
1594
|
+
if (ended)
|
|
1595
|
+
return;
|
|
1596
|
+
ended = true;
|
|
1597
|
+
this.#busyDepth = Math.max(0, this.#busyDepth - 1);
|
|
1598
|
+
if (this.#busyDepth === 0 && this.#animationTimer) {
|
|
1599
|
+
clearInterval(this.#animationTimer);
|
|
1600
|
+
this.#animationTimer = undefined;
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
async#refreshFromEvent() {
|
|
1605
|
+
if (this.#refreshing)
|
|
1606
|
+
return;
|
|
1607
|
+
this.#refreshing = true;
|
|
1608
|
+
const endBusy = this.#beginBusy();
|
|
1609
|
+
try {
|
|
1610
|
+
await this.#refreshCurrent();
|
|
1611
|
+
this.tui.requestRender();
|
|
1612
|
+
} catch {} finally {
|
|
1613
|
+
endBusy();
|
|
1614
|
+
this.#refreshing = false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
registerDomainView(screenId, view) {
|
|
1618
|
+
this.#domainViews.set(screenId, view);
|
|
1619
|
+
}
|
|
1620
|
+
viewHost() {
|
|
1621
|
+
return {
|
|
1622
|
+
requestRender: () => this.tui.requestRender(),
|
|
1623
|
+
act: (actionId) => this.#actOnItem({ id: actionId, label: "", values: ["go"] }),
|
|
1624
|
+
reload: () => this.#runAction(async () => {}),
|
|
1625
|
+
attach: (runId) => this.#attach(runId),
|
|
1626
|
+
openSteer: (runId) => {
|
|
1627
|
+
this.#inputLine = { kind: "steer", buffer: "", run: { runId } };
|
|
1628
|
+
this.tui.requestRender();
|
|
1629
|
+
},
|
|
1630
|
+
openSearch: (current, apply) => {
|
|
1631
|
+
this.#inputLine = { kind: "search", buffer: current, apply };
|
|
1632
|
+
this.tui.requestRender();
|
|
1633
|
+
},
|
|
1634
|
+
navigate: (screenId) => this.#navigate(screenId),
|
|
1635
|
+
back: () => this.#back(),
|
|
1636
|
+
toCockpit: () => this.#navigate("cockpit", false),
|
|
1637
|
+
showHelp: () => this.#setView("help"),
|
|
1638
|
+
setNotice: (text) => {
|
|
1639
|
+
this.#notice = text;
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
#feedViews() {
|
|
1644
|
+
if (this.#state.server)
|
|
1645
|
+
this.#serverLabel = serverLabel(this.#state.server);
|
|
1646
|
+
this.#runById.clear();
|
|
1647
|
+
for (const entry of this.#runs)
|
|
1648
|
+
this.#runById.set(entry.runId, entry);
|
|
1649
|
+
const board = this.#state.board;
|
|
1650
|
+
if (board)
|
|
1651
|
+
this.#domainViews.get(board.kind)?.setBoard(board);
|
|
1652
|
+
if (this.#state.error)
|
|
1653
|
+
this.#domainViews.get(this.#view)?.setError(this.#state.error);
|
|
1654
|
+
if (this.#activeView() === this.#genericView) {
|
|
1655
|
+
const genericScreen = this.#view === "help" ? "cockpit" : this.#view;
|
|
1656
|
+
this.#genericView.setTitle(screenLabel(genericScreen, this.catalog));
|
|
1657
|
+
this.#genericView.setItems(itemsFor(genericScreen, this.#runs, this.actionRowsEnabled, this.#selectedRunId, this.#state, this.catalog, this.caps.taskProjection));
|
|
1658
|
+
}
|
|
1659
|
+
if (this.#view === "task-detail") {
|
|
1660
|
+
this.#detailComposer.setModel(this.#state.error ? { screen: "task-detail", icon: screenGlyph2("task-detail"), ref: "TASK", title: "Task failed to load", body: this.#state.error, bodyLabel: "error", facts: [{ label: "hint", value: "Refresh or return to Tasks." }], actions: [] } : taskDetailModel(this.#state, this.#state.server ?? null, this.caps.taskProjection));
|
|
1661
|
+
} else if (this.#view === "run-detail") {
|
|
1662
|
+
const run = findRunById(this.#runs, this.#selectedRunId);
|
|
1663
|
+
this.#detailComposer.setModel(this.#state.error ? { screen: "run-detail", icon: screenGlyph2("run-detail"), ref: "RUN", title: "Run failed to load", body: this.#state.error, bodyLabel: "error", facts: [{ label: "hint", value: "Refresh or return to Runs." }], actions: [] } : run ? runDetailModel(run, this.#state) : { screen: "run-detail", icon: screenGlyph2("run-detail"), ref: "RUN", title: "Run unavailable", facts: [{ label: "hint", value: "The selected run is no longer discoverable; back to Runs." }], actions: [] });
|
|
1664
|
+
}
|
|
1665
|
+
for (const view of this.#domainViews.values())
|
|
1666
|
+
view.setTick(this.#tick);
|
|
1667
|
+
this.#detailComposer.tick = this.#tick;
|
|
1668
|
+
}
|
|
1669
|
+
#activeView() {
|
|
1670
|
+
const domainView = this.#domainViews.get(this.#view);
|
|
1671
|
+
if (domainView)
|
|
1672
|
+
return domainView;
|
|
1673
|
+
if (this.#view === "task-detail")
|
|
1674
|
+
return this.#detailComposer;
|
|
1675
|
+
if (this.#view === "run-detail")
|
|
1676
|
+
return this.#detailComposer;
|
|
1677
|
+
return this.#genericView;
|
|
1678
|
+
}
|
|
1679
|
+
#setView(next) {
|
|
1680
|
+
this.#view = next;
|
|
1681
|
+
this.#notice = null;
|
|
1682
|
+
this.#feedViews();
|
|
1683
|
+
this.tui.requestRender();
|
|
1684
|
+
}
|
|
1685
|
+
async#navigate(next, push = true) {
|
|
1686
|
+
if (push && this.#view !== "help")
|
|
1687
|
+
this.#screenStack.push(this.#view);
|
|
1688
|
+
this.#view = next;
|
|
1689
|
+
this.#notice = null;
|
|
1690
|
+
const endBusy = this.#beginBusy();
|
|
1691
|
+
try {
|
|
1692
|
+
await this.#refreshCurrent();
|
|
1693
|
+
} finally {
|
|
1694
|
+
endBusy();
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
async#back() {
|
|
1698
|
+
const previous = this.#screenStack.pop() ?? "cockpit";
|
|
1699
|
+
await this.#navigate(previous, false);
|
|
1700
|
+
}
|
|
1701
|
+
async#refreshCurrent() {
|
|
1702
|
+
let runLoadError = null;
|
|
1703
|
+
try {
|
|
1704
|
+
this.#runs = await this.refreshRuns();
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
runLoadError = notificationMessage(error);
|
|
1707
|
+
this.#runs = [];
|
|
1708
|
+
}
|
|
1709
|
+
if (this.#view !== "help") {
|
|
1710
|
+
const loadedState = await this.refreshState(this.#view, this.#selectedTaskId, this.#runs);
|
|
1711
|
+
this.#state = runLoadError ? { ...loadedState, error: loadedState.error ? `${runLoadError}; ${loadedState.error}` : runLoadError } : loadedState;
|
|
1712
|
+
if (this.#state.error)
|
|
1713
|
+
this.#error = this.#state.error;
|
|
1714
|
+
} else if (runLoadError) {
|
|
1715
|
+
this.#error = runLoadError;
|
|
1716
|
+
}
|
|
1717
|
+
this.#feedViews();
|
|
1718
|
+
this.tui.requestRender();
|
|
1719
|
+
}
|
|
1720
|
+
async#runAction(action) {
|
|
1721
|
+
const endBusy = this.#beginBusy();
|
|
1722
|
+
const onProgress = (message) => {
|
|
1723
|
+
this.#notice = message;
|
|
1724
|
+
this.#feedViews();
|
|
1725
|
+
this.tui.requestRender();
|
|
1726
|
+
};
|
|
1727
|
+
try {
|
|
1728
|
+
this.#error = "";
|
|
1729
|
+
await action(onProgress);
|
|
1730
|
+
await this.#refreshCurrent();
|
|
1731
|
+
this.tui.requestRender();
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
this.#error = this.caps.dispatchOutcome.humanize(error) ?? notificationMessage(error);
|
|
1734
|
+
this.tui.requestRender();
|
|
1735
|
+
} finally {
|
|
1736
|
+
endBusy();
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
#headerLine() {
|
|
1740
|
+
const crumb = this.#view === "help" ? ` ${ink42("\u203A")} ${ink22("help")}` : ` ${ink42("\u203A")} ${ink22(screenLabel(this.#view, this.catalog))}`;
|
|
1741
|
+
return ` ${accent2("\u258D")}${bold2(ink5("rig"))} ${ink32("\u2014 operate the drones")}${crumb} ${ink42("\xB7")} ${cyan2(this.#serverLabel)}`;
|
|
1742
|
+
}
|
|
1743
|
+
#noticeText() {
|
|
1744
|
+
if (!this.#notice)
|
|
1745
|
+
return null;
|
|
1746
|
+
const prefix = this.#busyDepth > 0 ? `${RIG_SPINNER_FRAMES2[this.#tick % RIG_SPINNER_FRAMES2.length]} ` : "";
|
|
1747
|
+
return `${prefix}${this.#notice}`;
|
|
1748
|
+
}
|
|
1749
|
+
#footerLines() {
|
|
1750
|
+
if (this.#inputLine) {
|
|
1751
|
+
const line = this.#inputLine;
|
|
1752
|
+
const label = line.kind === "search" ? `${accent2("/")}${ink5(line.buffer)}` : `${accentDim2("steer")} ${ink32(`(${line.run.runId.slice(0, 8)})`)} ${accent2("\u276F")} ${ink5(line.buffer)}`;
|
|
1753
|
+
return [
|
|
1754
|
+
` ${label}${accent2("\u2588")}`,
|
|
1755
|
+
` ${accent2("enter")}${ink32(line.kind === "search" ? " keep filter" : " send steer")} ${accent2("esc")}${ink32(line.kind === "search" ? " clear" : " cancel")}`
|
|
1756
|
+
];
|
|
1757
|
+
}
|
|
1758
|
+
if (this.#error)
|
|
1759
|
+
return [` ${red2(`error: ${this.#error}`)}`, ` ${accent2("esc")}${ink32(" cockpit")} ${accent2("q")}${ink32(" quit")}`];
|
|
1760
|
+
if (this.#view === "help")
|
|
1761
|
+
return [` ${accent2("\u2190")}${ink32(" back")} ${accent2("esc")}${ink32(" cockpit")} ${accent2("q")}${ink32(" quit")}`];
|
|
1762
|
+
const domainFooter = this.#domainViews.get(this.#view)?.footer;
|
|
1763
|
+
if (domainFooter)
|
|
1764
|
+
return domainFooter(this.#noticeText());
|
|
1765
|
+
if (this.#view === "task-detail" || this.#view === "run-detail") {
|
|
1766
|
+
const isTask = this.#view === "task-detail";
|
|
1767
|
+
const notice2 = this.#noticeText();
|
|
1768
|
+
const hint = notice2 ? accentDim2(notice2) : ink42(isTask ? "review the task, then dispatch" : "live run controls");
|
|
1769
|
+
return [
|
|
1770
|
+
` ${hint}`,
|
|
1771
|
+
` ${accent2("\u2191\u2193")}${ink32(" action")} ${accent2("enter")}${ink32(isTask ? " dispatch" : " run")} ${accent2("\u21DE\u21DF")}${ink32(" scroll")} ${accent2("\u2190")}${ink32(" back")} ${accent2("esc")}${ink32(" cockpit")} ${accent2("q")}${ink32(" quit")}`
|
|
1772
|
+
];
|
|
1773
|
+
}
|
|
1774
|
+
const notice = this.#noticeText();
|
|
1775
|
+
return [
|
|
1776
|
+
` ${notice ? accentDim2(notice) : ink42(`screen: ${screenLabel(this.#view, this.catalog)}`)}`,
|
|
1777
|
+
` ${accent2("\u2191\u2193")}${ink32(" move")} ${accent2("enter")}${ink32(" act/open")} ${accent2("\u2190")}${ink32(" back")} ${accent2("esc")}${ink32(" cockpit")} ${accent2("q")}${ink32(" quit")}`
|
|
1778
|
+
];
|
|
1779
|
+
}
|
|
1780
|
+
async#attach(runId) {
|
|
1781
|
+
const record = this.#runById.get(runId);
|
|
1782
|
+
if (!record || !canAttachRun(record, this.#state))
|
|
1783
|
+
return;
|
|
1784
|
+
await this.#runAction(() => this.actions.act(`run:${encodeURIComponent(runId)}`));
|
|
1785
|
+
if (!this.#error)
|
|
1786
|
+
this.done();
|
|
1787
|
+
}
|
|
1788
|
+
async#stop(run) {
|
|
1789
|
+
this.#notice = `stop requested for ${run.runId.slice(0, 8)}\u2026`;
|
|
1790
|
+
this.tui.requestRender();
|
|
1791
|
+
await this.#runAction(() => this.actions.act(`run-stop:${encodeURIComponent(run.runId)}`));
|
|
1792
|
+
}
|
|
1793
|
+
async#pause(run) {
|
|
1794
|
+
this.#notice = `pause requested for ${run.runId.slice(0, 8)}\u2026`;
|
|
1795
|
+
this.tui.requestRender();
|
|
1796
|
+
await this.#runAction(() => this.actions.act(`run-pause:${encodeURIComponent(run.runId)}`));
|
|
1797
|
+
}
|
|
1798
|
+
async#resume(run) {
|
|
1799
|
+
this.#notice = `resume requested for ${run.runId.slice(0, 8)}\u2026`;
|
|
1800
|
+
this.tui.requestRender();
|
|
1801
|
+
await this.#runAction(() => this.actions.act(`run-resume:${encodeURIComponent(run.runId)}`));
|
|
1802
|
+
}
|
|
1803
|
+
async#steer(run, message) {
|
|
1804
|
+
await this.#runAction(() => this.actions.act(`run-steer:${encodeURIComponent(run.runId)}`, message));
|
|
1805
|
+
}
|
|
1806
|
+
async#actOnItem(item) {
|
|
1807
|
+
if (item.id.startsWith("to:")) {
|
|
1808
|
+
const target = item.id.slice("to:".length);
|
|
1809
|
+
if (isRigScreen(target, this.catalog))
|
|
1810
|
+
await this.#navigate(target);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
if (item.id.startsWith("task-detail:") && !item.id.startsWith("task-detail:dispatch:")) {
|
|
1814
|
+
this.#selectedTaskId = decodeActionValue(item.id.slice("task-detail:".length));
|
|
1815
|
+
await this.#navigate("task-detail");
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
if (item.id.startsWith("run-detail:")) {
|
|
1819
|
+
this.#selectedRunId = decodeActionValue(item.id.slice("run-detail:".length));
|
|
1820
|
+
await this.#navigate("run-detail");
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
if (item.id === "detail:back") {
|
|
1824
|
+
await this.#back();
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (item.id === "detail:join") {
|
|
1828
|
+
const run = findRunById(this.#runs, this.#selectedRunId);
|
|
1829
|
+
if (run) {
|
|
1830
|
+
await this.#runAction(() => this.actions.act(`run:${encodeURIComponent(run.runId)}`));
|
|
1831
|
+
if (!this.#error)
|
|
1832
|
+
this.done();
|
|
1833
|
+
}
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (item.id === "config:relayUrl" || item.id.startsWith("stats:") || item.id.startsWith("inspect:audit")) {
|
|
1837
|
+
this.#notice = "informational row; no cockpit action";
|
|
1838
|
+
this.tui.requestRender();
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (item.id.startsWith("task-detail:dispatch:")) {
|
|
1842
|
+
await this.#runAction((onProgress) => this.actions.act(item.id, undefined, onProgress));
|
|
1843
|
+
if (!this.#error)
|
|
1844
|
+
this.done();
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (item.values && item.values.length > 0)
|
|
1848
|
+
await this.#runAction((onProgress) => this.actions.act(item.id, undefined, onProgress));
|
|
1849
|
+
}
|
|
1850
|
+
render(width) {
|
|
1851
|
+
const lines = [this.#headerLine(), hairline(width)];
|
|
1852
|
+
const view = this.#view === "help" ? this.#helpView : this.#activeView();
|
|
1853
|
+
for (const line of view.render(width))
|
|
1854
|
+
lines.push(line);
|
|
1855
|
+
lines.push(hairline(width));
|
|
1856
|
+
for (const line of this.#footerLines())
|
|
1857
|
+
lines.push(line);
|
|
1858
|
+
return lines;
|
|
1859
|
+
}
|
|
1860
|
+
invalidate() {
|
|
1861
|
+
for (const view of this.#domainViews.values())
|
|
1862
|
+
view.invalidate();
|
|
1863
|
+
this.#helpView.invalidate();
|
|
1864
|
+
this.#genericView.invalidate();
|
|
1865
|
+
}
|
|
1866
|
+
handleInput(data) {
|
|
1867
|
+
if (data === "q") {
|
|
1868
|
+
this.onQuit();
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
if (isKeyRelease(data))
|
|
1872
|
+
return;
|
|
1873
|
+
if (this.#inputLine) {
|
|
1874
|
+
const line = this.#inputLine;
|
|
1875
|
+
if (matchesKey(data, "escape")) {
|
|
1876
|
+
if (line.kind === "search")
|
|
1877
|
+
line.apply("");
|
|
1878
|
+
this.#inputLine = null;
|
|
1879
|
+
} else if (matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
1880
|
+
this.#inputLine = null;
|
|
1881
|
+
if (line.kind === "steer" && line.buffer.trim())
|
|
1882
|
+
this.#steer(line.run, line.buffer.trim());
|
|
1883
|
+
} else if (matchesKey(data, "backspace")) {
|
|
1884
|
+
const buffer = line.buffer.slice(0, -1);
|
|
1885
|
+
this.#inputLine = line.kind === "search" ? { kind: "search", buffer, apply: line.apply } : { kind: "steer", buffer, run: line.run };
|
|
1886
|
+
if (line.kind === "search")
|
|
1887
|
+
line.apply(buffer);
|
|
1888
|
+
} else if (data.length === 1 && data >= " ") {
|
|
1889
|
+
const buffer = line.buffer + data;
|
|
1890
|
+
this.#inputLine = line.kind === "search" ? { kind: "search", buffer, apply: line.apply } : { kind: "steer", buffer, run: line.run };
|
|
1891
|
+
if (line.kind === "search")
|
|
1892
|
+
line.apply(buffer);
|
|
1893
|
+
}
|
|
1894
|
+
this.tui.requestRender();
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
1898
|
+
this.done();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (this.#view === "help") {
|
|
1902
|
+
if (matchesKey(data, "up") || data === "k")
|
|
1903
|
+
this.#helpView.scroll(-1);
|
|
1904
|
+
else if (matchesKey(data, "down") || data === "j")
|
|
1905
|
+
this.#helpView.scroll(1);
|
|
1906
|
+
else if (matchesKey(data, "pageUp"))
|
|
1907
|
+
this.#helpView.scroll(-15);
|
|
1908
|
+
else if (matchesKey(data, "pageDown") || data === " ")
|
|
1909
|
+
this.#helpView.scroll(15);
|
|
1910
|
+
else if (matchesKey(data, "left") || data === "?")
|
|
1911
|
+
this.#back();
|
|
1912
|
+
else if (matchesKey(data, "escape"))
|
|
1913
|
+
this.#navigate("cockpit", false);
|
|
1914
|
+
this.tui.requestRender();
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
if (this.#view === "task-detail" || this.#view === "run-detail") {
|
|
1918
|
+
if (matchesKey(data, "up") || data === "k") {
|
|
1919
|
+
this.#detailComposer.moveSelection(-1);
|
|
1920
|
+
this.tui.requestRender();
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
if (matchesKey(data, "down") || data === "j") {
|
|
1924
|
+
this.#detailComposer.moveSelection(1);
|
|
1925
|
+
this.tui.requestRender();
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
1929
|
+
const action = this.#detailComposer.selectedAction();
|
|
1930
|
+
if (action) {
|
|
1931
|
+
if (action.id.startsWith("run-steer:")) {
|
|
1932
|
+
const run = findRunById(this.#runs, this.#selectedRunId);
|
|
1933
|
+
if (run) {
|
|
1934
|
+
this.#inputLine = { kind: "steer", buffer: "", run: { runId: run.runId } };
|
|
1935
|
+
this.tui.requestRender();
|
|
1936
|
+
}
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
if (action.id.startsWith("task-detail:dispatch:")) {
|
|
1940
|
+
this.#notice = "dispatch \xB7 starting\u2026";
|
|
1941
|
+
this.tui.requestRender();
|
|
1942
|
+
}
|
|
1943
|
+
if (action.id.startsWith("run-stop:")) {
|
|
1944
|
+
this.#notice = "stop requested\u2026";
|
|
1945
|
+
this.tui.requestRender();
|
|
1946
|
+
}
|
|
1947
|
+
this.#actOnItem({ id: action.id, label: action.label, values: [action.value ?? "go"] });
|
|
1948
|
+
}
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
if (matchesKey(data, "pageUp")) {
|
|
1952
|
+
this.#detailComposer.scrollBody(-5);
|
|
1953
|
+
this.tui.requestRender();
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
if (matchesKey(data, "pageDown") || data === " ") {
|
|
1957
|
+
this.#detailComposer.scrollBody(5);
|
|
1958
|
+
this.tui.requestRender();
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (data === "?" || data === "h") {
|
|
1962
|
+
this.#setView("help");
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
if (matchesKey(data, "left")) {
|
|
1966
|
+
this.#back();
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (matchesKey(data, "escape")) {
|
|
1970
|
+
this.#navigate("cockpit", false);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
const domainView = this.#domainViews.get(this.#view);
|
|
1976
|
+
if (domainView) {
|
|
1977
|
+
if (domainView.handleInput(data))
|
|
1978
|
+
return;
|
|
1979
|
+
if (data === "?" || data === "h") {
|
|
1980
|
+
this.#setView("help");
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
if (data === "r") {
|
|
1984
|
+
this.#runAction(async () => {});
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
if (matchesKey(data, "left")) {
|
|
1988
|
+
this.#back();
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
if (matchesKey(data, "escape")) {
|
|
1992
|
+
this.#navigate("cockpit", false);
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
} else {
|
|
1996
|
+
if (matchesKey(data, "up") || data === "k") {
|
|
1997
|
+
this.#genericView.moveSelection(-1);
|
|
1998
|
+
this.tui.requestRender();
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
if (matchesKey(data, "down") || data === "j") {
|
|
2002
|
+
this.#genericView.moveSelection(1);
|
|
2003
|
+
this.tui.requestRender();
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
2007
|
+
const item = this.#genericView.selectedItem();
|
|
2008
|
+
if (item)
|
|
2009
|
+
this.#actOnItem(item);
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
if (data === "?" || data === "h") {
|
|
2013
|
+
this.#setView("help");
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
if (data === "r") {
|
|
2017
|
+
this.#runAction(async () => {});
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
if (matchesKey(data, "left")) {
|
|
2021
|
+
this.#back();
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (matchesKey(data, "escape")) {
|
|
2025
|
+
this.#navigate("cockpit", false);
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
dispose() {
|
|
2031
|
+
const fiber = this.#discoveryFiber;
|
|
2032
|
+
this.#discoveryFiber = undefined;
|
|
2033
|
+
if (fiber)
|
|
2034
|
+
Effect.runFork(Fiber.interrupt(fiber));
|
|
2035
|
+
if (this.#animationTimer) {
|
|
2036
|
+
clearInterval(this.#animationTimer);
|
|
2037
|
+
this.#animationTimer = undefined;
|
|
2038
|
+
}
|
|
2039
|
+
this.#busyDepth = 0;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
class FullscreenSceneProxy {
|
|
2044
|
+
current;
|
|
2045
|
+
onDispose;
|
|
2046
|
+
constructor(current, onDispose) {
|
|
2047
|
+
this.current = current;
|
|
2048
|
+
this.onDispose = onDispose;
|
|
2049
|
+
}
|
|
2050
|
+
render() {
|
|
2051
|
+
return EMPTY_SCENE_LINES;
|
|
2052
|
+
}
|
|
2053
|
+
handleInput(data) {
|
|
2054
|
+
this.current().handleInput?.(data);
|
|
2055
|
+
}
|
|
2056
|
+
invalidate() {
|
|
2057
|
+
this.current().invalidate?.();
|
|
2058
|
+
}
|
|
2059
|
+
dispose() {
|
|
2060
|
+
this.onDispose();
|
|
2061
|
+
this.current().dispose?.();
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
async function openRigFlow(api, ctx, screen, actions = defaultFlowActions(api, ctx), actionRowsEnabled = true, quitExits = false) {
|
|
2065
|
+
const refreshRuns = async () => discoverRuns(ctx);
|
|
2066
|
+
const refreshState = (nextScreen, selectedTaskId, runs) => loadRigFlowState(ctx, nextScreen, selectedTaskId, api, runs, actionRowsEnabled);
|
|
2067
|
+
await ctx.ui.custom((tui, _theme, _keybindings, done) => {
|
|
2068
|
+
let handle;
|
|
2069
|
+
let finished = false;
|
|
2070
|
+
const hideScene = () => {
|
|
2071
|
+
handle?.hide();
|
|
2072
|
+
handle = undefined;
|
|
2073
|
+
};
|
|
2074
|
+
const finish = () => {
|
|
2075
|
+
finished = true;
|
|
2076
|
+
hideScene();
|
|
2077
|
+
done(undefined);
|
|
2078
|
+
};
|
|
2079
|
+
const onQuit = quitExits ? () => {
|
|
2080
|
+
finished = true;
|
|
2081
|
+
hideScene();
|
|
2082
|
+
postmortem.quit(0);
|
|
2083
|
+
} : finish;
|
|
2084
|
+
const preloader = new DronePreloaderComponent(tui);
|
|
2085
|
+
let active = preloader;
|
|
2086
|
+
handle = tui.showOverlay(preloader, { width: "100%", maxHeight: "100%", anchor: "top-left", margin: 0, fullscreen: true });
|
|
2087
|
+
tui.setFocus(preloader);
|
|
2088
|
+
(async () => {
|
|
2089
|
+
try {
|
|
2090
|
+
let runLoadError = null;
|
|
2091
|
+
let runs = [];
|
|
2092
|
+
try {
|
|
2093
|
+
runs = await refreshRuns();
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
runLoadError = notificationMessage(error);
|
|
2096
|
+
}
|
|
2097
|
+
const loadedState = await refreshState(screen, null, runs);
|
|
2098
|
+
const state = runLoadError ? { ...loadedState, error: loadedState.error ? `${runLoadError}; ${loadedState.error}` : runLoadError } : loadedState;
|
|
2099
|
+
await appendWorkflowStarted(api, ctx, state);
|
|
2100
|
+
const { catalog, caps } = await resolveCockpitRegistry(ctx);
|
|
2101
|
+
const helpRoot = state.projectRoot ?? rigProjectRoot();
|
|
2102
|
+
const helpCatalogService = await loadCapabilityForRoot(helpRoot, defineCapability(HELP_CATALOG));
|
|
2103
|
+
const helpHost = await resolvePluginHost(helpRoot).catch(() => null);
|
|
2104
|
+
const helpCommands = helpHost?.host.listExecutableCliCommands() ?? [];
|
|
2105
|
+
const helpCatalog = helpCatalogService ? helpCatalogService.helpCatalog(helpCommands) : { sections: [], groups: [] };
|
|
2106
|
+
if (finished)
|
|
2107
|
+
return;
|
|
2108
|
+
const component = new RigFlowComponent(tui, api, finish, onQuit, screen, runs, state, refreshRuns, refreshState, actions, actionRowsEnabled, catalog, caps, helpCatalog);
|
|
2109
|
+
try {
|
|
2110
|
+
const panelHost = await resolvePluginHost(helpRoot).catch(() => null);
|
|
2111
|
+
for (const panel of panelHost?.host.listExecutablePanels() ?? []) {
|
|
2112
|
+
if (typeof panel.createView !== "function" || !panel.screen)
|
|
2113
|
+
continue;
|
|
2114
|
+
const view = await panel.createView(component.viewHost());
|
|
2115
|
+
component.registerDomainView(panel.id, view);
|
|
2116
|
+
}
|
|
2117
|
+
} catch {}
|
|
2118
|
+
hideScene();
|
|
2119
|
+
preloader.dispose();
|
|
2120
|
+
active = component;
|
|
2121
|
+
handle = tui.showOverlay(component, { width: "100%", maxHeight: "100%", anchor: "top-left", margin: 0, fullscreen: true });
|
|
2122
|
+
tui.setFocus(component);
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
if (finished)
|
|
2125
|
+
return;
|
|
2126
|
+
hideScene();
|
|
2127
|
+
preloader.dispose();
|
|
2128
|
+
ctx.ui.notify(notificationMessage(error), "error");
|
|
2129
|
+
done(undefined);
|
|
2130
|
+
}
|
|
2131
|
+
})();
|
|
2132
|
+
return new FullscreenSceneProxy(() => active, hideScene);
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
function launchRigFlow(api, ctx, screen, actions = defaultFlowActions(api, ctx), actionRowsEnabled = true, quitExits = false) {
|
|
2136
|
+
openRigFlow(api, ctx, screen, actions, actionRowsEnabled, quitExits).catch((error) => {
|
|
2137
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
async function startCockpitOrSetup(api, ctx) {
|
|
2141
|
+
const actions = defaultFlowActions(api, ctx);
|
|
2142
|
+
const status = await detectRigStartupStatus(ctx);
|
|
2143
|
+
if (status.configured) {
|
|
2144
|
+
launchRigFlow(api, ctx, "cockpit", actions, true, true);
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
const autoSetupCompleted = await runRigAutomaticSetup(api, ctx, status);
|
|
2148
|
+
if (autoSetupCompleted) {
|
|
2149
|
+
launchRigFlow(api, ctx, "cockpit", actions, true, true);
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
launchRigFlow(api, ctx, "cockpit", actions, true, true);
|
|
2153
|
+
}
|
|
2154
|
+
function rigExtension(api, panels = []) {
|
|
2155
|
+
api.registerMessageRenderer(RIG_WORKFLOW_STARTED, () => {
|
|
2156
|
+
return;
|
|
2157
|
+
});
|
|
2158
|
+
api.registerMessageRenderer(RIG_WORKFLOW_TARGET_SELECTED, () => {
|
|
2159
|
+
return;
|
|
2160
|
+
});
|
|
2161
|
+
api.registerMessageRenderer(RIG_WORKFLOW_TASK_SELECTED, () => {
|
|
2162
|
+
return;
|
|
2163
|
+
});
|
|
2164
|
+
api.registerMessageRenderer(RIG_WORKFLOW_STATUS_CHANGED, () => {
|
|
2165
|
+
return;
|
|
2166
|
+
});
|
|
2167
|
+
api.registerMessageRenderer(RIG_WORKFLOW_OPERATOR_NOTE, () => {
|
|
2168
|
+
return;
|
|
2169
|
+
});
|
|
2170
|
+
const startupCatalog = resolveScreenCatalog(panels);
|
|
2171
|
+
for (const { screen, shortcut } of screenShortcuts(startupCatalog)) {
|
|
2172
|
+
const target = screen;
|
|
2173
|
+
api.registerShortcut(shortcut.key, {
|
|
2174
|
+
description: shortcut.description,
|
|
2175
|
+
handler: async (shortcutCtx) => {
|
|
2176
|
+
launchRigFlow(api, shortcutCtx, target, defaultFlowActions(api, shortcutCtx), true);
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
api.on("session_start", async (_event, ctx) => {
|
|
2181
|
+
if (!ctx.hasUI)
|
|
2182
|
+
return;
|
|
2183
|
+
ctx.ui.setTitle("RIG");
|
|
2184
|
+
await startCockpitOrSetup(api, ctx);
|
|
2185
|
+
});
|
|
2186
|
+
api.registerCommand("rig", {
|
|
2187
|
+
description: "Open the Rig operator flow",
|
|
2188
|
+
handler: async (args, ctx) => {
|
|
2189
|
+
const requested = args.trim();
|
|
2190
|
+
const actions = commandFlowActions(api, ctx);
|
|
2191
|
+
if (requested.startsWith("action ")) {
|
|
2192
|
+
await actions.act(requested.slice("action ".length).trim());
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (requested.startsWith("run ")) {
|
|
2196
|
+
await actions.act(`run:${requested.slice("run ".length).trim()}`);
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const lower = requested.toLowerCase();
|
|
2200
|
+
let screen = "cockpit";
|
|
2201
|
+
try {
|
|
2202
|
+
const { catalog } = await resolveCockpitRegistry(ctx);
|
|
2203
|
+
if (isRigScreen(lower, catalog))
|
|
2204
|
+
screen = lower;
|
|
2205
|
+
} catch {}
|
|
2206
|
+
launchRigFlow(api, ctx, screen, actions, true);
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
var UNCLASSIFIED_RUN_CLASSIFICATION, RunIdentityEnvCap, ProjectSetupCap, WorkflowJournalWriterCap, commandFlowActions, EMPTY_SCENE_LINES, __rigExtensionTest;
|
|
2211
|
+
var init_extension = __esm(() => {
|
|
2212
|
+
init_board_views();
|
|
2213
|
+
init_drone_preloader();
|
|
2214
|
+
init_screen_catalog();
|
|
2215
|
+
init_cockpit_product();
|
|
2216
|
+
UNCLASSIFIED_RUN_CLASSIFICATION = {
|
|
2217
|
+
status: "unclassified",
|
|
2218
|
+
runStatus: "needs-attention",
|
|
2219
|
+
phase: "unknown",
|
|
2220
|
+
role: "neutral",
|
|
2221
|
+
rank: Number.MAX_SAFE_INTEGER,
|
|
2222
|
+
isActive: false,
|
|
2223
|
+
isTerminal: false,
|
|
2224
|
+
isNeedsAttention: false,
|
|
2225
|
+
offersRecovery: false
|
|
2226
|
+
};
|
|
2227
|
+
RunIdentityEnvCap = defineCapability(RUN_IDENTITY_ENV);
|
|
2228
|
+
ProjectSetupCap = defineCapability(PROJECT_SETUP);
|
|
2229
|
+
WorkflowJournalWriterCap = defineCapability(RIG_WORKFLOW_JOURNAL_WRITER);
|
|
2230
|
+
commandFlowActions = defaultFlowActions;
|
|
2231
|
+
EMPTY_SCENE_LINES = [];
|
|
2232
|
+
__rigExtensionTest = {
|
|
2233
|
+
itemsFor,
|
|
2234
|
+
taskDetailModel,
|
|
2235
|
+
runDetailModel,
|
|
2236
|
+
defaultFlowActions,
|
|
2237
|
+
commandFlowActions,
|
|
2238
|
+
loadRigFlowState,
|
|
2239
|
+
detectRigStartupStatus,
|
|
2240
|
+
inboxActionId,
|
|
2241
|
+
parseInboxActionTarget,
|
|
2242
|
+
runListActionHints
|
|
2243
|
+
};
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
// packages/omp-extension-plugin/src/cockpit/plugin.ts
|
|
2247
|
+
import { definePlugin } from "@rig/core/config";
|
|
2248
|
+
var COCKPIT_HOST_PLUGIN_NAME = "@rig/omp-extension-plugin";
|
|
2249
|
+
function createCockpitHostPlugin() {
|
|
2250
|
+
return definePlugin({
|
|
2251
|
+
name: COCKPIT_HOST_PLUGIN_NAME,
|
|
2252
|
+
version: "0.0.0",
|
|
2253
|
+
effects: {
|
|
2254
|
+
readsFiles: true
|
|
2255
|
+
},
|
|
2256
|
+
contributes: {
|
|
2257
|
+
sessionExtensions: [
|
|
2258
|
+
{
|
|
2259
|
+
id: "rig:cockpit-product",
|
|
2260
|
+
description: "Rig cockpit screen-host extension for OMP sessions.",
|
|
2261
|
+
install: async (api, context) => {
|
|
2262
|
+
const { default: rigExtension2 } = await Promise.resolve().then(() => (init_extension(), exports_extension));
|
|
2263
|
+
return rigExtension2(api, context?.panels);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
]
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
var cockpitHostPlugin = createCockpitHostPlugin();
|
|
2271
|
+
var plugin_default = cockpitHostPlugin;
|
|
2272
|
+
export {
|
|
2273
|
+
plugin_default as default,
|
|
2274
|
+
createCockpitHostPlugin,
|
|
2275
|
+
cockpitHostPlugin,
|
|
2276
|
+
COCKPIT_HOST_PLUGIN_NAME
|
|
2277
|
+
};
|