@njeybe/nativ 2.2.0 → 2.3.1

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +15 -0
  3. package/dist/runner/agent-supervisor.d.ts +5 -3
  4. package/dist/runner/agent-supervisor.js +17 -12
  5. package/dist/server/studio-server.js +1 -130
  6. package/dist/server/studio-ui/document.d.ts +3 -0
  7. package/dist/server/studio-ui/document.js +22 -0
  8. package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
  9. package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
  10. package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
  11. package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
  12. package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
  13. package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
  14. package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
  15. package/dist/server/studio-ui/pages/database/browser.js +326 -0
  16. package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
  17. package/dist/server/studio-ui/pages/database/connection.js +158 -0
  18. package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
  19. package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
  20. package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
  21. package/dist/server/studio-ui/pages/database/drift.js +301 -0
  22. package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
  23. package/dist/server/studio-ui/pages/database/events.js +231 -0
  24. package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
  25. package/dist/server/studio-ui/pages/database/explorer.js +183 -0
  26. package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
  27. package/dist/server/studio-ui/pages/database/index.js +28 -0
  28. package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
  29. package/dist/server/studio-ui/pages/database/markup.js +32 -0
  30. package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
  31. package/dist/server/studio-ui/pages/database/state.js +57 -0
  32. package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
  33. package/dist/server/studio-ui/pages/database/styles.js +118 -0
  34. package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
  35. package/dist/server/studio-ui/pages/flow/index.js +11 -0
  36. package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
  37. package/dist/server/studio-ui/pages/flow/layout.js +105 -0
  38. package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
  39. package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
  40. package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
  41. package/dist/server/studio-ui/pages/flow/script.js +61 -0
  42. package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
  43. package/dist/server/studio-ui/pages/flow/styles.js +59 -0
  44. package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
  45. package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
  46. package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
  47. package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
  48. package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
  49. package/dist/server/studio-ui/pages/home/index.js +11 -0
  50. package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
  51. package/dist/server/studio-ui/pages/home/script.js +160 -0
  52. package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
  53. package/dist/server/studio-ui/pages/home/styles.js +48 -0
  54. package/dist/server/studio-ui/pages/index.d.ts +2 -0
  55. package/dist/server/studio-ui/pages/index.js +17 -0
  56. package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
  57. package/dist/server/studio-ui/pages/tasks/index.js +11 -0
  58. package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
  59. package/dist/server/studio-ui/pages/tasks/script.js +96 -0
  60. package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
  61. package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
  62. package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
  63. package/dist/server/studio-ui/pages/team/index.js +11 -0
  64. package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
  65. package/dist/server/studio-ui/pages/team/script.js +112 -0
  66. package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
  67. package/dist/server/studio-ui/pages/team/styles.js +22 -0
  68. package/dist/server/studio-ui/pages/types.d.ts +13 -0
  69. package/dist/server/studio-ui/pages/types.js +1 -0
  70. package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
  71. package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
  72. package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
  73. package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
  74. package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
  75. package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
  76. package/dist/server/studio-ui/shell/actions.d.ts +1 -0
  77. package/dist/server/studio-ui/shell/actions.js +49 -0
  78. package/dist/server/studio-ui/shell/boot.d.ts +2 -0
  79. package/dist/server/studio-ui/shell/boot.js +13 -0
  80. package/dist/server/studio-ui/shell/console.d.ts +1 -0
  81. package/dist/server/studio-ui/shell/console.js +194 -0
  82. package/dist/server/studio-ui/shell/core.d.ts +1 -0
  83. package/dist/server/studio-ui/shell/core.js +50 -0
  84. package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
  85. package/dist/server/studio-ui/shell/detail-view.js +80 -0
  86. package/dist/server/studio-ui/shell/detail.d.ts +1 -0
  87. package/dist/server/studio-ui/shell/detail.js +83 -0
  88. package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
  89. package/dist/server/studio-ui/shell/dispatch.js +150 -0
  90. package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
  91. package/dist/server/studio-ui/shell/events-tools.js +94 -0
  92. package/dist/server/studio-ui/shell/format.d.ts +1 -0
  93. package/dist/server/studio-ui/shell/format.js +151 -0
  94. package/dist/server/studio-ui/shell/icons.d.ts +2 -0
  95. package/dist/server/studio-ui/shell/icons.js +20 -0
  96. package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
  97. package/dist/server/studio-ui/shell/markup-head.js +43 -0
  98. package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
  99. package/dist/server/studio-ui/shell/markup-panels.js +7 -0
  100. package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
  101. package/dist/server/studio-ui/shell/markup-tools.js +222 -0
  102. package/dist/server/studio-ui/shell/model.d.ts +1 -0
  103. package/dist/server/studio-ui/shell/model.js +192 -0
  104. package/dist/server/studio-ui/shell/paint.d.ts +1 -0
  105. package/dist/server/studio-ui/shell/paint.js +154 -0
  106. package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
  107. package/dist/server/studio-ui/shell/palette-view.js +60 -0
  108. package/dist/server/studio-ui/shell/palette.d.ts +1 -0
  109. package/dist/server/studio-ui/shell/palette.js +80 -0
  110. package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
  111. package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
  112. package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
  113. package/dist/server/studio-ui/shell/proposals.js +197 -0
  114. package/dist/server/studio-ui/shell/pure.d.ts +3 -0
  115. package/dist/server/studio-ui/shell/pure.js +9 -0
  116. package/dist/server/studio-ui/shell/router.d.ts +1 -0
  117. package/dist/server/studio-ui/shell/router.js +95 -0
  118. package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
  119. package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
  120. package/dist/server/studio-ui/shell/sync.d.ts +1 -0
  121. package/dist/server/studio-ui/shell/sync.js +120 -0
  122. package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
  123. package/dist/server/studio-ui/shell/toasts-format.js +89 -0
  124. package/dist/server/studio-ui/shell/triage.d.ts +1 -0
  125. package/dist/server/studio-ui/shell/triage.js +210 -0
  126. package/dist/server/studio-ui/styles/console.d.ts +1 -0
  127. package/dist/server/studio-ui/styles/console.js +83 -0
  128. package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
  129. package/dist/server/studio-ui/styles/dialogs.js +118 -0
  130. package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
  131. package/dist/server/studio-ui/styles/responsive.js +28 -0
  132. package/dist/server/studio-ui/styles/shell.d.ts +1 -0
  133. package/dist/server/studio-ui/styles/shell.js +117 -0
  134. package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
  135. package/dist/server/studio-ui/styles/strategist.js +77 -0
  136. package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
  137. package/dist/server/studio-ui/styles/tokens-base.js +131 -0
  138. package/dist/server/studio-ui.d.ts +1 -10
  139. package/dist/server/studio-ui.js +63 -5314
  140. package/package.json +2 -2
  141. package/plugin/.claude-plugin/plugin.json +1 -1
