@njeybe/nativ 2.1.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +17 -0
  3. package/dist/server/studio-ui/document.d.ts +3 -0
  4. package/dist/server/studio-ui/document.js +22 -0
  5. package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
  6. package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
  7. package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
  8. package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
  9. package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
  10. package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
  11. package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
  12. package/dist/server/studio-ui/pages/database/browser.js +326 -0
  13. package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
  14. package/dist/server/studio-ui/pages/database/connection.js +158 -0
  15. package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
  16. package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
  17. package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
  18. package/dist/server/studio-ui/pages/database/drift.js +301 -0
  19. package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
  20. package/dist/server/studio-ui/pages/database/events.js +231 -0
  21. package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
  22. package/dist/server/studio-ui/pages/database/explorer.js +183 -0
  23. package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
  24. package/dist/server/studio-ui/pages/database/index.js +28 -0
  25. package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
  26. package/dist/server/studio-ui/pages/database/markup.js +32 -0
  27. package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
  28. package/dist/server/studio-ui/pages/database/state.js +57 -0
  29. package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
  30. package/dist/server/studio-ui/pages/database/styles.js +118 -0
  31. package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
  32. package/dist/server/studio-ui/pages/flow/index.js +11 -0
  33. package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
  34. package/dist/server/studio-ui/pages/flow/layout.js +105 -0
  35. package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
  36. package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
  37. package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
  38. package/dist/server/studio-ui/pages/flow/script.js +61 -0
  39. package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
  40. package/dist/server/studio-ui/pages/flow/styles.js +59 -0
  41. package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
  42. package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
  43. package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
  44. package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
  45. package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
  46. package/dist/server/studio-ui/pages/home/index.js +11 -0
  47. package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
  48. package/dist/server/studio-ui/pages/home/script.js +160 -0
  49. package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
  50. package/dist/server/studio-ui/pages/home/styles.js +48 -0
  51. package/dist/server/studio-ui/pages/index.d.ts +2 -0
  52. package/dist/server/studio-ui/pages/index.js +17 -0
  53. package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
  54. package/dist/server/studio-ui/pages/tasks/index.js +11 -0
  55. package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
  56. package/dist/server/studio-ui/pages/tasks/script.js +96 -0
  57. package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
  58. package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
  59. package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
  60. package/dist/server/studio-ui/pages/team/index.js +11 -0
  61. package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
  62. package/dist/server/studio-ui/pages/team/script.js +112 -0
  63. package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
  64. package/dist/server/studio-ui/pages/team/styles.js +22 -0
  65. package/dist/server/studio-ui/pages/types.d.ts +13 -0
  66. package/dist/server/studio-ui/pages/types.js +1 -0
  67. package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
  68. package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
  69. package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
  70. package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
  71. package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
  72. package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
  73. package/dist/server/studio-ui/shell/actions.d.ts +1 -0
  74. package/dist/server/studio-ui/shell/actions.js +49 -0
  75. package/dist/server/studio-ui/shell/boot.d.ts +2 -0
  76. package/dist/server/studio-ui/shell/boot.js +13 -0
  77. package/dist/server/studio-ui/shell/console.d.ts +1 -0
  78. package/dist/server/studio-ui/shell/console.js +194 -0
  79. package/dist/server/studio-ui/shell/core.d.ts +1 -0
  80. package/dist/server/studio-ui/shell/core.js +50 -0
  81. package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
  82. package/dist/server/studio-ui/shell/detail-view.js +80 -0
  83. package/dist/server/studio-ui/shell/detail.d.ts +1 -0
  84. package/dist/server/studio-ui/shell/detail.js +83 -0
  85. package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
  86. package/dist/server/studio-ui/shell/dispatch.js +156 -0
  87. package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
  88. package/dist/server/studio-ui/shell/events-tools.js +94 -0
  89. package/dist/server/studio-ui/shell/format.d.ts +1 -0
  90. package/dist/server/studio-ui/shell/format.js +151 -0
  91. package/dist/server/studio-ui/shell/icons.d.ts +2 -0
  92. package/dist/server/studio-ui/shell/icons.js +20 -0
  93. package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
  94. package/dist/server/studio-ui/shell/markup-head.js +43 -0
  95. package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
  96. package/dist/server/studio-ui/shell/markup-panels.js +7 -0
  97. package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
  98. package/dist/server/studio-ui/shell/markup-tools.js +222 -0
  99. package/dist/server/studio-ui/shell/model.d.ts +1 -0
  100. package/dist/server/studio-ui/shell/model.js +192 -0
  101. package/dist/server/studio-ui/shell/paint.d.ts +1 -0
  102. package/dist/server/studio-ui/shell/paint.js +154 -0
  103. package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
  104. package/dist/server/studio-ui/shell/palette-view.js +60 -0
  105. package/dist/server/studio-ui/shell/palette.d.ts +1 -0
  106. package/dist/server/studio-ui/shell/palette.js +80 -0
  107. package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
  108. package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
  109. package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
  110. package/dist/server/studio-ui/shell/proposals.js +197 -0
  111. package/dist/server/studio-ui/shell/pure.d.ts +3 -0
  112. package/dist/server/studio-ui/shell/pure.js +9 -0
  113. package/dist/server/studio-ui/shell/router.d.ts +1 -0
  114. package/dist/server/studio-ui/shell/router.js +95 -0
  115. package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
  116. package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
  117. package/dist/server/studio-ui/shell/sync.d.ts +1 -0
  118. package/dist/server/studio-ui/shell/sync.js +120 -0
  119. package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
  120. package/dist/server/studio-ui/shell/toasts-format.js +89 -0
  121. package/dist/server/studio-ui/shell/triage.d.ts +1 -0
  122. package/dist/server/studio-ui/shell/triage.js +210 -0
  123. package/dist/server/studio-ui/styles/console.d.ts +1 -0
  124. package/dist/server/studio-ui/styles/console.js +83 -0
  125. package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
  126. package/dist/server/studio-ui/styles/dialogs.js +118 -0
  127. package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
  128. package/dist/server/studio-ui/styles/responsive.js +28 -0
  129. package/dist/server/studio-ui/styles/shell.d.ts +1 -0
  130. package/dist/server/studio-ui/styles/shell.js +117 -0
  131. package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
  132. package/dist/server/studio-ui/styles/strategist.js +77 -0
  133. package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
  134. package/dist/server/studio-ui/styles/tokens-base.js +131 -0
  135. package/dist/server/studio-ui.d.ts +1 -10
  136. package/dist/server/studio-ui.js +63 -5314
  137. package/package.json +2 -2
  138. package/plugin/.claude-plugin/plugin.json +1 -1
  139. package/plugin/agents/architect.md +9 -5
  140. package/templates/GEMINI.md +12 -8
  141. package/templates/claude-agents/architect.md +9 -5
  142. package/templates/dot-ai/subagents/flutter-developer.md +10 -0
  143. package/templates/dot-ai/subagents/frontend.md +10 -0
  144. package/templates/dot-ai/ui_specs.md +36 -6
