@njeybe/nativ 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/README.md +15 -0
- package/dist/server/studio-ui/document.d.ts +3 -0
- package/dist/server/studio-ui/document.js +22 -0
- package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
- package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/browser.js +326 -0
- package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/connection.js +158 -0
- package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
- package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/drift.js +301 -0
- package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/events.js +231 -0
- package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/explorer.js +183 -0
- package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/index.js +28 -0
- package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/markup.js +32 -0
- package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/state.js +57 -0
- package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/styles.js +118 -0
- package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/flow/index.js +11 -0
- package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/layout.js +105 -0
- package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
- package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/script.js +61 -0
- package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/styles.js +59 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
- package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/home/index.js +11 -0
- package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/script.js +160 -0
- package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/styles.js +48 -0
- package/dist/server/studio-ui/pages/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/index.js +17 -0
- package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/tasks/index.js +11 -0
- package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/script.js +96 -0
- package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
- package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/team/index.js +11 -0
- package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/script.js +112 -0
- package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/styles.js +22 -0
- package/dist/server/studio-ui/pages/types.d.ts +13 -0
- package/dist/server/studio-ui/pages/types.js +1 -0
- package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
- package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
- package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
- package/dist/server/studio-ui/shell/actions.d.ts +1 -0
- package/dist/server/studio-ui/shell/actions.js +49 -0
- package/dist/server/studio-ui/shell/boot.d.ts +2 -0
- package/dist/server/studio-ui/shell/boot.js +13 -0
- package/dist/server/studio-ui/shell/console.d.ts +1 -0
- package/dist/server/studio-ui/shell/console.js +194 -0
- package/dist/server/studio-ui/shell/core.d.ts +1 -0
- package/dist/server/studio-ui/shell/core.js +50 -0
- package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail-view.js +80 -0
- package/dist/server/studio-ui/shell/detail.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail.js +83 -0
- package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
- package/dist/server/studio-ui/shell/dispatch.js +156 -0
- package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/events-tools.js +94 -0
- package/dist/server/studio-ui/shell/format.d.ts +1 -0
- package/dist/server/studio-ui/shell/format.js +151 -0
- package/dist/server/studio-ui/shell/icons.d.ts +2 -0
- package/dist/server/studio-ui/shell/icons.js +20 -0
- package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-head.js +43 -0
- package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
- package/dist/server/studio-ui/shell/markup-panels.js +7 -0
- package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-tools.js +222 -0
- package/dist/server/studio-ui/shell/model.d.ts +1 -0
- package/dist/server/studio-ui/shell/model.js +192 -0
- package/dist/server/studio-ui/shell/paint.d.ts +1 -0
- package/dist/server/studio-ui/shell/paint.js +154 -0
- package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette-view.js +60 -0
- package/dist/server/studio-ui/shell/palette.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette.js +80 -0
- package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
- package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
- package/dist/server/studio-ui/shell/proposals.js +197 -0
- package/dist/server/studio-ui/shell/pure.d.ts +3 -0
- package/dist/server/studio-ui/shell/pure.js +9 -0
- package/dist/server/studio-ui/shell/router.d.ts +1 -0
- package/dist/server/studio-ui/shell/router.js +95 -0
- package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
- package/dist/server/studio-ui/shell/sync.d.ts +1 -0
- package/dist/server/studio-ui/shell/sync.js +120 -0
- package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
- package/dist/server/studio-ui/shell/toasts-format.js +89 -0
- package/dist/server/studio-ui/shell/triage.d.ts +1 -0
- package/dist/server/studio-ui/shell/triage.js +210 -0
- package/dist/server/studio-ui/styles/console.d.ts +1 -0
- package/dist/server/studio-ui/styles/console.js +83 -0
- package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
- package/dist/server/studio-ui/styles/dialogs.js +118 -0
- package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
- package/dist/server/studio-ui/styles/responsive.js +28 -0
- package/dist/server/studio-ui/styles/shell.d.ts +1 -0
- package/dist/server/studio-ui/styles/shell.js +117 -0
- package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
- package/dist/server/studio-ui/styles/strategist.js +77 -0
- package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
- package/dist/server/studio-ui/styles/tokens-base.js +131 -0
- package/dist/server/studio-ui.d.ts +1 -10
- package/dist/server/studio-ui.js +63 -5314
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Painting: pages, sidebar, banner, plus small fragments the kept pages still use.
|
|
2
|
+
export const paintScript = String.raw ` // ─── Painting ──────────────────────────────────────────────────────────────
|
|
3
|
+
function cssEsc(v) { return window.CSS && CSS.escape ? CSS.escape(v) : String(v).replace(/["\\]/g, '\\$&'); }
|
|
4
|
+
|
|
5
|
+
function focusMemo(root) {
|
|
6
|
+
var el = document.activeElement;
|
|
7
|
+
if (!el || el === document.body || el === root || !root.contains(el)) return null;
|
|
8
|
+
var sel = null;
|
|
9
|
+
if (el.id) sel = '#' + cssEsc(el.id);
|
|
10
|
+
else if (el.getAttribute('data-act')) {
|
|
11
|
+
sel = '[data-act="' + cssEsc(el.getAttribute('data-act')) + '"]';
|
|
12
|
+
if (el.getAttribute('data-key')) sel += '[data-key="' + cssEsc(el.getAttribute('data-key')) + '"]';
|
|
13
|
+
} else if (el.hasAttribute('data-ms-select')) sel = '[data-ms-select]';
|
|
14
|
+
return sel ? { sel: sel, start: el.selectionStart, end: el.selectionEnd } : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function restoreFocus(root, memo) {
|
|
18
|
+
if (!memo) return;
|
|
19
|
+
var el = root.querySelector(memo.sel);
|
|
20
|
+
if (!el || el.disabled) return;
|
|
21
|
+
el.focus({ preventScroll: true });
|
|
22
|
+
if (memo.start != null && typeof el.setSelectionRange === 'function') {
|
|
23
|
+
try { el.setSelectionRange(memo.start, memo.end); } catch (e) { /* ignore */ }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A page is loading until every server slice it depends on has answered once. */
|
|
28
|
+
function isPageLoading(def) {
|
|
29
|
+
return (def.deps || []).some(function (d) { return PIPE_PATHS[d] && !pipe.loaded[d]; });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function pageCtx(panel) {
|
|
33
|
+
return { model: Studio.model, state: Studio.state, fmt: fmt, ui: ui, width: panel ? panel.clientWidth : 0 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Renders one page into its panel; keeps focus and skips the write when nothing changed. */
|
|
37
|
+
function paintPage(id) {
|
|
38
|
+
var def = Studio.pages[id];
|
|
39
|
+
var panel = $('panel-' + id);
|
|
40
|
+
if (!def || !panel || !def.render) return;
|
|
41
|
+
var ctx = pageCtx(panel);
|
|
42
|
+
var html = isPageLoading(def) || !Studio.model ? (def.skeleton ? def.skeleton(ctx) : ui.skeleton()) : def.render(ctx);
|
|
43
|
+
panel.setAttribute('aria-busy', String(isPageLoading(def)));
|
|
44
|
+
if (panel.__html === html) return;
|
|
45
|
+
var memo = focusMemo(panel);
|
|
46
|
+
panel.innerHTML = html;
|
|
47
|
+
panel.__html = html;
|
|
48
|
+
restoreFocus(panel, memo);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Repaints a page (kept pages call this after their own state changes). */
|
|
52
|
+
function repaint(view) {
|
|
53
|
+
if (Studio.state.view !== view) return;
|
|
54
|
+
paintPage(view);
|
|
55
|
+
paintChrome();
|
|
56
|
+
}
|
|
57
|
+
Studio.repaint = function (id) { paintPage(id || Studio.state.view); };
|
|
58
|
+
|
|
59
|
+
function repaintFor(parts) {
|
|
60
|
+
var id = Studio.state.view;
|
|
61
|
+
var def = Studio.pages[id];
|
|
62
|
+
if (!def || !def.render) return;
|
|
63
|
+
if (!parts || (def.deps || []).some(function (d) { return parts.indexOf(d) !== -1; })) paintPage(id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function paintChrome() {
|
|
67
|
+
var side = $('side-nav');
|
|
68
|
+
var main = mainWorktree();
|
|
69
|
+
var agents = pipe.worktrees.filter(function (w) { return w.isAgentWorktree; }).length;
|
|
70
|
+
var s = pipe.report && pipe.report.summary;
|
|
71
|
+
var navHtml = Studio.sidebarHtml({
|
|
72
|
+
model: Studio.model, view: Studio.state.view,
|
|
73
|
+
worktreeCount: pipe.loaded.worktrees && !pipe.errors.worktrees ? agents : null,
|
|
74
|
+
benchmarkText: s ? compactNum(Number(s.averageThroughputOpsPerSec) || 0) : ''
|
|
75
|
+
});
|
|
76
|
+
if (side.__html !== navHtml) { side.innerHTML = navHtml; side.__html = navHtml; }
|
|
77
|
+
var foot = $('side-foot');
|
|
78
|
+
var footHtml = Studio.sideFootHtml({ root: main ? String(main.path || '') : '', branch: main ? (main.branch || 'detached') : '' });
|
|
79
|
+
if (foot.__html !== footHtml) { foot.innerHTML = footHtml; foot.__html = footHtml; }
|
|
80
|
+
var lost = !!(pipe.errors.tasks || pipe.errors.status) || live.mode === 'offline';
|
|
81
|
+
$('banner').hidden = !lost;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One entry point after data or state changes: model, sidebar, current page, panel, palette. */
|
|
85
|
+
function refreshUi(parts) {
|
|
86
|
+
rebuildModel();
|
|
87
|
+
paintChrome();
|
|
88
|
+
repaintFor(parts);
|
|
89
|
+
refreshDetail();
|
|
90
|
+
refreshPalette();
|
|
91
|
+
backfillLogs();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var uiTimer = null, uiParts = {};
|
|
95
|
+
function queueUiRefresh(parts) {
|
|
96
|
+
parts.forEach(function (p) { uiParts[p] = true; });
|
|
97
|
+
if (uiTimer) return;
|
|
98
|
+
uiTimer = setTimeout(function () {
|
|
99
|
+
var list = Object.keys(uiParts);
|
|
100
|
+
uiParts = {};
|
|
101
|
+
uiTimer = null;
|
|
102
|
+
refreshUi(list);
|
|
103
|
+
}, 250);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Newest output line per task comes from the live stream and, once, from the log tail. */
|
|
107
|
+
function feedLogTail(entry) {
|
|
108
|
+
pipe.logTail[entry.taskId] = ((pipe.logTail[entry.taskId] || '') + (entry.chunk || '')).slice(-2000);
|
|
109
|
+
queueUiRefresh(['logs']);
|
|
110
|
+
}
|
|
111
|
+
var logAsked = {};
|
|
112
|
+
function backfillLogs() {
|
|
113
|
+
if (!Studio.model) return;
|
|
114
|
+
Studio.model.running.forEach(function (t) {
|
|
115
|
+
if (logAsked[t.id] || pipe.logTail[t.id] != null) return;
|
|
116
|
+
logAsked[t.id] = true;
|
|
117
|
+
api('/api/pipeline/tasks/logs?taskId=' + encodeURIComponent(t.id) + '&tailLines=20').then(function (body) {
|
|
118
|
+
pipe.logTail[t.id] = body.log || '';
|
|
119
|
+
queueUiRefresh(['logs']);
|
|
120
|
+
}, function () { /* no log yet */ });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fragments the kept pages (Worktrees, Benchmarks) still use.
|
|
125
|
+
function panelHead(title, lead, actions) {
|
|
126
|
+
return '<div class="panel-head"><div class="panel-title"><h1>' + title + '</h1>' + (lead ? '<p class="lead">' + lead + '</p>' : '') + '</div>' +
|
|
127
|
+
(actions ? '<div class="panel-actions">' + actions + '</div>' : '') + '</div>';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function progressBar(pct, key, label, tone) {
|
|
131
|
+
return '<div class="progress' + (tone ? ' ' + tone : '') + '" role="progressbar" aria-label="' + esc(label) + '" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '">' +
|
|
132
|
+
'<span style="width:' + pct + '%"></span></div>';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function errorCard(what, message, part) {
|
|
136
|
+
var missing = /No route for|NOT_FOUND|\(404\)/.test(message || '');
|
|
137
|
+
return '<div class="card state state-error" role="alert"><div class="state-icon">' + ICON.alert + '</div>' +
|
|
138
|
+
'<h2>Could not load ' + esc(what) + '</h2><p>' + esc(message) + '</p>' +
|
|
139
|
+
(missing ? '<p>This studio server does not expose the pipeline API yet. Restart it with a nativ build that includes the Mission Control endpoints.</p>' : '') +
|
|
140
|
+
'<button class="btn" type="button" data-act="retry" data-key="' + part + '">Retry</button></div>';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Data is still shown after a failed live refresh; say so instead of silently going stale. */
|
|
144
|
+
function staleNote(part) {
|
|
145
|
+
return pipe.errors[part] ? '<p class="notice" role="status">Showing the last synced data. Refresh failed: ' + esc(pipe.errors[part]) + '</p>' : '';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function skeletonCards(n, cls) {
|
|
149
|
+
var out = '';
|
|
150
|
+
for (var i = 0; i < n; i++) out += '<div class="card ' + (cls || 'kpi') + '"><div class="skeleton short"></div><div class="skeleton"></div><div class="skeleton"></div></div>';
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const paletteViewScript: string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Pure command palette logic: what a query finds and how the list is drawn.
|
|
2
|
+
export const paletteViewScript = String.raw ` // ─── Command palette (pure) ─────────────────────────────────────────────────
|
|
3
|
+
var PALETTE_MAX = 9;
|
|
4
|
+
|
|
5
|
+
function taskHaystack(t) {
|
|
6
|
+
var info = agentInfo(t.agent);
|
|
7
|
+
return [t.id, t.title, t.agent || '', info.label].concat(t.files).join(' ').toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function emptyQueryTasks(model) {
|
|
11
|
+
var done = model.tasks.filter(function (t) { return t.status === 'completed'; });
|
|
12
|
+
var timed = done.filter(function (t) { return t.endT; }).sort(function (a, b) { return b.endT - a.endT; });
|
|
13
|
+
var rest = done.filter(function (t) { return !t.endT; }).reverse();
|
|
14
|
+
return model.running.concat(model.stuck, timed, rest);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function matchRank(t, q) {
|
|
18
|
+
var id = t.id.toLowerCase();
|
|
19
|
+
return id === q ? 0 : id.indexOf(q) === 0 ? 1 : 2;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Pages first ("Go to Flow"), then tasks matching id, title, agent id, agent label or any file. */
|
|
23
|
+
Studio.paletteItems = function (query) {
|
|
24
|
+
var model = Studio.model;
|
|
25
|
+
var q = String(query || '').replace(/^\s+|\s+$/g, '').toLowerCase();
|
|
26
|
+
var pages = q ? Studio.order.filter(function (p) { return p.label.toLowerCase().indexOf(q) !== -1; })
|
|
27
|
+
.map(function (p) { return { page: p }; }) : [];
|
|
28
|
+
var tasks = [];
|
|
29
|
+
if (model) {
|
|
30
|
+
tasks = q ? model.tasks.filter(function (t) { return taskHaystack(t).indexOf(q) !== -1; })
|
|
31
|
+
.sort(function (a, b) { return matchRank(a, q) - matchRank(b, q); }) : emptyQueryTasks(model);
|
|
32
|
+
}
|
|
33
|
+
return pages.concat(tasks.map(function (t) { return { task: t }; })).slice(0, PALETTE_MAX);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
Studio.paletteFrame = function () {
|
|
37
|
+
return '<div class="pal" role="dialog" aria-label="Find a task" aria-modal="true">' +
|
|
38
|
+
'<input id="pal-q" type="text" placeholder="Search by task id, title, agent or file" autocomplete="off" ' +
|
|
39
|
+
'role="combobox" aria-expanded="true" aria-controls="pal-list" aria-label="Find a task">' +
|
|
40
|
+
'<ul id="pal-list" role="listbox"></ul>' +
|
|
41
|
+
'<div class="hint"><span>Up and down to move</span><span>Enter to open</span><span>Esc to close</span></div></div>';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Rows for the listbox; sel is the highlighted index. */
|
|
45
|
+
Studio.paletteListHtml = function (items, sel, query) {
|
|
46
|
+
if (!items.length) {
|
|
47
|
+
return '<li class="faint pal-none">No task matches. Try an id like task-31 or a file name.</li>';
|
|
48
|
+
}
|
|
49
|
+
return items.map(function (it, i) {
|
|
50
|
+
var on = ' role="option" id="pal-opt-' + i + '" data-i="' + i + '" aria-selected="' + (i === sel) + '">';
|
|
51
|
+
if (it.page) {
|
|
52
|
+
return '<li' + on + '<span class="pal-ico">' + ui.svg(it.page.icon) + '</span><span class="t">Go to ' + esc(it.page.label) + '</span></li>';
|
|
53
|
+
}
|
|
54
|
+
var t = it.task;
|
|
55
|
+
return '<li' + on + ui.statusIcon(t.status) + '<span class="mono faint hide-sm">' + esc(t.id) + '</span>' +
|
|
56
|
+
'<span class="t">' + esc(t.title) + '</span><span class="hide-sm">' + ui.agentChip(t.agent) + '</span></li>';
|
|
57
|
+
}).join('');
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const paletteScript: string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Command palette: Ctrl+K / Cmd+K / "/" to open, arrows and Enter to pick, Esc to close.
|
|
2
|
+
export const paletteScript = String.raw ` // ─── Command palette ───────────────────────────────────────────────────────
|
|
3
|
+
var pal = { open: false, sel: 0, items: [], opener: null };
|
|
4
|
+
|
|
5
|
+
function drawPalette() {
|
|
6
|
+
var q = $('pal-q');
|
|
7
|
+
pal.items = Studio.paletteItems(q ? q.value : '');
|
|
8
|
+
if (pal.sel >= pal.items.length) pal.sel = 0;
|
|
9
|
+
$('pal-list').innerHTML = Studio.paletteListHtml(pal.items, pal.sel);
|
|
10
|
+
if (q) q.setAttribute('aria-activedescendant', pal.items.length ? 'pal-opt-' + pal.sel : '');
|
|
11
|
+
var cur = $('pal-opt-' + pal.sel);
|
|
12
|
+
if (cur && cur.scrollIntoView) cur.scrollIntoView({ block: 'nearest' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function openPalette() {
|
|
16
|
+
if (pal.open) return;
|
|
17
|
+
pal.open = true;
|
|
18
|
+
pal.sel = 0;
|
|
19
|
+
pal.opener = document.activeElement;
|
|
20
|
+
$('pal').innerHTML = Studio.paletteFrame();
|
|
21
|
+
$('pal').hidden = false;
|
|
22
|
+
drawPalette();
|
|
23
|
+
$('pal-q').focus();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function closePalette() {
|
|
27
|
+
if (!pal.open) return;
|
|
28
|
+
pal.open = false;
|
|
29
|
+
$('pal').hidden = true;
|
|
30
|
+
$('pal').innerHTML = '';
|
|
31
|
+
if (pal.opener && document.body.contains(pal.opener) && pal.opener.focus) pal.opener.focus();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function refreshPalette() { if (pal.open) drawPalette(); }
|
|
35
|
+
|
|
36
|
+
function pickPalette(i) {
|
|
37
|
+
var it = pal.items[i];
|
|
38
|
+
if (!it) return;
|
|
39
|
+
pal.opener = null;
|
|
40
|
+
closePalette();
|
|
41
|
+
if (it.page) { Studio.go(it.page.id); return; }
|
|
42
|
+
Studio.state.milestoneId = it.task.milestoneId;
|
|
43
|
+
openDetail(it.task.id);
|
|
44
|
+
repaintFor(null);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$('btn-find').addEventListener('click', openPalette);
|
|
48
|
+
$('pal').addEventListener('click', function (e) {
|
|
49
|
+
var li = e.target.closest('li[data-i]');
|
|
50
|
+
if (li) { pickPalette(Number(li.getAttribute('data-i'))); return; }
|
|
51
|
+
if (e.target.id === 'pal') closePalette();
|
|
52
|
+
});
|
|
53
|
+
$('pal').addEventListener('input', function () { pal.sel = 0; drawPalette(); });
|
|
54
|
+
|
|
55
|
+
function typingInField() {
|
|
56
|
+
var a = document.activeElement;
|
|
57
|
+
return !!a && /^(input|select|textarea)$/i.test(a.tagName);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
document.addEventListener('keydown', function (e) {
|
|
61
|
+
if ((e.ctrlKey || e.metaKey) && String(e.key).toLowerCase() === 'k') {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
if (pal.open) closePalette(); else openPalette();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (e.key === '/' && !pal.open && !typingInField() && !e.ctrlKey && !e.metaKey) { e.preventDefault(); openPalette(); return; }
|
|
67
|
+
if (pal.open) {
|
|
68
|
+
var n = Math.max(1, pal.items.length);
|
|
69
|
+
if (e.key === 'Escape') { e.preventDefault(); closePalette(); }
|
|
70
|
+
else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
pal.sel = (pal.sel + (e.key === 'ArrowDown' ? 1 : -1) + n) % n;
|
|
73
|
+
drawPalette();
|
|
74
|
+
} else if (e.key === 'Enter') { e.preventDefault(); pickPalette(pal.sel); }
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (e.key === 'Escape' && Studio.state.detailTaskId && !document.querySelector('dialog[open]')) closeDetail(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const pipelineStateScript: string;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Pipeline data store, fetching, and the model rebuild that follows every change.
|
|
2
|
+
export const pipelineStateScript = String.raw ` // ─── Pipeline state ────────────────────────────────────────────────────────
|
|
3
|
+
var PIPE_PATHS = {
|
|
4
|
+
status: '/api/pipeline/status',
|
|
5
|
+
tasks: '/api/pipeline/tasks',
|
|
6
|
+
worktrees: '/api/pipeline/worktrees',
|
|
7
|
+
benchmarks: '/api/pipeline/benchmarks',
|
|
8
|
+
runs: '/api/pipeline/tasks/runs',
|
|
9
|
+
telemetry: '/api/pipeline/telemetry/detailed',
|
|
10
|
+
escalations: '/api/pipeline/escalations?status=pending_review',
|
|
11
|
+
triage: '/api/pipeline/triage/status'
|
|
12
|
+
};
|
|
13
|
+
var ALL_PARTS = ['status', 'tasks', 'worktrees', 'benchmarks', 'runs', 'telemetry', 'escalations', 'triage'];
|
|
14
|
+
var REDUCED_MOTION = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : { matches: false };
|
|
15
|
+
|
|
16
|
+
var pipe = {
|
|
17
|
+
status: null,
|
|
18
|
+
milestones: [],
|
|
19
|
+
worktrees: [],
|
|
20
|
+
runs: [],
|
|
21
|
+
report: null,
|
|
22
|
+
telemetry: null,
|
|
23
|
+
escalations: [],
|
|
24
|
+
triage: null,
|
|
25
|
+
triageRunning: {},
|
|
26
|
+
triageRecent: {},
|
|
27
|
+
triageLast: null,
|
|
28
|
+
logTail: {},
|
|
29
|
+
loaded: {},
|
|
30
|
+
errors: {},
|
|
31
|
+
busy: {},
|
|
32
|
+
syncedAt: null
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function allTasks() {
|
|
36
|
+
var out = [];
|
|
37
|
+
(pipe.milestones || []).forEach(function (m) { (m.tasks || []).forEach(function (t) { out.push(t); }); });
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function taskById(id) {
|
|
41
|
+
var found = null;
|
|
42
|
+
allTasks().some(function (t) { if (t.id === id) { found = t; return true; } return false; });
|
|
43
|
+
return found;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setProjectName(name) {
|
|
47
|
+
if (!name) return;
|
|
48
|
+
var el = $('project-name');
|
|
49
|
+
if (el) { el.textContent = name; el.hidden = false; }
|
|
50
|
+
document.title = 'Nativ Studio · ' + name;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fetchPipeline(parts) {
|
|
54
|
+
return Promise.all(parts.map(function (part) {
|
|
55
|
+
return api(PIPE_PATHS[part]).then(function (body) {
|
|
56
|
+
applyPipeline(part, body);
|
|
57
|
+
pipe.errors[part] = null;
|
|
58
|
+
}, function (e) {
|
|
59
|
+
pipe.errors[part] = e.message;
|
|
60
|
+
}).then(function () { pipe.loaded[part] = true; });
|
|
61
|
+
})).then(function () {
|
|
62
|
+
pipe.syncedAt = new Date();
|
|
63
|
+
refreshUi(parts);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function applyPipeline(part, body) {
|
|
68
|
+
if (part === 'status') {
|
|
69
|
+
pipe.status = body.pipeline || null;
|
|
70
|
+
if (pipe.status) setProjectName(pipe.status.projectName);
|
|
71
|
+
} else if (part === 'tasks') {
|
|
72
|
+
pipe.milestones = Array.isArray(body.milestones) ? body.milestones : [];
|
|
73
|
+
setProjectName(body.projectName);
|
|
74
|
+
} else if (part === 'worktrees') {
|
|
75
|
+
pipe.worktrees = Array.isArray(body.worktrees) ? body.worktrees : [];
|
|
76
|
+
} else if (part === 'runs') {
|
|
77
|
+
pipe.runs = Array.isArray(body.runs) ? body.runs : [];
|
|
78
|
+
} else if (part === 'benchmarks') {
|
|
79
|
+
pipe.report = body.report || null;
|
|
80
|
+
} else if (part === 'telemetry') {
|
|
81
|
+
pipe.telemetry = { summary: body.summary || {}, taskBreakdowns: Array.isArray(body.taskBreakdowns) ? body.taskBreakdowns : [] };
|
|
82
|
+
} else if (part === 'escalations') {
|
|
83
|
+
pipe.escalations = Array.isArray(body.escalations) ? body.escalations : [];
|
|
84
|
+
syncHealTab();
|
|
85
|
+
renderTriageChip();
|
|
86
|
+
} else if (part === 'triage') {
|
|
87
|
+
pipe.triage = body;
|
|
88
|
+
renderTriageChip();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Rebuilds the client model from the raw slices. */
|
|
93
|
+
function rebuildModel() {
|
|
94
|
+
Studio.setModel(buildModel({
|
|
95
|
+
milestones: pipe.milestones,
|
|
96
|
+
status: pipe.status,
|
|
97
|
+
telemetry: pipe.telemetry,
|
|
98
|
+
runs: pipe.runs,
|
|
99
|
+
escalations: pipe.escalations,
|
|
100
|
+
logTail: pipe.logTail
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Newest pending escalation for a task (the list arrives newest first). */
|
|
105
|
+
function escalationFor(taskId) {
|
|
106
|
+
var found = null;
|
|
107
|
+
(pipe.escalations || []).some(function (e) { if (e.taskId === taskId && e.status === 'pending_review') { found = e; return true; } return false; });
|
|
108
|
+
return found;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The main (non-agent) worktree is the workspace root and carries the active branch. */
|
|
112
|
+
function mainWorktree() {
|
|
113
|
+
var main = null;
|
|
114
|
+
pipe.worktrees.some(function (w) { if (!w.isAgentWorktree) { main = w; return true; } return false; });
|
|
115
|
+
return main;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 1234 becomes "1.2k" so the sidebar badge stays small. */
|
|
119
|
+
function compactNum(n) {
|
|
120
|
+
if (!isFinite(n)) return '–';
|
|
121
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
122
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
123
|
+
return String(Math.round(n));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Newest Just-finished style helpers live in pages; here only what the runner tools need. */
|
|
127
|
+
var RUN_LABEL = {
|
|
128
|
+
spawning_worktree: 'Worktree', running: 'Claude Running', verifying: 'Verifying', merging: 'Merging',
|
|
129
|
+
completed: 'Run completed', failed: 'Run failed', aborted: 'Run aborted'
|
|
130
|
+
};
|
|
131
|
+
function runFor(taskId) {
|
|
132
|
+
var found = null;
|
|
133
|
+
(pipe.runs || []).some(function (r) { if (r.taskId === taskId) { found = r; return true; } return false; });
|
|
134
|
+
return found;
|
|
135
|
+
}
|
|
136
|
+
function upsertRun(run) {
|
|
137
|
+
if (!run || !run.taskId) return;
|
|
138
|
+
pipe.runs = [run].concat((pipe.runs || []).filter(function (r) { return r.taskId !== run.taskId; }));
|
|
139
|
+
}
|
|
140
|
+
function elapsedOf(run) {
|
|
141
|
+
if (!run || !run.startedAt) return null;
|
|
142
|
+
var ms = isRunActive(run) ? Date.now() - Date.parse(run.startedAt) : run.durationMs;
|
|
143
|
+
return typeof ms === 'number' && isFinite(ms) && ms >= 0 ? ms : null;
|
|
144
|
+
}
|
|
145
|
+
/** mm:ss elapsed time shown in the console drawer. */
|
|
146
|
+
function mmss(ms) {
|
|
147
|
+
var total = Math.floor((ms || 0) / 1000);
|
|
148
|
+
var mm = Math.floor(total / 60), ss = total % 60;
|
|
149
|
+
return (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const proposalsScript: string;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Self-healing proposal review.
|
|
2
|
+
export const proposalsScript = String.raw ` // ─── Console tab 3: self-healing proposal review (/api/pipeline/escalations) ─
|
|
3
|
+
/** Shows the tab only while the console's task has a pending escalation; keeps the panel in sync. */
|
|
4
|
+
function syncHealTab() {
|
|
5
|
+
var escalation = rc.taskId ? escalationFor(rc.taskId) : null;
|
|
6
|
+
// Keep the tab while a just-finished resolution is still on screen.
|
|
7
|
+
var show = !!escalation || !!(rc.heal && (rc.heal.result || rc.heal.triage) && rc.heal.taskId === rc.taskId);
|
|
8
|
+
$('rc-tab-heal').hidden = !show;
|
|
9
|
+
$('rc-heal-count').hidden = !(escalation && escalation.proposedPatch);
|
|
10
|
+
if (!show && rc.tab === 'heal') setConsoleTab('logs');
|
|
11
|
+
else if (rc.tab === 'heal') renderHeal();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function patchLine(sign, p) {
|
|
15
|
+
if (!p) return '';
|
|
16
|
+
var value = p.value === undefined ? '' : ' ' + JSON.stringify(p.value);
|
|
17
|
+
return '<span class="' + (sign === '-' ? 'df-del' : 'df-add') + '">' + sign + ' ' + esc(p.operation + ' ' + p.path + value) + '</span>';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function healBody(escalation, opts) {
|
|
21
|
+
var withActions = !(opts && opts.actions === false);
|
|
22
|
+
var p = escalation.proposedPatch;
|
|
23
|
+
var diag = '<p class="heal-diag"><strong>' + esc(escalation.summary || 'Escalated') + '</strong>' +
|
|
24
|
+
(escalation.details ? '<br>' + esc(escalation.details) : '') + '</p>';
|
|
25
|
+
if (!p) {
|
|
26
|
+
return '<div class="heal-head"><h3>No automatic fix could be proven</h3><span class="heal-tag">' + esc(escalation.id) + '</span></div>' + diag +
|
|
27
|
+
'<p class="heal-diag">' + esc(escalation.recommendedAction || 'Review the affected contracts, then unblock the task or dismiss the escalation.') + '</p>' +
|
|
28
|
+
(withActions ? '<div class="heal-actions"><button class="rc-btn reject" type="button" data-heal="reject">Dismiss</button>' +
|
|
29
|
+
'<button class="rc-btn approve" type="button" data-heal="approve">Approve & Unblock Task</button></div>' : '');
|
|
30
|
+
}
|
|
31
|
+
var proof = p.verificationProof || {};
|
|
32
|
+
var verdict = proof.passed
|
|
33
|
+
? '<span class="heal-verdict pass">' + ICON.check + 'PASSED in sandbox</span>'
|
|
34
|
+
: '<span class="heal-verdict fail">' + ICON.x + 'NOT VERIFIED</span>';
|
|
35
|
+
var patch = p.kind === 'restore_tests'
|
|
36
|
+
? (p.files || []).map(function (f) { return '<span class="df-add">+ restore ' + esc(f) + '</span>'; }).join('') +
|
|
37
|
+
(p.commands || []).map(function (c) { return '<span class="df-meta">$ ' + esc(c) + '</span>'; }).join('')
|
|
38
|
+
: patchLine('-', p.original) + patchLine('+', p.candidate);
|
|
39
|
+
var checks = (proof.checks || []).map(function (c) {
|
|
40
|
+
return '<li><span class="' + (c.passed ? 'ok' : 'bad') + '">' + (c.passed ? ICON.check : ICON.x) + '</span><span><b>' + esc(c.name) + '</b> ' + esc(c.detail) + '</span></li>';
|
|
41
|
+
}).join('');
|
|
42
|
+
return '<div class="heal-head"><h3>' + esc(humanize(p.strategy || 'proposal')) + '</h3><span class="heal-tag">' + esc(p.proposalId || escalation.id) + '</span>' + verdict + '</div>' +
|
|
43
|
+
diag + '<p class="heal-diag">' + esc(p.rationale || '') + '</p>' +
|
|
44
|
+
'<div class="heal-grid">' +
|
|
45
|
+
'<section class="heal-section"><h4>' + (p.kind === 'restore_tests' ? 'Restoration plan' : 'Rejected change → proposed change') + '</h4><pre class="heal-patch">' + patch + '</pre></section>' +
|
|
46
|
+
'<section class="heal-section"><h4>Isolated verification</h4><ul class="heal-checks">' + (checks || '<li>No checks recorded.</li>') + '</ul></section>' +
|
|
47
|
+
'</div>' +
|
|
48
|
+
(withActions ? '<div class="heal-actions"><button class="rc-btn reject" type="button" data-heal="reject">Reject Proposal</button>' +
|
|
49
|
+
'<button class="rc-btn approve" type="button" data-heal="approve"' + (proof.passed ? '' : ' disabled title="Only sandbox-verified proposals can be applied"') + '>Approve & Apply Patch</button></div>' : '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderHeal() {
|
|
53
|
+
var panel = $('rc-heal');
|
|
54
|
+
// Live updates must not wipe instructions while they are being typed.
|
|
55
|
+
if (document.activeElement && document.activeElement.id === 't1-custom' && panel.contains(document.activeElement)) return;
|
|
56
|
+
var heal = rc.heal && rc.heal.taskId === rc.taskId ? rc.heal : null;
|
|
57
|
+
var result = heal && heal.result ? '<p class="heal-result' + (heal.result.ok ? '' : ' is-error') + '" role="status">' + esc(heal.result.message) + '</p>' : '';
|
|
58
|
+
if (!pipe.loaded.escalations) { panel.innerHTML = '<div class="heal-skel"></div><div class="heal-skel"></div><div class="heal-skel" style="width:60%"></div>'; return; }
|
|
59
|
+
if (pipe.errors.escalations && !pipe.escalations.length) {
|
|
60
|
+
panel.innerHTML = '<p class="heal-result is-error">Could not load escalations: ' + esc(pipe.errors.escalations) + '</p>' +
|
|
61
|
+
'<div class="heal-actions"><button class="rc-btn" type="button" data-heal="retry">Retry</button></div>';
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
var escalation = escalationFor(rc.taskId);
|
|
65
|
+
var tri = triageFor(escalation, heal);
|
|
66
|
+
if (!escalation && !tri) {
|
|
67
|
+
panel.innerHTML = result || '<p class="heal-diag">No pending proposal for this task. When the circuit breaker trips on a third failed attempt, a sandbox-verified fix appears here for review.</p>';
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
var html = result;
|
|
71
|
+
if (escalation) html += triageStrip(escalation, !!tri);
|
|
72
|
+
if (tri) html += triageVerdictHtml(tri.verdict, tri.escalation);
|
|
73
|
+
if (escalation) {
|
|
74
|
+
var deciding = !!tri && tri.verdict.classification === 'REQUIRE_HUMAN_DECISION' && tri.escalation.id === escalation.id;
|
|
75
|
+
html += healBody(escalation, { actions: !deciding }) + (deciding ? decisionActions(escalation) : '');
|
|
76
|
+
}
|
|
77
|
+
panel.innerHTML = html;
|
|
78
|
+
if (heal && heal.busy) panel.querySelectorAll('[data-heal]').forEach(function (b) { b.disabled = true; });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** One click resolves: approve applies the proposal and unblocks the task, reject dismisses it. */
|
|
82
|
+
function resolveEscalation(decision, notes, buttonKey) {
|
|
83
|
+
var escalation = rc.taskId ? escalationFor(rc.taskId) : null;
|
|
84
|
+
if (!escalation || (rc.heal && rc.heal.busy)) return;
|
|
85
|
+
var taskId = rc.taskId;
|
|
86
|
+
rc.heal = { taskId: taskId, busy: true, result: null };
|
|
87
|
+
renderHeal();
|
|
88
|
+
var btn = $('rc-heal').querySelector('[data-heal="' + (buttonKey || decision) + '"]');
|
|
89
|
+
if (btn) btn.innerHTML = '<span class="spinner" aria-hidden="true"></span>' + (decision === 'approve' ? 'Applying…' : 'Rejecting…');
|
|
90
|
+
var payload = { escalationId: escalation.id, decision: decision };
|
|
91
|
+
if (notes) payload.notes = notes;
|
|
92
|
+
postJson('/api/pipeline/escalations/resolve', payload).then(function (body) {
|
|
93
|
+
rc.heal = { taskId: taskId, busy: false, result: { ok: true, message: body.message || 'Escalation resolved.' } };
|
|
94
|
+
toast(body.message || 'Escalation resolved', 'ok');
|
|
95
|
+
}, function (e) {
|
|
96
|
+
rc.heal = { taskId: taskId, busy: false, result: { ok: false, message: e.message } };
|
|
97
|
+
toast('Could not ' + decision + ' ' + escalation.id + ': ' + e.message);
|
|
98
|
+
}).then(function () {
|
|
99
|
+
renderHeal();
|
|
100
|
+
return fetchPipeline(['escalations', 'status', 'tasks', 'telemetry', 'triage']);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Per-turn usage from the native engine: fold the running total into the run record. */
|
|
105
|
+
function onRunnerUsage(event) {
|
|
106
|
+
if (!event || !event.taskId || !event.total) return;
|
|
107
|
+
var run = runFor(event.taskId);
|
|
108
|
+
if (run && (!event.runId || run.runId === event.runId)) run.usage = event.total;
|
|
109
|
+
if (rc.taskId === event.taskId) syncConsole();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function openConsole(taskId, quiet, tab) {
|
|
113
|
+
var drawer = $('runner-console-drawer');
|
|
114
|
+
if (rc.taskId !== taskId) {
|
|
115
|
+
rc.taskId = taskId;
|
|
116
|
+
rc.backfilled = false;
|
|
117
|
+
rc.stage = 0;
|
|
118
|
+
rc.diffSeq++;
|
|
119
|
+
rc.heal = null;
|
|
120
|
+
setDiffState('Uncommitted changes in the task worktree', [], '', null);
|
|
121
|
+
// The run list includes restored history, so no run means no log to fetch (the server would 400).
|
|
122
|
+
if (runFor(taskId)) {
|
|
123
|
+
$('rc-body').innerHTML = '<span class="rc-empty">Loading runner output…</span>';
|
|
124
|
+
loadConsoleLogs(taskId);
|
|
125
|
+
} else {
|
|
126
|
+
$('rc-body').innerHTML = '<span class="rc-empty">No runner has been dispatched for this task yet.</span>';
|
|
127
|
+
rc.backfilled = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
rc.minimized = false;
|
|
131
|
+
drawer.hidden = false;
|
|
132
|
+
drawer.classList.remove('is-min');
|
|
133
|
+
$('rc-minimize').textContent = 'Minimize';
|
|
134
|
+
$('rc-minimize').setAttribute('aria-expanded', 'true');
|
|
135
|
+
document.body.classList.add('has-console');
|
|
136
|
+
document.body.classList.remove('has-console-min');
|
|
137
|
+
syncConsole();
|
|
138
|
+
syncHealTab();
|
|
139
|
+
rcTick();
|
|
140
|
+
setConsoleTab(tab || rc.tab);
|
|
141
|
+
if (!quiet) (rc.tab === 'diff' ? $('rc-diff-body') : rc.tab === 'heal' ? $('rc-heal') : $('rc-body')).focus();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function closeConsole() {
|
|
145
|
+
rc.taskId = null;
|
|
146
|
+
clearInterval(rc.ticker);
|
|
147
|
+
rc.ticker = null;
|
|
148
|
+
clearTimeout(rc.diffTimer);
|
|
149
|
+
rc.diffTimer = null;
|
|
150
|
+
$('runner-console-drawer').hidden = true;
|
|
151
|
+
document.body.classList.remove('has-console', 'has-console-min');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toggleConsoleMinimized() {
|
|
155
|
+
rc.minimized = !rc.minimized;
|
|
156
|
+
var drawer = $('runner-console-drawer');
|
|
157
|
+
drawer.classList.toggle('is-min', rc.minimized);
|
|
158
|
+
document.body.classList.toggle('has-console', !rc.minimized);
|
|
159
|
+
document.body.classList.toggle('has-console-min', rc.minimized);
|
|
160
|
+
var btn = $('rc-minimize');
|
|
161
|
+
btn.textContent = rc.minimized ? 'Expand' : 'Minimize';
|
|
162
|
+
btn.setAttribute('aria-expanded', String(!rc.minimized));
|
|
163
|
+
if (!rc.minimized && rc.tab === 'diff' && rc.taskId) loadConsoleDiff(rc.taskId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onRunnerStatus(run) {
|
|
167
|
+
if (!run || !run.taskId) return;
|
|
168
|
+
upsertRun(run);
|
|
169
|
+
if (rc.taskId === run.taskId) {
|
|
170
|
+
syncConsole();
|
|
171
|
+
if (isRunActive(run)) rcTick();
|
|
172
|
+
queueConsoleDiff();
|
|
173
|
+
}
|
|
174
|
+
refreshUi(['runs']);
|
|
175
|
+
if (!RUN_ACTIVE[run.status]) {
|
|
176
|
+
var statusMsg = (RUN_LABEL[run.status] || run.status) + ': ' + run.taskId;
|
|
177
|
+
if (run.error && run.status !== 'completed') statusMsg += ' (' + run.error + ')';
|
|
178
|
+
toast(statusMsg, run.status === 'completed' ? 'ok' : 'err');
|
|
179
|
+
// The agent may have moved the task through "nativ task complete" while it ran.
|
|
180
|
+
queueRefresh(['status', 'tasks', 'worktrees', 'runs']);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function onRunnerLog(entry) {
|
|
185
|
+
if (!entry || !entry.taskId) return;
|
|
186
|
+
var run = runFor(entry.taskId);
|
|
187
|
+
if (run) run.logBytes = (run.logBytes || 0) + (entry.chunk ? entry.chunk.length : 0);
|
|
188
|
+
feedLogTail(entry);
|
|
189
|
+
if (rc.taskId !== entry.taskId || !rc.backfilled) return;
|
|
190
|
+
var empty = $('rc-body').querySelector('.rc-empty');
|
|
191
|
+
if (empty) $('rc-body').innerHTML = '';
|
|
192
|
+
appendConsole(ansiToHtml(entry.chunk, entry.stream === 'stderr' ? 'rc-fg-warn' : ''));
|
|
193
|
+
$('rc-bytes').textContent = formatBytes((run && run.logBytes) || 0);
|
|
194
|
+
queueConsoleDiff();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
`;
|