agent-nuvira 2.7.0 → 2.7.2

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 (114) hide show
  1. package/dist/agents/orchestrator.d.ts.map +1 -1
  2. package/dist/agents/orchestrator.js +17 -3
  3. package/dist/agents/orchestrator.js.map +1 -1
  4. package/dist/app.js +26 -0
  5. package/dist/cli/chat.d.ts.map +1 -1
  6. package/dist/cli/chat.js +108 -2
  7. package/dist/cli/chat.js.map +1 -1
  8. package/dist/config/auth.js +18 -0
  9. package/dist/config/jwt.js +24 -0
  10. package/dist/config/keys.js +14 -0
  11. package/dist/gateway/chat-store.d.ts +32 -5
  12. package/dist/gateway/chat-store.d.ts.map +1 -1
  13. package/dist/gateway/chat-store.js +52 -6
  14. package/dist/gateway/chat-store.js.map +1 -1
  15. package/dist/gateway/registry.d.ts.map +1 -1
  16. package/dist/gateway/registry.js +54 -10
  17. package/dist/gateway/registry.js.map +1 -1
  18. package/dist/inference/model-validator.d.ts.map +1 -1
  19. package/dist/inference/model-validator.js +11 -1
  20. package/dist/inference/model-validator.js.map +1 -1
  21. package/dist/inference/tool-call-utils.d.ts +31 -0
  22. package/dist/inference/tool-call-utils.d.ts.map +1 -1
  23. package/dist/inference/tool-call-utils.js +41 -0
  24. package/dist/inference/tool-call-utils.js.map +1 -1
  25. package/dist/learning/reasoning-trace.d.ts +5 -3
  26. package/dist/learning/reasoning-trace.d.ts.map +1 -1
  27. package/dist/learning/reasoning-trace.js +2 -2
  28. package/dist/learning/reasoning-trace.js.map +1 -1
  29. package/dist/middleware/auth.js +30 -0
  30. package/dist/nlu/conversation-gate.js +1 -1
  31. package/dist/nlu/conversation-gate.js.map +1 -1
  32. package/dist/nlu/entities.d.ts.map +1 -1
  33. package/dist/nlu/entities.js +12 -0
  34. package/dist/nlu/entities.js.map +1 -1
  35. package/dist/nlu/intent.d.ts.map +1 -1
  36. package/dist/nlu/intent.js +83 -15
  37. package/dist/nlu/intent.js.map +1 -1
  38. package/dist/passport.js +34 -0
  39. package/dist/routes/auth.js +42 -0
  40. package/dist/routes/user.js +31 -0
  41. package/dist/server.js +17 -0
  42. package/dist/tools/pipeline-tool.d.ts.map +1 -1
  43. package/dist/tools/pipeline-tool.js +14 -0
  44. package/dist/tools/pipeline-tool.js.map +1 -1
  45. package/dist/tools/release-sync.d.ts +52 -16
  46. package/dist/tools/release-sync.d.ts.map +1 -1
  47. package/dist/tools/release-sync.js +143 -79
  48. package/dist/tools/release-sync.js.map +1 -1
  49. package/dist/web-dashboard/src/App.js +85 -0
  50. package/dist/web-dashboard/src/admin-auth.test.js +186 -0
  51. package/dist/web-dashboard/src/ansi.js +23 -0
  52. package/dist/web-dashboard/src/ansi.test.js +31 -0
  53. package/dist/web-dashboard/src/api-admin-auth.test.js +172 -0
  54. package/dist/web-dashboard/src/api-admin.test.js +65 -0
  55. package/dist/web-dashboard/src/api-hub.test.js +117 -0
  56. package/dist/web-dashboard/src/api.js +1421 -0
  57. package/dist/web-dashboard/src/api.test.js +51 -0
  58. package/dist/web-dashboard/src/artifacts.js +128 -0
  59. package/dist/web-dashboard/src/artifacts.test.js +139 -0
  60. package/dist/web-dashboard/src/components/AdminPanel.js +567 -0
  61. package/dist/web-dashboard/src/components/AdminPanel.test.js +288 -0
  62. package/dist/web-dashboard/src/components/AgentHub.js +1580 -0
  63. package/dist/web-dashboard/src/components/AgentHub.test.js +343 -0
  64. package/dist/web-dashboard/src/components/BedrockOnboarding.js +320 -0
  65. package/dist/web-dashboard/src/components/BenchmarkCharts.js +228 -0
  66. package/dist/web-dashboard/src/components/ChatPage.js +1586 -0
  67. package/dist/web-dashboard/src/components/ChatPage.test.js +899 -0
  68. package/dist/web-dashboard/src/components/ContactsPage.js +141 -0
  69. package/dist/web-dashboard/src/components/CostDashboard.js +105 -0
  70. package/dist/web-dashboard/src/components/DAGView.js +477 -0
  71. package/dist/web-dashboard/src/components/DAGView.test.js +147 -0
  72. package/dist/web-dashboard/src/components/EnvVarEditor.js +138 -0
  73. package/dist/web-dashboard/src/components/EvalsPage.js +73 -0
  74. package/dist/web-dashboard/src/components/EvalsPage.test.js +120 -0
  75. package/dist/web-dashboard/src/components/ExecutionHistory.js +201 -0
  76. package/dist/web-dashboard/src/components/GatewayPage.js +40 -0
  77. package/dist/web-dashboard/src/components/GatewayPage.test.js +74 -0
  78. package/dist/web-dashboard/src/components/HealthPanel.js +68 -0
  79. package/dist/web-dashboard/src/components/HistoryBrowser.js +34 -0
  80. package/dist/web-dashboard/src/components/Layout.js +50 -0
  81. package/dist/web-dashboard/src/components/Markdown.js +152 -0
  82. package/dist/web-dashboard/src/components/Markdown.test.js +126 -0
  83. package/dist/web-dashboard/src/components/MarkdownZeroDep.js +237 -0
  84. package/dist/web-dashboard/src/components/MemoryPanel.js +81 -0
  85. package/dist/web-dashboard/src/components/ModelTimeline.js +298 -0
  86. package/dist/web-dashboard/src/components/ModelsPanel.js +1484 -0
  87. package/dist/web-dashboard/src/components/ModelsPanel.test.js +460 -0
  88. package/dist/web-dashboard/src/components/Overview.js +123 -0
  89. package/dist/web-dashboard/src/components/PhaseTimeline.js +359 -0
  90. package/dist/web-dashboard/src/components/PhaseTimeline.test.js +234 -0
  91. package/dist/web-dashboard/src/components/PlatformConfigSection.js +384 -0
  92. package/dist/web-dashboard/src/components/PlatformConfigSection.test.js +106 -0
  93. package/dist/web-dashboard/src/components/PlatformsPage.js +250 -0
  94. package/dist/web-dashboard/src/components/QuotaPanel.js +159 -0
  95. package/dist/web-dashboard/src/components/QuotaPanel.test.js +85 -0
  96. package/dist/web-dashboard/src/components/RequestsPanel.js +229 -0
  97. package/dist/web-dashboard/src/components/RequestsPanel.test.js +103 -0
  98. package/dist/web-dashboard/src/components/RoutingInsightsPanel.js +938 -0
  99. package/dist/web-dashboard/src/components/RoutingInsightsPanel.test.js +339 -0
  100. package/dist/web-dashboard/src/components/RoutingWalkthrough.js +408 -0
  101. package/dist/web-dashboard/src/components/RoutingWalkthrough.test.js +209 -0
  102. package/dist/web-dashboard/src/components/TaskConsole.js +119 -0
  103. package/dist/web-dashboard/src/components/TaskConsole.test.js +123 -0
  104. package/dist/web-dashboard/src/components/TasksPage.js +256 -0
  105. package/dist/web-dashboard/src/components/TasksPage.test.js +103 -0
  106. package/dist/web-dashboard/src/components/TracePanel.js +306 -0
  107. package/dist/web-dashboard/src/components/WhatsAppPanel.js +195 -0
  108. package/dist/web-dashboard/src/components/WhatsAppPanel.test.js +122 -0
  109. package/dist/web-dashboard/src/jsonOrNull.js +37 -0
  110. package/dist/web-dashboard/src/main.js +15 -0
  111. package/dist/web-dashboard/src/mask.js +11 -0
  112. package/dist/web-dashboard/vite.config.js +23 -0
  113. package/dist/web-dashboard/vitest.config.js +15 -0
  114. package/package.json +5 -3
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * Environment Variable Editor — Manages API keys for marketplace skills.
4
+ *
5
+ * This component allows users to:
6
+ * - View all configured environment variables
7
+ * - Add new environment variables
8
+ * - Edit existing values
9
+ * - Delete variables
10
+ * - Test if variables are set correctly
11
+ * - Import/export variable sets
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.EnvVarEditor = EnvVarEditor;
15
+ const react_1 = require("react");
16
+ // ─── Component ───────────────────────────────────────────────────────────
17
+ function EnvVarEditor({ envVars, onSave, onDelete, onTest, readOnly = false, }) {
18
+ const [editingVar, setEditingVar] = (0, react_1.useState)(null);
19
+ const [editValue, setEditValue] = (0, react_1.useState)('');
20
+ const [showAdd, setShowAdd] = (0, react_1.useState)(false);
21
+ const [newName, setNewName] = (0, react_1.useState)('');
22
+ const [newValue, setNewValue] = (0, react_1.useState)('');
23
+ const [testingVar, setTestingVar] = (0, react_1.useState)(null);
24
+ const [testResults, setTestResults] = (0, react_1.useState)({});
25
+ // Sort: required first, then by name
26
+ const sortedVars = [...envVars].sort((a, b) => {
27
+ if (a.requiredBy && !b.requiredBy)
28
+ return -1;
29
+ if (!a.requiredBy && b.requiredBy)
30
+ return 1;
31
+ return a.name.localeCompare(b.name);
32
+ });
33
+ const handleSave = (name) => {
34
+ onSave(name, editValue);
35
+ setEditingVar(null);
36
+ setEditValue('');
37
+ };
38
+ const handleAdd = () => {
39
+ if (newName && newValue) {
40
+ onSave(newName, newValue);
41
+ setNewName('');
42
+ setNewValue('');
43
+ setShowAdd(false);
44
+ }
45
+ };
46
+ const handleTest = async (name) => {
47
+ if (!onTest)
48
+ return;
49
+ setTestingVar(name);
50
+ try {
51
+ const result = await onTest(name);
52
+ setTestResults((prev) => ({ ...prev, [name]: result }));
53
+ }
54
+ catch {
55
+ setTestResults((prev) => ({ ...prev, [name]: false }));
56
+ }
57
+ finally {
58
+ setTestingVar(null);
59
+ }
60
+ };
61
+ return (<div className="env-var-editor">
62
+ <div className="env-var-header">
63
+ <h3 className="env-var-title">🔐 Environment Variables</h3>
64
+ {!readOnly && (<button className="admin-mini-btn" type="button" onClick={() => setShowAdd(!showAdd)}>
65
+ {showAdd ? '✕ Cancel' : '+ Add Variable'}
66
+ </button>)}
67
+ </div>
68
+
69
+ {/* Add new variable form */}
70
+ {showAdd && !readOnly && (<div className="env-var-add-form">
71
+ <input className="env-var-input" type="text" placeholder="Variable name (e.g., MY_API_KEY)" value={newName} onChange={(e) => setNewName(e.target.value)}/>
72
+ <input className="env-var-input" type="password" placeholder="Value" value={newValue} onChange={(e) => setNewValue(e.target.value)}/>
73
+ <button className="admin-refresh-btn" type="button" disabled={!newName || !newValue} onClick={handleAdd}>
74
+ 💾 Save
75
+ </button>
76
+ </div>)}
77
+
78
+ {/* Variable list */}
79
+ <div className="env-var-list">
80
+ {sortedVars.length === 0 ? (<div className="env-var-empty">
81
+ No environment variables configured. Add one to get started.
82
+ </div>) : (sortedVars.map((envVar) => (<div key={envVar.name} className={`env-var-row ${envVar.isSet ? 'env-var-set' : 'env-var-missing'} ${envVar.isProviderCredential ? 'env-var-blocked' : ''}`}>
83
+ <div className="env-var-row-header">
84
+ <span className="env-var-name">
85
+ {envVar.name}
86
+ {envVar.requiredBy && (<span className="env-var-required-badge">
87
+ required by {envVar.requiredBy}
88
+ </span>)}
89
+ {envVar.isProviderCredential && (<span className="env-var-blocked-badge">
90
+ 🔒 provider credential (blocked)
91
+ </span>)}
92
+ </span>
93
+ <span className="env-var-status">
94
+ {envVar.isSet ? '✅ Set' : '❌ Not set'}
95
+ </span>
96
+ </div>
97
+
98
+ {envVar.description && (<div className="env-var-description">{envVar.description}</div>)}
99
+
100
+ {editingVar === envVar.name ? (<div className="env-var-edit">
101
+ <input className="env-var-input" type="password" value={editValue} onChange={(e) => setEditValue(e.target.value)} placeholder="Enter new value..."/>
102
+ <button className="admin-refresh-btn" type="button" onClick={() => handleSave(envVar.name)}>
103
+ 💾 Save
104
+ </button>
105
+ <button className="admin-mini-btn" type="button" onClick={() => setEditingVar(null)}>
106
+ Cancel
107
+ </button>
108
+ </div>) : (<div className="env-var-actions">
109
+ {!readOnly && !envVar.isProviderCredential && (<button className="admin-mini-btn" type="button" onClick={() => {
110
+ setEditingVar(envVar.name);
111
+ setEditValue('');
112
+ }}>
113
+ ✏️ Edit
114
+ </button>)}
115
+ {onTest && (<button className="admin-mini-btn" type="button" disabled={testingVar === envVar.name} onClick={() => handleTest(envVar.name)}>
116
+ {testingVar === envVar.name ? '⏳ Testing...' : '🧪 Test'}
117
+ </button>)}
118
+ {!readOnly && (<button className="admin-mini-btn admin-mini-btn-danger" type="button" onClick={() => onDelete(envVar.name)}>
119
+ 🗑️ Delete
120
+ </button>)}
121
+ {testResults[envVar.name] !== undefined && (<span className={`env-var-test-result ${testResults[envVar.name] ? 'env-var-test-pass' : 'env-var-test-fail'}`}>
122
+ {testResults[envVar.name] ? '✅ Valid' : '❌ Invalid'}
123
+ </span>)}
124
+ </div>)}
125
+ </div>)))}
126
+ </div>
127
+
128
+ {/* Help text */}
129
+ <div className="env-var-help">
130
+ <p>
131
+ <strong>Tip:</strong> Environment variables are stored in <code>~/.nuvira/.env</code> and
132
+ injected into skill executions. Provider credentials (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
133
+ are automatically blocked from skill execution for security.
134
+ </p>
135
+ </div>
136
+ </div>);
137
+ }
138
+ exports.default = EnvVarEditor;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * EvalsPage — P2 eval runner in the GUI (GUI parity with `buff eval run`).
4
+ *
5
+ * Runs the REAL `buff eval run` through the task runner (shared TaskConsole),
6
+ * with live output and cancel. Past runs come from the dashboard data feed
7
+ * (evals.json, auto-refreshed every 10s), so results appear in the table as
8
+ * soon as a run finishes.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.default = EvalsPage;
15
+ const TaskConsole_1 = __importDefault(require("./TaskConsole"));
16
+ const PRESETS = [
17
+ { label: '⚡ Quick smoke', args: ['eval', 'run', '--tasks', 'quick', '--format', 'text'] },
18
+ { label: '⏱ Medium suite', args: ['eval', 'run', '--tasks', 'medium', '--format', 'text'] },
19
+ { label: '🐢 Slow suite', args: ['eval', 'run', '--tasks', 'slow', '--format', 'text'] },
20
+ { label: '🏆 M2B parity', args: ['eval', 'run', '--suite', 'm2b', '--format', 'text'] },
21
+ { label: '📦 Full suite', args: ['eval', 'run', '--suite', 'full', '--format', 'text'] },
22
+ ];
23
+ function fmtPct(v) {
24
+ if (typeof v !== 'number')
25
+ return '—';
26
+ return `${Math.round(v * 100)}%`;
27
+ }
28
+ function fmtDate(at) {
29
+ return new Date(at).toLocaleString();
30
+ }
31
+ function EvalsPage({ data }) {
32
+ const runs = data?.evals?.runs ?? [];
33
+ return (<div className="panel">
34
+ <div className="panel-header">
35
+ <h2>🏆 Evals — run buff eval from the GUI</h2>
36
+ </div>
37
+
38
+ <TaskConsole_1.default presets={PRESETS} customPlaceholder="e.g. eval run --tasks my-task --provider groq --model llama" timeoutMs={900_000} hint={<>
39
+ Runs the real <code>buff eval run</code> as an isolated process — same engine, same ledger, same results
40
+ as the CLI. Long suites can take minutes; you can cancel mid-run.
41
+ </>}/>
42
+
43
+ <h3 className="section-subtitle">📜 Past runs (auto-refreshes)</h3>
44
+ {runs.length > 0 ? (<div className="admin-table-wrapper">
45
+ <table className="admin-table">
46
+ <thead>
47
+ <tr>
48
+ <th>Started</th>
49
+ <th>Provider</th>
50
+ <th>Model</th>
51
+ <th>Tasks</th>
52
+ <th>Pass</th>
53
+ <th>Completion</th>
54
+ <th>Composite</th>
55
+ <th>Cost</th>
56
+ </tr>
57
+ </thead>
58
+ <tbody>
59
+ {runs.map((r) => (<tr key={r.id}>
60
+ <td>{fmtDate(r.startedAt)}</td>
61
+ <td className="admin-provider-type">{r.provider}</td>
62
+ <td>{r.model}</td>
63
+ <td>{r.summary.totalTasks}</td>
64
+ <td>{r.summary.tasksPassed}/{r.summary.totalTasks}</td>
65
+ <td>{fmtPct(r.summary.completionRate)}</td>
66
+ <td>{fmtPct(r.summary.avgCompositeScore)}</td>
67
+ <td>${(r.summary.totalCostUsd ?? 0).toFixed(4)}</td>
68
+ </tr>))}
69
+ </tbody>
70
+ </table>
71
+ </div>) : (<div className="empty-state">No eval runs yet — start one above (or `buff eval run` from the CLI).</div>)}
72
+ </div>);
73
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * P2 — EvalsPage tests (eval runner in the GUI).
4
+ *
5
+ * The page mounts the shared TaskConsole with the `buff eval run` presets and
6
+ * renders past runs from the dashboard data feed. Page-specific behavior is
7
+ * covered here; the console's run/stream/cancel mechanics live in
8
+ * TaskConsole.test.tsx.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const vitest_1 = require("vitest");
15
+ const react_1 = require("@testing-library/react");
16
+ const EvalsPage_1 = __importDefault(require("./EvalsPage"));
17
+ const api_1 = require("../api");
18
+ const DATA = {
19
+ evals: {
20
+ totalRuns: 2,
21
+ latest: null,
22
+ runs: [
23
+ {
24
+ id: 'r1',
25
+ provider: 'groq',
26
+ model: 'llama-3.3-70b',
27
+ startedAt: 1700000000000,
28
+ summary: {
29
+ totalTasks: 4, tasksPassed: 3, completionRate: 0.75, testPassRate: 0.8,
30
+ avgTimeToFixMs: 1000, avgEditAccuracy: 0.9, avgTokenEfficiency: 0.5,
31
+ totalRollbacks: 1, dependencyInstallRate: 1, recoveryRate: 0.9,
32
+ avgCompositeScore: 0.72, totalCostUsd: 0.012,
33
+ },
34
+ },
35
+ ],
36
+ },
37
+ serverTime: 1,
38
+ };
39
+ const TASK = {
40
+ id: 'task-1',
41
+ command: 'eval run --tasks quick --format text',
42
+ args: ['eval', 'run', '--tasks', 'quick', '--format', 'text'],
43
+ cwd: '/tmp',
44
+ status: 'running',
45
+ exitCode: null,
46
+ startedAt: Date.now(),
47
+ finishedAt: null,
48
+ durationMs: null,
49
+ timeoutMs: 900000,
50
+ logs: [],
51
+ };
52
+ function mockAuthed(authenticated = true, role = 'admin') {
53
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'fetchAdminAuthStatus').mockResolvedValue({
54
+ configured: true,
55
+ authenticated,
56
+ user: authenticated ? 'admin' : null,
57
+ role: authenticated ? role : null,
58
+ });
59
+ }
60
+ function mockRunApi() {
61
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'startTask').mockResolvedValue({ ok: true, task: TASK });
62
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'getTask').mockResolvedValue({ status: 200, task: TASK });
63
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'subscribeTask').mockReturnValue(() => { });
64
+ }
65
+ (0, vitest_1.afterEach)(() => {
66
+ (0, react_1.cleanup)();
67
+ vitest_1.vi.restoreAllMocks();
68
+ (0, api_1.setAdminToken)(null);
69
+ });
70
+ (0, vitest_1.describe)('EvalsPage', () => {
71
+ (0, vitest_1.it)('gates running behind the admin session', async () => {
72
+ mockAuthed(false);
73
+ (0, react_1.render)(<EvalsPage_1.default data={DATA}/>);
74
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Log in \(admin or operator\)/)).toBeTruthy());
75
+ (0, vitest_1.expect)(react_1.screen.queryByRole('button', { name: /Quick smoke/ })).toBeNull();
76
+ });
77
+ (0, vitest_1.it)('renders past runs from the data feed', async () => {
78
+ mockAuthed();
79
+ (0, react_1.render)(<EvalsPage_1.default data={DATA}/>);
80
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText('groq')).toBeTruthy());
81
+ (0, vitest_1.expect)(react_1.screen.getByText('llama-3.3-70b')).toBeTruthy();
82
+ (0, vitest_1.expect)(react_1.screen.getByText('3/4')).toBeTruthy();
83
+ (0, vitest_1.expect)(react_1.screen.getByText('72%')).toBeTruthy();
84
+ });
85
+ (0, vitest_1.it)('a preset button starts the right eval task with the eval timeout', async () => {
86
+ mockAuthed();
87
+ mockRunApi();
88
+ const start = vitest_1.vi.mocked(api_1.dashboardAPI.startTask);
89
+ (0, react_1.render)(<EvalsPage_1.default data={DATA}/>);
90
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('button', { name: /Quick smoke/ })).toBeTruthy());
91
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Quick smoke/ }));
92
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(start).toHaveBeenCalledWith(['eval', 'run', '--tasks', 'quick', '--format', 'text'], 900_000));
93
+ });
94
+ (0, vitest_1.it)('the custom input runs a full eval command line', async () => {
95
+ mockAuthed();
96
+ mockRunApi();
97
+ const start = vitest_1.vi.mocked(api_1.dashboardAPI.startTask);
98
+ (0, react_1.render)(<EvalsPage_1.default data={DATA}/>);
99
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByPlaceholderText(/eval run/)).toBeTruthy());
100
+ react_1.fireEvent.change(react_1.screen.getByPlaceholderText(/eval run/), { target: { value: 'eval run --tasks my-task --format text' } });
101
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: 'Run' }));
102
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(start).toHaveBeenCalledWith(['eval', 'run', '--tasks', 'my-task', '--format', 'text'], 900_000));
103
+ });
104
+ (0, vitest_1.it)('streams the live console and offers cancel while running', async () => {
105
+ mockAuthed();
106
+ mockRunApi();
107
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'getTask').mockResolvedValue({
108
+ status: 200,
109
+ task: { ...TASK, logs: [{ stream: 'stdout', text: 'task 1/4 started', at: Date.now() }] },
110
+ });
111
+ const cancel = vitest_1.vi.spyOn(api_1.dashboardAPI, 'cancelTask').mockResolvedValue({ ok: true });
112
+ (0, react_1.render)(<EvalsPage_1.default data={DATA}/>);
113
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('button', { name: /Quick smoke/ })).toBeTruthy());
114
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Quick smoke/ }));
115
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText('task 1/4 started')).toBeTruthy());
116
+ (0, vitest_1.expect)(react_1.screen.getByRole('button', { name: /Cancel/ })).toBeTruthy();
117
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Cancel/ }));
118
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(cancel).toHaveBeenCalledWith('task-1'));
119
+ });
120
+ });
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ /**
3
+ * Execution History — View past skill executions in the dashboard.
4
+ *
5
+ * This component shows:
6
+ * - List of all skill executions
7
+ * - Filter by skill name, status, runtime
8
+ * - View execution details (stdout, stderr, duration)
9
+ * - Export execution history
10
+ * - Clear history
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ExecutionHistory = ExecutionHistory;
14
+ const react_1 = require("react");
15
+ // ─── Component ───────────────────────────────────────────────────────────
16
+ function ExecutionHistory({ onFetch, onClear, onExport, }) {
17
+ const [entries, setEntries] = (0, react_1.useState)([]);
18
+ const [loading, setLoading] = (0, react_1.useState)(false);
19
+ const [filterSkill, setFilterSkill] = (0, react_1.useState)('');
20
+ const [filterStatus, setFilterStatus] = (0, react_1.useState)('');
21
+ const [selectedEntry, setSelectedEntry] = (0, react_1.useState)(null);
22
+ const [stats, setStats] = (0, react_1.useState)(null);
23
+ // Fetch entries on mount and when filters change
24
+ (0, react_1.useEffect)(() => {
25
+ fetchEntries();
26
+ }, [filterSkill, filterStatus]);
27
+ const fetchEntries = async () => {
28
+ setLoading(true);
29
+ try {
30
+ const fetched = await onFetch({
31
+ skillName: filterSkill || undefined,
32
+ status: filterStatus || undefined,
33
+ limit: 100,
34
+ });
35
+ setEntries(fetched);
36
+ // Calculate stats
37
+ const total = fetched.length;
38
+ const success = fetched.filter((e) => e.status === 'success').length;
39
+ const failure = fetched.filter((e) => e.status === 'failure').length;
40
+ const avgDuration = total > 0
41
+ ? Math.round(fetched.reduce((sum, e) => sum + e.durationMs, 0) / total)
42
+ : 0;
43
+ setStats({ total, success, failure, avgDuration });
44
+ }
45
+ catch (err) {
46
+ console.error('Failed to fetch execution history:', err);
47
+ }
48
+ finally {
49
+ setLoading(false);
50
+ }
51
+ };
52
+ const handleExport = async (format) => {
53
+ if (!onExport)
54
+ return;
55
+ try {
56
+ const data = await onExport(format);
57
+ const blob = new Blob([data], { type: format === 'json' ? 'application/json' : 'text/csv' });
58
+ const url = URL.createObjectURL(blob);
59
+ const a = document.createElement('a');
60
+ a.href = url;
61
+ a.download = `execution-history.${format}`;
62
+ a.click();
63
+ URL.revokeObjectURL(url);
64
+ }
65
+ catch (err) {
66
+ console.error('Failed to export history:', err);
67
+ }
68
+ };
69
+ const handleClear = async () => {
70
+ if (!onClear)
71
+ return;
72
+ if (!confirm('Are you sure you want to clear all execution history?'))
73
+ return;
74
+ try {
75
+ await onClear();
76
+ setEntries([]);
77
+ setStats(null);
78
+ }
79
+ catch (err) {
80
+ console.error('Failed to clear history:', err);
81
+ }
82
+ };
83
+ const getStatusIcon = (status) => {
84
+ switch (status) {
85
+ case 'success': return '✅';
86
+ case 'failure': return '❌';
87
+ case 'timeout': return '⏰';
88
+ case 'rejected': return '🚫';
89
+ case 'error': return '💥';
90
+ default: return '❓';
91
+ }
92
+ };
93
+ const getRuntimeIcon = (runtime) => {
94
+ switch (runtime) {
95
+ case 'python': return '🐍';
96
+ case 'node': return '🟢';
97
+ case 'shell': return '🖥️';
98
+ case 'ruby': return '💎';
99
+ case 'go': return '🔵';
100
+ case 'rust': return '🦀';
101
+ default: return '📜';
102
+ }
103
+ };
104
+ return (<div className="execution-history">
105
+ <div className="execution-history-header">
106
+ <h3 className="execution-history-title">📜 Execution History</h3>
107
+ <div className="execution-history-actions">
108
+ <button className="admin-mini-btn" type="button" onClick={() => handleExport('json')}>
109
+ 📥 Export JSON
110
+ </button>
111
+ <button className="admin-mini-btn" type="button" onClick={() => handleExport('csv')}>
112
+ 📥 Export CSV
113
+ </button>
114
+ {onClear && (<button className="admin-mini-btn admin-mini-btn-danger" type="button" onClick={handleClear}>
115
+ 🗑️ Clear
116
+ </button>)}
117
+ </div>
118
+ </div>
119
+
120
+ {/* Stats */}
121
+ {stats && (<div className="execution-history-stats">
122
+ <div className="stat-item">
123
+ <span className="stat-value">{stats.total}</span>
124
+ <span className="stat-label">Total</span>
125
+ </div>
126
+ <div className="stat-item stat-success">
127
+ <span className="stat-value">{stats.success}</span>
128
+ <span className="stat-label">Success</span>
129
+ </div>
130
+ <div className="stat-item stat-failure">
131
+ <span className="stat-value">{stats.failure}</span>
132
+ <span className="stat-label">Failed</span>
133
+ </div>
134
+ <div className="stat-item">
135
+ <span className="stat-value">{stats.avgDuration}ms</span>
136
+ <span className="stat-label">Avg Duration</span>
137
+ </div>
138
+ </div>)}
139
+
140
+ {/* Filters */}
141
+ <div className="execution-history-filters">
142
+ <input className="env-var-input" type="text" placeholder="Filter by skill name..." value={filterSkill} onChange={(e) => setFilterSkill(e.target.value)}/>
143
+ <select className="env-var-input" value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)}>
144
+ <option value="">All statuses</option>
145
+ <option value="success">Success</option>
146
+ <option value="failure">Failure</option>
147
+ <option value="timeout">Timeout</option>
148
+ <option value="rejected">Rejected</option>
149
+ <option value="error">Error</option>
150
+ </select>
151
+ </div>
152
+
153
+ {/* Entry list */}
154
+ <div className="execution-history-list">
155
+ {loading ? (<div className="execution-history-empty">Loading...</div>) : entries.length === 0 ? (<div className="execution-history-empty">
156
+ No execution history found. Execute a skill to see results here.
157
+ </div>) : (entries.map((entry) => (<div key={entry.id} className={`execution-history-entry ${entry.status}`} onClick={() => setSelectedEntry(selectedEntry?.id === entry.id ? null : entry)}>
158
+ <div className="entry-header">
159
+ <span className="entry-icon">{getStatusIcon(entry.status)}</span>
160
+ <span className="entry-skill">{entry.skillName}</span>
161
+ <span className="entry-runtime">{getRuntimeIcon(entry.runtime)} {entry.runtime}</span>
162
+ <span className="entry-duration">{entry.durationMs}ms</span>
163
+ <span className="entry-time">
164
+ {new Date(entry.timestamp).toLocaleString()}
165
+ </span>
166
+ <span className="entry-expand">
167
+ {selectedEntry?.id === entry.id ? '▼' : '▶'}
168
+ </span>
169
+ </div>
170
+
171
+ {selectedEntry?.id === entry.id && (<div className="entry-details">
172
+ <div className="entry-detail-row">
173
+ <span className="detail-label">Status:</span>
174
+ <span className="detail-value">{entry.status}</span>
175
+ </div>
176
+ <div className="entry-detail-row">
177
+ <span className="detail-label">Runtime:</span>
178
+ <span className="detail-value">{entry.runtime}</span>
179
+ </div>
180
+ <div className="entry-detail-row">
181
+ <span className="detail-label">Duration:</span>
182
+ <span className="detail-value">{entry.durationMs}ms</span>
183
+ </div>
184
+ {entry.exitCode !== undefined && (<div className="entry-detail-row">
185
+ <span className="detail-label">Exit Code:</span>
186
+ <span className="detail-value">{entry.exitCode}</span>
187
+ </div>)}
188
+ {entry.error && (<div className="entry-detail-row">
189
+ <span className="detail-label">Error:</span>
190
+ <span className="detail-value entry-error">{entry.error}</span>
191
+ </div>)}
192
+ {entry.command && (<div className="entry-detail-row">
193
+ <span className="detail-label">Command:</span>
194
+ <code className="detail-value">{entry.command}</code>
195
+ </div>)}
196
+ </div>)}
197
+ </div>)))}
198
+ </div>
199
+ </div>);
200
+ }
201
+ exports.default = ExecutionHistory;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * GatewayPage — gateway ops in the GUI (GUI parity with `buff gateway`).
4
+ *
5
+ * Status, delivery ledger, foreground start, and cron management run the REAL
6
+ * CLI commands through the shared TaskConsole (isolated child process + live
7
+ * SSE console + cancel). Note: `gateway start` runs in the foreground — it
8
+ * streams platform events until you cancel it (the console's Cancel button
9
+ * SIGTERMs the process).
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.default = GatewayPage;
16
+ const TaskConsole_1 = __importDefault(require("./TaskConsole"));
17
+ const PRESETS = [
18
+ { label: '🌐 Gateway status', args: ['gateway', 'status'] },
19
+ { label: '📮 Delivery ledger', args: ['gateway', 'delivery'] },
20
+ // timeoutMs: 0 = run forever. A gateway is a server process — the default
21
+ // 5-minute task timeout used to SIGTERM it while the user believed it was
22
+ // still running.
23
+ { label: '▶️ Start gateway (foreground)', args: ['gateway', 'start', '--no-events'], timeoutMs: 0 },
24
+ { label: '⏰ Cron jobs', args: ['admin', 'cron', 'list'] },
25
+ ];
26
+ function GatewayPage() {
27
+ return (<div className="panel">
28
+ <div className="panel-header">
29
+ <h2>🌐 Gateway ops — run buff gateway from the GUI</h2>
30
+ </div>
31
+
32
+ <TaskConsole_1.default presets={PRESETS} customPlaceholder="e.g. admin cron run nightly or gateway alias add ops email:team@example.com" timeoutMs={300_000} hint={<>
33
+ Each command runs as the real CLI in an isolated process (the P1 task runner) — the same engine as your
34
+ terminal. <code>gateway start</code> runs in the foreground <strong>with no timeout</strong>: it keeps running
35
+ even after you switch tabs (the process lives on the dashboard server, not the page) and streams platform
36
+ events until you press <strong>Cancel</strong>. The delivery ledger also has its own view in{' '}
37
+ <strong>Agent Hub → Channels</strong>.
38
+ </>}/>
39
+ </div>);
40
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * P2/P3 — GatewayPage tests (gateway ops in the GUI).
4
+ *
5
+ * The page mounts the shared TaskConsole with `buff gateway` presets. Its own
6
+ * surface is thin (page title + presets); the console mechanics live in
7
+ * TaskConsole.test.tsx.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const vitest_1 = require("vitest");
14
+ const react_1 = require("@testing-library/react");
15
+ const GatewayPage_1 = __importDefault(require("./GatewayPage"));
16
+ const api_1 = require("../api");
17
+ const TASK = {
18
+ id: 'task-1',
19
+ command: 'gateway status',
20
+ args: ['gateway', 'status'],
21
+ cwd: '/tmp',
22
+ status: 'running',
23
+ exitCode: null,
24
+ startedAt: Date.now(),
25
+ finishedAt: null,
26
+ durationMs: null,
27
+ timeoutMs: 300000,
28
+ logs: [],
29
+ };
30
+ function mockAuthed(authenticated = true, role = 'admin') {
31
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'fetchAdminAuthStatus').mockResolvedValue({
32
+ configured: true,
33
+ authenticated,
34
+ user: authenticated ? 'admin' : null,
35
+ role: authenticated ? role : null,
36
+ });
37
+ }
38
+ function mockRunApi() {
39
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'startTask').mockResolvedValue({ ok: true, task: TASK });
40
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'getTask').mockResolvedValue({ status: 200, task: TASK });
41
+ vitest_1.vi.spyOn(api_1.dashboardAPI, 'subscribeTask').mockReturnValue(() => { });
42
+ }
43
+ (0, vitest_1.afterEach)(() => {
44
+ (0, react_1.cleanup)();
45
+ vitest_1.vi.restoreAllMocks();
46
+ (0, api_1.setAdminToken)(null);
47
+ });
48
+ (0, vitest_1.describe)('GatewayPage', () => {
49
+ (0, vitest_1.it)('gates ops behind the admin session', async () => {
50
+ mockAuthed(false);
51
+ (0, react_1.render)(<GatewayPage_1.default />);
52
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Log in \(admin or operator\)/)).toBeTruthy());
53
+ (0, vitest_1.expect)(react_1.screen.queryByRole('button', { name: /Gateway status/ })).toBeNull();
54
+ });
55
+ (0, vitest_1.it)('preset buttons run the real gateway CLI commands', async () => {
56
+ mockAuthed();
57
+ mockRunApi();
58
+ const start = vitest_1.vi.mocked(api_1.dashboardAPI.startTask);
59
+ (0, react_1.render)(<GatewayPage_1.default />);
60
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('button', { name: /Gateway status/ })).toBeTruthy());
61
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Gateway status/ }));
62
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(start).toHaveBeenCalledWith(['gateway', 'status'], 300_000));
63
+ });
64
+ (0, vitest_1.it)('the custom input runs an arbitrary gateway/admin command', async () => {
65
+ mockAuthed();
66
+ mockRunApi();
67
+ const start = vitest_1.vi.mocked(api_1.dashboardAPI.startTask);
68
+ (0, react_1.render)(<GatewayPage_1.default />);
69
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByPlaceholderText(/admin cron run/)).toBeTruthy());
70
+ react_1.fireEvent.change(react_1.screen.getByPlaceholderText(/admin cron run/), { target: { value: 'admin cron run nightly' } });
71
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: 'Run' }));
72
+ await (0, react_1.waitFor)(() => (0, vitest_1.expect)(start).toHaveBeenCalledWith(['admin', 'cron', 'run', 'nightly'], 300_000));
73
+ });
74
+ });