@@ -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;
@@ -0,0 +1,210 @@
1
+ // Tier 1 strategist.
2
+ export const triageScript = String.raw ` // ─── Tier 1 AI Strategist (/api/pipeline/triage/*, ui_specs.md §5) ─────────
3
+ /** Decision-card selection for the escalation on screen; survives re-renders from live updates. */
4
+ var t1Choice = { escalationId: null, index: 0, custom: '' };
5
+
6
+ /** "gemini-3.8-flash" -> "Gemini 3.8 Flash". */
7
+ function modelName(id) {
8
+ return String(id || 'gemini').split('-').map(function (w) { return w.charAt(0).toUpperCase() + w.slice(1); }).join(' ');
9
+ }
10
+ function triageRunningCount() { return Object.keys(pipe.triageRunning).length; }
11
+ /** Pending escalations whose stored Tier 1 verdict asks the product owner to decide. */
12
+ function awaitingDecision() {
13
+ return (pipe.escalations || []).filter(function (e) {
14
+ return e.status === 'pending_review' && e.triage && e.triage.classification === 'REQUIRE_HUMAN_DECISION';
15
+ });
16
+ }
17
+ function lastTriage() {
18
+ if (pipe.triageLast) return pipe.triageLast;
19
+ var newest = null;
20
+ (pipe.escalations || []).forEach(function (e) {
21
+ if (e.triage && (!newest || String(e.triage.evaluatedAt) > String(newest.evaluatedAt))) newest = e.triage;
22
+ });
23
+ return newest;
24
+ }
25
+
26
+ function renderTriageChip() {
27
+ var t = pipe.triage;
28
+ var model = (t && t.model) || 'gemini-3.8-flash';
29
+ var waiting = awaitingDecision().length;
30
+ var state = triageRunningCount() ? 'triaging' : waiting ? 'attention' : 'idle';
31
+ var chip = $('t1-chip');
32
+ chip.setAttribute('data-state', state);
33
+ chip.title = state === 'triaging' ? 'Tier 1 AI Strategist is assessing an escalation'
34
+ : state === 'attention' ? waiting + ' escalation' + (waiting === 1 ? ' needs' : 's need') + ' your decision' : 'Tier 1 AI Strategist';
35
+ $('t1-chip-model').textContent = modelName(model);
36
+ $('t1-model').textContent = model;
37
+ var last = lastTriage();
38
+ $('t1-latency').textContent = last ? fmtMs(last.latencyMs) + ' · ' + modelName(last.model || model) : 'No evaluations yet';
39
+ $('t1-key').textContent = pipe.errors.triage && !t ? 'Status unavailable' : !t ? 'Checking' : t.hasApiKey ? 'Configured' : 'Not set (offline rules)';
40
+ var sw = $('t1-auto');
41
+ var on = !!(t && t.autoTriageEnabled);
42
+ sw.setAttribute('aria-checked', String(on));
43
+ sw.textContent = on ? 'ON' : 'OFF';
44
+ sw.disabled = !t || !!pipe.busy.triageConfig;
45
+ var stats = (t && t.stats) || {};
46
+ $('t1-evaluated').textContent = num(stats.totalEvaluated || 0);
47
+ $('t1-resolved').textContent = num(stats.autoResolved || 0);
48
+ $('t1-human').textContent = num(stats.escalatedToHuman || 0);
49
+ }
50
+
51
+ function setTriageMenu(open) {
52
+ var menu = $('t1-menu');
53
+ if (menu.hidden === !open) return;
54
+ menu.hidden = !open;
55
+ $('t1-chip').setAttribute('aria-expanded', String(open));
56
+ if (!open) return;
57
+ renderTriageChip();
58
+ fetchPipeline(['triage']);
59
+ ($('t1-auto').disabled ? menu : $('t1-auto')).focus();
60
+ }
61
+
62
+ function setAutoTriage(enabled) {
63
+ if (pipe.busy.triageConfig) return;
64
+ pipe.busy.triageConfig = true;
65
+ renderTriageChip();
66
+ postJson('/api/pipeline/triage/config', { autoTriageEnabled: enabled }).then(function (body) {
67
+ pipe.triage = Object.assign({}, pipe.triage || {}, { autoTriageEnabled: !!(body.config && body.config.autoTriageEnabled) });
68
+ toast(enabled ? 'Autonomous Auto-Triage on: fixes Tier 1 proves safe are applied without asking' : 'Autonomous Auto-Triage off: every fix waits for your approval', 'ok');
69
+ }, function (e) {
70
+ toast('Could not change Autonomous Auto-Triage: ' + e.message);
71
+ }).then(function () {
72
+ pipe.busy.triageConfig = false;
73
+ renderTriageChip();
74
+ if (rc.tab === 'heal') renderHeal();
75
+ });
76
+ }
77
+
78
+ /** Asks the strategist to assess one escalation. The server stores the verdict on the escalation record. */
79
+ function runTriage(escalation) {
80
+ if (!escalation || pipe.triageRunning[escalation.id]) return;
81
+ var taskId = escalation.taskId;
82
+ pipe.triageRunning[escalation.id] = taskId;
83
+ rc.heal = { taskId: taskId, busy: true, result: null };
84
+ renderTriageChip();
85
+ renderHeal();
86
+ refreshUi([]);
87
+ postJson('/api/pipeline/triage/evaluate', { escalationId: escalation.id }).then(function (body) {
88
+ pipe.triageLast = { latencyMs: body.latencyMs, model: body.model };
89
+ t1Choice.escalationId = null; // a re-run may bring new options
90
+ rc.heal = { taskId: taskId, busy: false, result: null, triage: body, escalation: escalation };
91
+ if (body.autoPatchApplied) {
92
+ pipe.triageRecent[taskId] = Date.now();
93
+ setTimeout(function () { delete pipe.triageRecent[taskId]; }, 12000);
94
+ toast('Tier 1 auto-resolved ' + escalation.id + ' in ' + fmtMs(body.latencyMs), 'ok');
95
+ } else if (body.classification === 'REQUIRE_HUMAN_DECISION') {
96
+ toast(escalation.id + ' needs your decision', 'ok');
97
+ }
98
+ // A resolved escalation leaves the pending list; keep its stored verdict (applied patch, unblock) on screen.
99
+ return api('/api/pipeline/escalations?status=all').then(function (list) {
100
+ (list.escalations || []).some(function (e) {
101
+ if (e.id !== escalation.id) return false;
102
+ if (rc.heal && rc.heal.triage === body) rc.heal.escalation = e;
103
+ return true;
104
+ });
105
+ }, function () { /* the evaluate response is enough to render */ });
106
+ }, function (e) {
107
+ rc.heal = { taskId: taskId, busy: false, result: { ok: false, message: 'Tier 1 triage failed: ' + e.message } };
108
+ toast('Tier 1 triage failed for ' + escalation.id + ': ' + e.message);
109
+ }).then(function () {
110
+ delete pipe.triageRunning[escalation.id];
111
+ renderTriageChip();
112
+ renderHeal();
113
+ refreshUi([]);
114
+ return fetchPipeline(['escalations', 'triage', 'status', 'tasks', 'telemetry']);
115
+ });
116
+ }
117
+
118
+ /** Verdict for the console task: this tab's latest evaluation first, else the one stored on the escalation. */
119
+ function triageFor(escalation, heal) {
120
+ if (heal && heal.triage) {
121
+ var stored = heal.escalation || escalation;
122
+ return { verdict: (stored && stored.triage) || heal.triage, escalation: stored };
123
+ }
124
+ if (escalation && escalation.triage) return { verdict: escalation.triage, escalation: escalation };
125
+ return null;
126
+ }
127
+
128
+ function triageStrip(escalation, hasVerdict) {
129
+ var running = !!pipe.triageRunning[escalation.id];
130
+ var text = running ? 'is assessing this escalation' : hasVerdict ? 'has assessed this escalation. Run it again after the contracts change.'
131
+ : 'can assess this escalation: resolve a safe fix automatically or prepare a decision card for you.';
132
+ return '<div class="t1-strip' + (running ? ' is-triaging' : '') + '" role="status"><span>' + ICON.spark + '<strong>Tier 1 AI Strategist</strong> ' + esc(text) + '</span>' +
133
+ '<button class="rc-btn t1-run" type="button" data-heal="t1-run"' + (running ? ' disabled' : '') + '>' +
134
+ (running ? '<span class="spinner" aria-hidden="true"></span>Triaging…' : hasVerdict ? 'Re-run Triage' : 'Run Tier 1 Triage') + '</button></div>';
135
+ }
136
+
137
+ function triageVerdictHtml(v, escalation) {
138
+ var meta = modelName(v.model) + ' in ' + fmtMs(v.latencyMs);
139
+ if (v.classification === 'AUTO_RESOLVE' && v.autoPatchApplied) {
140
+ var patch = (v.resolution && v.resolution.patch) || (escalation.proposedPatch && escalation.proposedPatch.candidate);
141
+ var unblocked = v.unblockedTaskId || (escalation.resolution && escalation.resolution.unblockedTaskId);
142
+ return '<p class="t1-banner is-resolved" role="status">' + ICON.spark + esc('Auto-Resolved by Tier 1 AI Strategist (' + meta + ')') + '</p>' +
143
+ (patch ? '<section class="heal-section"><h4>Applied contract modification</h4><pre class="heal-patch">' + patchLine('+', patch) + '</pre></section>' : '') +
144
+ '<p class="heal-diag"><strong>Auto-unblocking:</strong> ' +
145
+ esc(unblocked ? 'Task ' + unblocked + ' is back in the queue and can be dispatched again.' : 'The task was not blocked, so nothing needed unblocking.') +
146
+ (v.reasoning ? ' ' + esc(v.reasoning) : '') + '</p>';
147
+ }
148
+ if (v.classification === 'AUTO_RESOLVE') {
149
+ var auto = !!(pipe.triage && pipe.triage.autoTriageEnabled);
150
+ var next = escalation.proposedPatch
151
+ ? 'Nothing was written yet. Approve the proposal below' + (auto ? '.' : ', or turn on Autonomous Auto-Triage and run triage again.')
152
+ : 'No contract change is needed. Approve below to unblock the task.';
153
+ return '<p class="t1-banner is-safe" role="status">' + ICON.spark + esc('Tier 1 AI Strategist: safe to resolve (' + meta + ')') + '</p>' +
154
+ '<p class="heal-diag">' + esc((v.reasoning ? v.reasoning + ' ' : '') + next) + '</p>';
155
+ }
156
+ return humanCardHtml(v, escalation, meta);
157
+ }
158
+
159
+ /** ui_specs.md §5.2: amber 4-part card. Options are selectable; the footer commits the choice. */
160
+ function humanCardHtml(v, escalation, meta) {
161
+ var card = v.humanCard || {};
162
+ var options = Array.isArray(card.options) ? card.options : [];
163
+ var recorded = escalation.humanDecision || null;
164
+ if (t1Choice.escalationId !== escalation.id) {
165
+ var pick = 0;
166
+ options.some(function (o, i) { if (o.recommended) { pick = i; return true; } return false; });
167
+ if (recorded) options.some(function (o, i) { if (o.id === recorded.optionId) { pick = i; return true; } return false; });
168
+ t1Choice = { escalationId: escalation.id, index: pick, custom: recorded && recorded.instructions ? recorded.instructions : '' };
169
+ }
170
+ var buttons = options.map(function (o, i) {
171
+ var label = String(o.label || 'Option ' + (i + 1));
172
+ var tag = o.recommended && !/recommended/i.test(label) ? ' (Recommended)' : '';
173
+ return '<button class="t1-opt' + (o.recommended ? ' is-recommended' : '') + '" type="button" role="radio" aria-checked="' + (t1Choice.index === i) + '"' +
174
+ ' tabindex="' + (t1Choice.index === i ? '0' : '-1') + '" data-t1-opt="' + i + '"><b>' + esc(label + tag) + '</b><span>' + esc(o.outcome || o.description || '') + '</span></button>';
175
+ }).join('');
176
+ return '<article class="t1-card" aria-label="Human decision required">' +
177
+ '<div class="t1-card-head"><span class="t1-badge">Human Decision Required</span><span class="heal-tag">' + esc(escalation.id) + '</span><span class="t1-meta">' + esc(meta) + '</span></div>' +
178
+ '<ol class="t1-parts">' +
179
+ '<li><h4>What is Happening?</h4><p>' + esc(card.symptom || escalation.summary || '') + '</p></li>' +
180
+ '<li><h4>Why is This Happening?</h4><p>' + esc(card.rootCause || '') + '</p></li>' +
181
+ '<li><h4>Who &amp; What is Affected?</h4><p>' + esc(card.blastRadius || '') + '</p></li>' +
182
+ '<li><h4>Actionable Options &amp; Trade-offs</h4>' +
183
+ '<div class="t1-options" role="radiogroup" aria-label="Decision options">' + buttons + '</div>' +
184
+ '<label class="t1-custom" for="t1-custom">Custom instructions (optional, used instead of the option above)' +
185
+ '<textarea id="t1-custom" maxlength="900" rows="2" placeholder="Tell the agent exactly what to do instead">' + esc(t1Choice.custom) + '</textarea></label>' +
186
+ (recorded ? '<p class="t1-decided">Recorded from the terminal: ' + esc(recorded.label || recorded.optionId) + '</p>' : '') +
187
+ '</li>' +
188
+ '</ol>' +
189
+ '</article>';
190
+ }
191
+
192
+ function decisionActions(escalation) {
193
+ return '<div class="heal-actions"><button class="rc-btn reject" type="button" data-heal="t1-dismiss">Dismiss Escalation</button>' +
194
+ '<button class="rc-btn approve" type="button" data-heal="t1-decide">' + (escalation.proposedPatch ? 'Apply Proposal &amp; Unblock' : 'Unblock with This Decision') + '</button></div>';
195
+ }
196
+
197
+ /** Sends the card choice (or custom instructions) as the resolution notes the unblocked agent reads. */
198
+ function commitDecision(decision) {
199
+ var escalation = rc.taskId ? escalationFor(rc.taskId) : null;
200
+ var tri = triageFor(escalation, rc.heal && rc.heal.taskId === rc.taskId ? rc.heal : null);
201
+ if (!escalation || !tri) return;
202
+ var options = (tri.verdict.humanCard && tri.verdict.humanCard.options) || [];
203
+ var chosen = options[t1Choice.index];
204
+ var custom = String(t1Choice.custom || '').trim();
205
+ var notes = custom ? 'Product owner instructions: ' + custom
206
+ : chosen ? 'Product owner chose "' + (chosen.label || chosen.id) + '": ' + (chosen.outcome || chosen.description || '') : '';
207
+ resolveEscalation(decision, notes.slice(0, 1000), decision === 'approve' ? 't1-decide' : 't1-dismiss');
208
+ }
209
+
210
+ `;
@@ -0,0 +1 @@
1
+ export declare const consoleCss: string;