@@ -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 &amp; 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 &#8594; 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 &amp; 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
+ `;
@@ -0,0 +1,3 @@
1
+ export declare const pureScript: string;
2
+ /** Runs a page script in its own function scope with the Studio API. */
3
+ export declare const wrapPage: (script: string) => string;
@@ -0,0 +1,9 @@
1
+ import { formatScript } from './format.js';
2
+ import { modelScript } from './model.js';
3
+ import { sidebarViewScript } from './sidebar-view.js';
4
+ import { paletteViewScript } from './palette-view.js';
5
+ import { detailViewScript } from './detail-view.js';
6
+ // Everything that needs no DOM; the test harness runs exactly this in node:vm.
7
+ export const pureScript = [formatScript, modelScript, sidebarViewScript, paletteViewScript, detailViewScript].join('');
8
+ /** Runs a page script in its own function scope with the Studio API. */
9
+ export const wrapPage = (script) => ` (function (Studio) {\n${script}\n })(Studio);\n`;
@@ -0,0 +1 @@
1
+ export declare const routerScript: string;
@@ -0,0 +1,95 @@
1
+ // Routing, shell-wide click handling, theme toggle and live timers.
2
+ export const routerScript = String.raw ` // ─── Router and shell-wide events ──────────────────────────────────────────
3
+ function pageIds() { return Studio.order.map(function (p) { return p.id; }); }
4
+
5
+ function setView(view) {
6
+ Studio.state.view = view;
7
+ Studio.order.forEach(function (p) {
8
+ var el = $('panel-' + p.id);
9
+ if (el) el.hidden = p.id !== view;
10
+ if (p.id === view) $('crumb-view').textContent = p.label;
11
+ });
12
+ if (location.hash !== '#' + view && window.history && history.replaceState) history.replaceState(null, '', '#' + view);
13
+ paintChrome();
14
+ var def = Studio.pages[view];
15
+ if (def && def.onShow) def.onShow();
16
+ paintPage(view);
17
+ }
18
+
19
+ Studio.go = function (view, opts) {
20
+ opts = opts || {};
21
+ if (!Studio.pages[view]) return;
22
+ var S = Studio.state;
23
+ if (!opts.keepDetail) closeDetail(true);
24
+ if (opts.milestoneId) S.milestoneId = opts.milestoneId;
25
+ S.focusTaskId = opts.focusTaskId || null;
26
+ setView(view);
27
+ if (opts.scroll !== false) window.scrollTo(0, 0);
28
+ };
29
+
30
+ window.addEventListener('hashchange', function () {
31
+ var v = Studio.resolveHash(location.hash, pageIds());
32
+ if (v !== Studio.state.view) Studio.go(v);
33
+ });
34
+
35
+ function currentActions() {
36
+ var def = Studio.pages[Studio.state.view];
37
+ return (def && def.actions) || {};
38
+ }
39
+
40
+ $('main').addEventListener('click', function (e) {
41
+ var t = e.target;
42
+ var task = t.closest('[data-task]');
43
+ if (task) { openDetail(task.getAttribute('data-task')); return; }
44
+ var go = t.closest('[data-go]');
45
+ if (go) { Studio.go(go.getAttribute('data-go')); return; }
46
+ var ms = t.closest('[data-ms]');
47
+ if (ms) { Studio.go('tasks', { milestoneId: ms.getAttribute('data-ms') }); return; }
48
+ var act = t.closest('[data-act]');
49
+ if (!act || act.disabled) return;
50
+ var name = act.getAttribute('data-act');
51
+ var handler = currentActions()[name];
52
+ if (handler) handler(act, pageCtx($('panel-' + Studio.state.view)));
53
+ else if (name === 'retry' && PIPE_PATHS[act.getAttribute('data-key')]) fetchPipeline([act.getAttribute('data-key')]);
54
+ });
55
+ $('side-nav').addEventListener('click', function (e) {
56
+ var go = e.target.closest('[data-go]');
57
+ if (go) Studio.go(go.getAttribute('data-go'));
58
+ });
59
+
60
+ $('main').addEventListener('change', function (e) {
61
+ if (!e.target.hasAttribute || !e.target.hasAttribute('data-ms-select')) return;
62
+ Studio.state.milestoneId = e.target.value;
63
+ Studio.state.focusTaskId = null;
64
+ repaintFor(null);
65
+ });
66
+
67
+ $('banner-retry').addEventListener('click', function () {
68
+ fetchPipeline(ALL_PARTS);
69
+ connectEvents();
70
+ });
71
+
72
+ function applyTheme(theme) {
73
+ document.documentElement.setAttribute('data-theme', theme);
74
+ try { localStorage.setItem('nativ-studio:theme', theme); } catch (e) { /* private mode */ }
75
+ }
76
+ $('btn-theme').addEventListener('click', function () {
77
+ var root = document.documentElement;
78
+ var dark = window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches;
79
+ var cur = root.getAttribute('data-theme') || (dark ? 'dark' : 'light');
80
+ applyTheme(cur === 'dark' ? 'light' : 'dark');
81
+ });
82
+
83
+ // Live timers: any element with data-since counts up from that moment.
84
+ setInterval(function () {
85
+ var now = Studio.now();
86
+ document.querySelectorAll('[data-since]').forEach(function (el) {
87
+ el.textContent = fmt.dur(now - Number(el.getAttribute('data-since')));
88
+ });
89
+ }, 1000);
90
+
91
+ Studio.api = api;
92
+ Studio.postJson = postJson;
93
+ Studio.toast = function (message, kind) { toast(message, kind); };
94
+
95
+ `;
@@ -0,0 +1 @@
1
+ export declare const sidebarViewScript: string;
@@ -0,0 +1,38 @@
1
+ // Pure sidebar markup: grouped navigation with badges, and the workspace footer.
2
+ export const sidebarViewScript = String.raw ` // ─── Sidebar markup (pure) ──────────────────────────────────────────────────
3
+ var NAV_GROUPS = [['now', 'Now'], ['project', 'Project'], ['system', 'System']];
4
+
5
+ function navBadges(env) {
6
+ var badges = {}, model = env.model;
7
+ if (model && model.needsYou.length) badges.home = ['warn', model.needsYou.length];
8
+ if (model && model.running.length) badges.tasks = ['run', model.running.length];
9
+ if (env.worktreeCount != null) badges.worktrees = ['', env.worktreeCount];
10
+ if (env.benchmarkText) badges.benchmarks = ['', env.benchmarkText];
11
+ return badges;
12
+ }
13
+
14
+ /** env = { model, view, worktreeCount, benchmarkText }. */
15
+ Studio.sidebarHtml = function (env) {
16
+ var badges = navBadges(env);
17
+ var item = function (p) {
18
+ var b = badges[p.id];
19
+ return '<button type="button" class="nav-item" data-go="' + esc(p.id) + '"' +
20
+ (env.view === p.id ? ' aria-current="page"' : '') + '>' + ui.svg(p.icon) +
21
+ '<span class="nav-text">' + esc(p.label) + '</span>' +
22
+ (b ? '<span class="nav-badge' + (b[0] ? ' ' + b[0] : '') + '">' + esc(b[1]) + '</span>' : '') + '</button>';
23
+ };
24
+ var groups = NAV_GROUPS.map(function (g) {
25
+ var items = Studio.order.filter(function (p) { return p.group === g[0]; });
26
+ if (!items.length) return '';
27
+ return '<div class="nav-group"><div class="nav-glabel">' + g[1] + '</div>' + items.map(item).join('') + '</div>';
28
+ }).join('');
29
+ return '<nav class="nav" aria-label="Studio">' + groups + '</nav>';
30
+ };
31
+
32
+ /** env = { root, branch }. */
33
+ Studio.sideFootHtml = function (env) {
34
+ return '<span>Workspace</span><span class="mono" id="sb-root" title="' + esc(env.root || '') + '">' +
35
+ esc(env.root || '–') + '</span><span>branch <span class="mono" id="sb-branch-name">' + esc(env.branch || '–') + '</span></span>';
36
+ };
37
+
38
+ `;
@@ -0,0 +1 @@
1
+ export declare const syncScript: string;
@@ -0,0 +1,120 @@
1
+ // Live sync over the event stream.
2
+ export const syncScript = String.raw ` // ─── Real-time sync: EventSource on /api/events ────────────────────────────
3
+ var live = { mode: 'connecting', es: null, retry: 0, timer: null, lastBeat: 0, sawBeat: false, opened: false };
4
+ var LIVE_TEXT = { connecting: 'Connecting', live: 'Live', offline: 'Reconnecting' };
5
+ // Keys are SSE event names on the wire; values are the pipeline slices each one invalidates.
6
+ var EVENT_PARTS = {
7
+ // .ai/escalation.json writes (circuit-breaker trips, resolutions) arrive as plan changes.
8
+ 'plan_change': ['status', 'tasks', 'worktrees', 'escalations', 'triage'],
9
+ // The server also reports .ai/benchmark_report.json writes (e.g. a CLI "nativ bench") as telemetry changes.
10
+ 'telemetry_change': ['status', 'benchmarks', 'telemetry'],
11
+ 'benchmark_change': ['benchmarks'],
12
+ 'worktree_change': ['worktrees']
13
+ };
14
+
15
+ function setLive(mode, detail) {
16
+ live.mode = mode;
17
+ var el = $('live');
18
+ el.setAttribute('data-state', mode);
19
+ $('live-label').textContent = LIVE_TEXT[mode];
20
+ el.title = detail || (mode === 'live' ? 'Receiving real-time updates from /api/events' : '');
21
+ paintChrome();
22
+ }
23
+
24
+ function flashLive() {
25
+ var dot = $('live-dot');
26
+ dot.classList.remove('flash');
27
+ void dot.offsetWidth;
28
+ dot.classList.add('flash');
29
+ }
30
+
31
+ // fs.watch fires several events per write; batch them into one refetch.
32
+ var queuedParts = {}, queueTimer = null;
33
+ function queueRefresh(parts) {
34
+ parts.forEach(function (p) { queuedParts[p] = true; });
35
+ clearTimeout(queueTimer);
36
+ queueTimer = setTimeout(function () {
37
+ var list = Object.keys(queuedParts);
38
+ queuedParts = {};
39
+ fetchPipeline(list);
40
+ }, 150);
41
+ }
42
+
43
+ function onStreamEvent(type) {
44
+ live.lastBeat = Date.now();
45
+ if (type === 'heartbeat' || type === 'ping') { live.sawBeat = true; return; }
46
+ var parts = EVENT_PARTS[type];
47
+ if (!parts) return;
48
+ flashLive();
49
+ queueRefresh(parts);
50
+ if (parts.indexOf('worktrees') !== -1) queueConsoleDiff();
51
+ }
52
+
53
+ function connectEvents() {
54
+ clearTimeout(live.timer);
55
+ if (typeof window.EventSource !== 'function') {
56
+ setLive('offline', 'This browser has no EventSource support; refreshing every 15 seconds instead.');
57
+ setInterval(function () { fetchPipeline(ALL_PARTS); }, 15000);
58
+ return;
59
+ }
60
+ if (live.es) live.es.close();
61
+ setLive('connecting');
62
+ var es = new EventSource('/api/events');
63
+ live.es = es;
64
+ es.onopen = function () {
65
+ live.retry = 0;
66
+ live.lastBeat = Date.now();
67
+ setLive('live');
68
+ // Catch up on anything written while the stream was down.
69
+ if (live.opened) queueRefresh(ALL_PARTS);
70
+ live.opened = true;
71
+ };
72
+ es.onerror = function () {
73
+ if (es.readyState === EventSource.CLOSED) scheduleReconnect();
74
+ else setLive('connecting', 'Reconnecting to /api/events');
75
+ };
76
+ // Unnamed events may carry the type in a JSON payload: { "type": "plan_change" }.
77
+ es.onmessage = function (e) {
78
+ var type = 'message';
79
+ try { type = JSON.parse(e.data).type || type; } catch (err) { /* plain-text payload */ }
80
+ onStreamEvent(type);
81
+ };
82
+ Object.keys(EVENT_PARTS).concat(['heartbeat', 'ping']).forEach(function (name) {
83
+ es.addEventListener(name, function () { onStreamEvent(name); });
84
+ });
85
+ // Runner events carry a payload, so they bypass the slice-invalidation table.
86
+ es.addEventListener('runner_status', function (e) {
87
+ live.lastBeat = Date.now();
88
+ flashLive();
89
+ onRunnerStatus(parseEventData(e));
90
+ });
91
+ es.addEventListener('runner_log', function (e) {
92
+ live.lastBeat = Date.now();
93
+ onRunnerLog(parseEventData(e));
94
+ });
95
+ // Native engine: one event per API turn with the delta and the run's running total.
96
+ es.addEventListener('runner_token_usage', function (e) {
97
+ live.lastBeat = Date.now();
98
+ flashLive();
99
+ onRunnerUsage(parseEventData(e));
100
+ });
101
+ }
102
+
103
+ function parseEventData(e) {
104
+ try { return JSON.parse(e.data); } catch (err) { return null; }
105
+ }
106
+
107
+ function scheduleReconnect() {
108
+ if (live.es) { live.es.close(); live.es = null; }
109
+ var delay = Math.min(30000, 1000 * Math.pow(2, live.retry++));
110
+ setLive('offline', 'Live stream unavailable; retrying in ' + Math.round(delay / 1000) + 's');
111
+ clearTimeout(live.timer);
112
+ live.timer = setTimeout(connectEvents, delay);
113
+ }
114
+
115
+ // Heartbeat watchdog: armed only once the server has shown it sends heartbeats.
116
+ setInterval(function () {
117
+ if (live.es && live.sawBeat && Date.now() - live.lastBeat > 45000) scheduleReconnect();
118
+ }, 15000);
119
+
120
+ `;
@@ -0,0 +1 @@
1
+ export declare const toastsFormatScript: string;
@@ -0,0 +1,89 @@
1
+ // Toasts and formatting helpers.
2
+ export const toastsFormatScript = String.raw ` // ─── Toasts ────────────────────────────────────────────────────────────────
3
+ function hintFor(message) {
4
+ var m = String(message || '');
5
+ if (/ECONNREFUSED|ETIMEDOUT|timeout|EHOSTUNREACH/i.test(m)) return 'Check that the database server is running and the host/port are reachable from this machine.';
6
+ if (/ENOTFOUND|getaddrinfo/i.test(m)) return 'The hostname could not be resolved. Verify the host in your connection string.';
7
+ if (/password|authentication|access denied/i.test(m)) return 'Credentials were rejected. Re-enter the connection string in Connection Settings.';
8
+ if (/unable to open database file/i.test(m)) return 'The SQLite file was not found. Check the path relative to where nativ was started.';
9
+ if (/not configured|NOT_CONNECTED/i.test(m)) return 'Open Connection Settings to connect a database.';
10
+ if (/default credentials|UNAUTHENTICATED|PERMISSION_DENIED/i.test(m)) return 'For Firestore, run "gcloud auth application-default login" or connect to the emulator instead.';
11
+ if (/Server selection timed out|MongoServerSelectionError/i.test(m)) return 'MongoDB did not respond. Check the host, port, and network access list.';
12
+ return '';
13
+ }
14
+
15
+ function toast(message, kind) {
16
+ var el = document.createElement('div');
17
+ el.className = 'toast' + (kind === 'ok' ? ' ok' : '');
18
+ var hint = kind === 'ok' ? '' : hintFor(message);
19
+ el.innerHTML = '<div class="t-body"><div>' + esc(message) + '</div>' + (hint ? '<div class="t-hint">' + esc(hint) + '</div>' : '') +
20
+ '</div><button type="button" aria-label="Dismiss">×</button>';
21
+ el.querySelector('button').addEventListener('click', function () { el.remove(); });
22
+ $('toasts').appendChild(el);
23
+ setTimeout(function () { el.remove(); }, kind === 'ok' ? 3500 : 8000);
24
+ }
25
+
26
+ function flashCopied(button) {
27
+ var wrap = button.parentElement;
28
+ var tip = wrap && wrap.querySelector('.tooltip');
29
+ if (!tip) return;
30
+ tip.classList.add('show');
31
+ setTimeout(function () { tip.classList.remove('show'); }, 1200);
32
+ }
33
+
34
+ function copyText(text, button) {
35
+ var done = function () { flashCopied(button); };
36
+ if (navigator.clipboard && navigator.clipboard.writeText) {
37
+ navigator.clipboard.writeText(text).then(done, function () { fallbackCopy(text); done(); });
38
+ } else { fallbackCopy(text); done(); }
39
+ }
40
+
41
+ function fallbackCopy(text) {
42
+ var ta = document.createElement('textarea');
43
+ ta.value = text; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.opacity = '0';
44
+ document.body.appendChild(ta); ta.select();
45
+ try { document.execCommand('copy'); } catch (e) { /* ignore */ }
46
+ ta.remove();
47
+ }
48
+
49
+ // ─── Formatting ────────────────────────────────────────────────────────────
50
+ function num(n) {
51
+ if (n == null || n === '' || isNaN(n)) return '–';
52
+ return Number(n).toLocaleString('en-US', { maximumFractionDigits: 3 });
53
+ }
54
+ function usd(n) {
55
+ if (n == null || isNaN(n)) return '–';
56
+ n = Number(n);
57
+ return '$' + (n >= 100 ? n.toFixed(0) : n >= 1 ? n.toFixed(2) : n.toFixed(3));
58
+ }
59
+ function fmtMs(ms) {
60
+ if (ms == null || isNaN(ms)) return '–';
61
+ if (ms < 1000) return Math.round(ms) + ' ms';
62
+ if (ms < 60000) return (ms / 1000).toFixed(2) + ' s';
63
+ return Math.floor(ms / 60000) + 'm ' + Math.round((ms % 60000) / 1000) + 's';
64
+ }
65
+ function clampPct(v) { return Math.max(0, Math.min(100, Math.round(Number(v) || 0))); }
66
+ /** Telemetry stores the gatekeeper pass rate as a 0..1 ratio; tolerate a 0..100 percentage too. */
67
+ function passRatePct(v) {
68
+ if (v == null || isNaN(v)) return null;
69
+ return clampPct(v <= 1 ? v * 100 : v);
70
+ }
71
+ function fmtDate(iso) {
72
+ var d = new Date(iso);
73
+ if (isNaN(d.getTime())) return String(iso);
74
+ try { return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); } catch (e) { return d.toLocaleString(); }
75
+ }
76
+ function humanize(key) {
77
+ var words = String(key).replace(/[_-]+/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
78
+ words = words.replace(/ ms$/, ' (ms)').replace(/ usd$/, ' (USD)');
79
+ return words.charAt(0).toUpperCase() + words.slice(1);
80
+ }
81
+ function detailValue(v) {
82
+ if (typeof v === 'boolean') return v ? 'Yes' : 'No';
83
+ if (typeof v === 'number') return num(v);
84
+ if (Array.isArray(v)) return v.map(String).join(', ');
85
+ if (v && typeof v === 'object') return JSON.stringify(v);
86
+ return String(v == null ? '–' : v);
87
+ }
88
+
89
+ `;
@@ -0,0 +1 @@
1
+ export declare const triageScript: string;