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.
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +17 -3
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/app.js +26 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +108 -2
- package/dist/cli/chat.js.map +1 -1
- package/dist/config/auth.js +18 -0
- package/dist/config/jwt.js +24 -0
- package/dist/config/keys.js +14 -0
- package/dist/gateway/chat-store.d.ts +32 -5
- package/dist/gateway/chat-store.d.ts.map +1 -1
- package/dist/gateway/chat-store.js +52 -6
- package/dist/gateway/chat-store.js.map +1 -1
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +54 -10
- package/dist/gateway/registry.js.map +1 -1
- package/dist/inference/model-validator.d.ts.map +1 -1
- package/dist/inference/model-validator.js +11 -1
- package/dist/inference/model-validator.js.map +1 -1
- package/dist/inference/tool-call-utils.d.ts +31 -0
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +41 -0
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +5 -3
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +2 -2
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/middleware/auth.js +30 -0
- package/dist/nlu/conversation-gate.js +1 -1
- package/dist/nlu/conversation-gate.js.map +1 -1
- package/dist/nlu/entities.d.ts.map +1 -1
- package/dist/nlu/entities.js +12 -0
- package/dist/nlu/entities.js.map +1 -1
- package/dist/nlu/intent.d.ts.map +1 -1
- package/dist/nlu/intent.js +83 -15
- package/dist/nlu/intent.js.map +1 -1
- package/dist/passport.js +34 -0
- package/dist/routes/auth.js +42 -0
- package/dist/routes/user.js +31 -0
- package/dist/server.js +17 -0
- package/dist/tools/pipeline-tool.d.ts.map +1 -1
- package/dist/tools/pipeline-tool.js +14 -0
- package/dist/tools/pipeline-tool.js.map +1 -1
- package/dist/tools/release-sync.d.ts +52 -16
- package/dist/tools/release-sync.d.ts.map +1 -1
- package/dist/tools/release-sync.js +143 -79
- package/dist/tools/release-sync.js.map +1 -1
- package/dist/web-dashboard/src/App.js +85 -0
- package/dist/web-dashboard/src/admin-auth.test.js +186 -0
- package/dist/web-dashboard/src/ansi.js +23 -0
- package/dist/web-dashboard/src/ansi.test.js +31 -0
- package/dist/web-dashboard/src/api-admin-auth.test.js +172 -0
- package/dist/web-dashboard/src/api-admin.test.js +65 -0
- package/dist/web-dashboard/src/api-hub.test.js +117 -0
- package/dist/web-dashboard/src/api.js +1421 -0
- package/dist/web-dashboard/src/api.test.js +51 -0
- package/dist/web-dashboard/src/artifacts.js +128 -0
- package/dist/web-dashboard/src/artifacts.test.js +139 -0
- package/dist/web-dashboard/src/components/AdminPanel.js +567 -0
- package/dist/web-dashboard/src/components/AdminPanel.test.js +288 -0
- package/dist/web-dashboard/src/components/AgentHub.js +1580 -0
- package/dist/web-dashboard/src/components/AgentHub.test.js +343 -0
- package/dist/web-dashboard/src/components/BedrockOnboarding.js +320 -0
- package/dist/web-dashboard/src/components/BenchmarkCharts.js +228 -0
- package/dist/web-dashboard/src/components/ChatPage.js +1586 -0
- package/dist/web-dashboard/src/components/ChatPage.test.js +899 -0
- package/dist/web-dashboard/src/components/ContactsPage.js +141 -0
- package/dist/web-dashboard/src/components/CostDashboard.js +105 -0
- package/dist/web-dashboard/src/components/DAGView.js +477 -0
- package/dist/web-dashboard/src/components/DAGView.test.js +147 -0
- package/dist/web-dashboard/src/components/EnvVarEditor.js +138 -0
- package/dist/web-dashboard/src/components/EvalsPage.js +73 -0
- package/dist/web-dashboard/src/components/EvalsPage.test.js +120 -0
- package/dist/web-dashboard/src/components/ExecutionHistory.js +201 -0
- package/dist/web-dashboard/src/components/GatewayPage.js +40 -0
- package/dist/web-dashboard/src/components/GatewayPage.test.js +74 -0
- package/dist/web-dashboard/src/components/HealthPanel.js +68 -0
- package/dist/web-dashboard/src/components/HistoryBrowser.js +34 -0
- package/dist/web-dashboard/src/components/Layout.js +50 -0
- package/dist/web-dashboard/src/components/Markdown.js +152 -0
- package/dist/web-dashboard/src/components/Markdown.test.js +126 -0
- package/dist/web-dashboard/src/components/MarkdownZeroDep.js +237 -0
- package/dist/web-dashboard/src/components/MemoryPanel.js +81 -0
- package/dist/web-dashboard/src/components/ModelTimeline.js +298 -0
- package/dist/web-dashboard/src/components/ModelsPanel.js +1484 -0
- package/dist/web-dashboard/src/components/ModelsPanel.test.js +460 -0
- package/dist/web-dashboard/src/components/Overview.js +123 -0
- package/dist/web-dashboard/src/components/PhaseTimeline.js +359 -0
- package/dist/web-dashboard/src/components/PhaseTimeline.test.js +234 -0
- package/dist/web-dashboard/src/components/PlatformConfigSection.js +384 -0
- package/dist/web-dashboard/src/components/PlatformConfigSection.test.js +106 -0
- package/dist/web-dashboard/src/components/PlatformsPage.js +250 -0
- package/dist/web-dashboard/src/components/QuotaPanel.js +159 -0
- package/dist/web-dashboard/src/components/QuotaPanel.test.js +85 -0
- package/dist/web-dashboard/src/components/RequestsPanel.js +229 -0
- package/dist/web-dashboard/src/components/RequestsPanel.test.js +103 -0
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.js +938 -0
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.test.js +339 -0
- package/dist/web-dashboard/src/components/RoutingWalkthrough.js +408 -0
- package/dist/web-dashboard/src/components/RoutingWalkthrough.test.js +209 -0
- package/dist/web-dashboard/src/components/TaskConsole.js +119 -0
- package/dist/web-dashboard/src/components/TaskConsole.test.js +123 -0
- package/dist/web-dashboard/src/components/TasksPage.js +256 -0
- package/dist/web-dashboard/src/components/TasksPage.test.js +103 -0
- package/dist/web-dashboard/src/components/TracePanel.js +306 -0
- package/dist/web-dashboard/src/components/WhatsAppPanel.js +195 -0
- package/dist/web-dashboard/src/components/WhatsAppPanel.test.js +122 -0
- package/dist/web-dashboard/src/jsonOrNull.js +37 -0
- package/dist/web-dashboard/src/main.js +15 -0
- package/dist/web-dashboard/src/mask.js +11 -0
- package/dist/web-dashboard/vite.config.js +23 -0
- package/dist/web-dashboard/vitest.config.js +15 -0
- package/package.json +5 -3
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Hub panel tests (I4 + I5).
|
|
4
|
+
*
|
|
5
|
+
* - 4 tabs render from the /api/hub payload (Tools/Channels/Artifacts/Skills).
|
|
6
|
+
* - An authenticated admin/operator can toggle a toolset — the switch calls
|
|
7
|
+
* setToolsetEnabled and the panel re-reads.
|
|
8
|
+
* - An unauthenticated user is routed through the login gate, then the queued
|
|
9
|
+
* toggle is applied.
|
|
10
|
+
* - Reads degrade to a friendly error, never a crash.
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
const vitest_1 = require("vitest");
|
|
17
|
+
const react_1 = require("@testing-library/react");
|
|
18
|
+
const AgentHub_1 = __importDefault(require("./AgentHub"));
|
|
19
|
+
const api_1 = require("../api");
|
|
20
|
+
const HUB = {
|
|
21
|
+
toolsets: {
|
|
22
|
+
toolsets: [
|
|
23
|
+
{ name: 'core', label: 'Core', description: 'Pipeline actions', enabled: true, tools: ['build', 'test'], toolCount: 2 },
|
|
24
|
+
{ name: 'web', label: 'Web research', description: 'Search + page reading', enabled: true, tools: ['web_search', 'read_page'], toolCount: 2 },
|
|
25
|
+
],
|
|
26
|
+
enabled: 2,
|
|
27
|
+
disabled: 0,
|
|
28
|
+
totalTools: 4,
|
|
29
|
+
},
|
|
30
|
+
channels: {
|
|
31
|
+
delivery: {
|
|
32
|
+
total: 1,
|
|
33
|
+
pending: 1,
|
|
34
|
+
sent: 0,
|
|
35
|
+
failed: 0,
|
|
36
|
+
recent: [
|
|
37
|
+
{ id: 'e1', target: 'ops', platform: 'slack', channelId: 'C1', text: 'nightly done', status: 'pending', attempts: 2, nextAttemptAt: Date.now() + 30000, createdAt: Date.now() },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
aliases: [{ alias: 'ops', platform: 'slack', channelId: 'C1', addedAt: Date.now() }],
|
|
41
|
+
reachable: [],
|
|
42
|
+
platforms: [
|
|
43
|
+
{ platform: 'email', label: 'Email (SMTP)', configured: false, envVars: ['BUFF_SMTP_HOST', 'BUFF_SMTP_USER'] },
|
|
44
|
+
{ platform: 'signal', label: 'Signal (signal-cli-rest-api)', configured: true, envVars: ['BUFF_SIGNAL_ACCOUNT'] },
|
|
45
|
+
{ platform: 'whatsapp', label: 'WhatsApp (Baileys bridge)', configured: true, envVars: ['BUFF_WHATSAPP_SESSION_DIR'] },
|
|
46
|
+
],
|
|
47
|
+
policies: {
|
|
48
|
+
whatsapp: { allowedUsers: ['+919876543210', '919999999999'], silentDrop: true },
|
|
49
|
+
},
|
|
50
|
+
contacts: [{ name: 'Alex', platform: 'whatsapp', id: '+919876543210', addedAt: Date.now() }],
|
|
51
|
+
statusRecipients: [],
|
|
52
|
+
statusRecipientDisplay: {},
|
|
53
|
+
inbox: {
|
|
54
|
+
total: 0,
|
|
55
|
+
pipeline: 0,
|
|
56
|
+
chat: 0,
|
|
57
|
+
help: 0,
|
|
58
|
+
refused: 0,
|
|
59
|
+
recent: [],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
artifacts: {
|
|
63
|
+
totalSessions: 1,
|
|
64
|
+
totalArtifacts: 2,
|
|
65
|
+
sessions: [
|
|
66
|
+
{ sessionId: 's1', count: 2, latestAt: Date.now(), recent: [{ kind: 'doc', title: 'deploy report', preview: 'published ok' }] },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
skills: {
|
|
70
|
+
compiled: [
|
|
71
|
+
{ id: 'skill-fix-lint', name: 'Fix lint', description: 'Fixes lint errors', version: '1.0.0', origin: 'compiled', usageCount: 3, bundled: true },
|
|
72
|
+
{ id: 'skill-custom-x', name: 'Custom X', description: 'A user-added skill', version: '1.0.0', origin: 'compiled', usageCount: 0, bundled: false },
|
|
73
|
+
],
|
|
74
|
+
hub: [{ id: 'demo-fix', name: 'demo-fix', description: 'Fix a demo issue', origin: 'hub' }],
|
|
75
|
+
total: 3,
|
|
76
|
+
},
|
|
77
|
+
adminConfigured: true,
|
|
78
|
+
serverTime: 123,
|
|
79
|
+
};
|
|
80
|
+
/** The panel's mount-time fetches (hub + auth status). */
|
|
81
|
+
function mockReads(payload = HUB, auth = { configured: true, authenticated: true, role: 'admin' }) {
|
|
82
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'fetchHub').mockResolvedValue(payload);
|
|
83
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'fetchAdminAuthStatus').mockResolvedValue({
|
|
84
|
+
configured: auth.configured,
|
|
85
|
+
authenticated: auth.authenticated,
|
|
86
|
+
user: auth.authenticated ? 'admin' : null,
|
|
87
|
+
role: auth.authenticated ? (auth.role ?? 'admin') : null,
|
|
88
|
+
});
|
|
89
|
+
// The Channels tab mounts WhatsAppPanel (P2), whose SSE subscription needs
|
|
90
|
+
// a browser EventSource — keep the hub tests hermetic.
|
|
91
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'subscribeWhatsApp').mockReturnValue(() => { });
|
|
92
|
+
// …and PlatformConfigSection (v1.69) fetches the transport list on mount.
|
|
93
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'getPlatformConfigs').mockResolvedValue([]);
|
|
94
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'getWhatsAppStatus').mockResolvedValue({
|
|
95
|
+
status: {
|
|
96
|
+
state: 'idle',
|
|
97
|
+
paired: false,
|
|
98
|
+
sessionDir: '/tmp/wa-session',
|
|
99
|
+
qr: null,
|
|
100
|
+
qrRaw: null,
|
|
101
|
+
pairingCode: null,
|
|
102
|
+
phone: null,
|
|
103
|
+
error: null,
|
|
104
|
+
startedAt: null,
|
|
105
|
+
},
|
|
106
|
+
contacts: {},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
(0, vitest_1.afterEach)(() => {
|
|
110
|
+
(0, react_1.cleanup)();
|
|
111
|
+
vitest_1.vi.restoreAllMocks();
|
|
112
|
+
(0, api_1.setAdminToken)(null);
|
|
113
|
+
});
|
|
114
|
+
(0, vitest_1.describe)('AgentHub', () => {
|
|
115
|
+
(0, vitest_1.it)('renders the 4 tabs with counts from the hub payload', async () => {
|
|
116
|
+
mockReads();
|
|
117
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
118
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
119
|
+
(0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Channels/ })).toBeTruthy();
|
|
120
|
+
(0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Artifacts/ })).toBeTruthy();
|
|
121
|
+
(0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Skills/ })).toBeTruthy();
|
|
122
|
+
// Tab badges carry counts (2/2 toolsets, 1 channel, 1 session, 2 skills).
|
|
123
|
+
(0, vitest_1.expect)(react_1.screen.getByText('2/2')).toBeTruthy();
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)('shows a friendly error when the hub read fails', async () => {
|
|
126
|
+
mockReads(null);
|
|
127
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
128
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Could not reach the dashboard server/)).toBeTruthy());
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.it)('toggles a toolset when authed (admin)', async () => {
|
|
131
|
+
mockReads();
|
|
132
|
+
const setMock = vitest_1.vi.spyOn(api_1.dashboardAPI, 'setToolsetEnabled').mockResolvedValue({ ok: true });
|
|
133
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
134
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
135
|
+
const webSwitch = react_1.screen.getByRole('switch', { name: /Disable Web research/ });
|
|
136
|
+
(0, vitest_1.expect)(webSwitch.disabled).toBe(false);
|
|
137
|
+
react_1.fireEvent.click(webSwitch);
|
|
138
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(setMock).toHaveBeenCalledWith('web', false));
|
|
139
|
+
});
|
|
140
|
+
(0, vitest_1.it)('queues the toggle behind the login gate when not authed, then applies it', async () => {
|
|
141
|
+
mockReads(HUB, { configured: true, authenticated: false });
|
|
142
|
+
const loginMock = vitest_1.vi.spyOn(api_1.dashboardAPI, 'adminLogin').mockResolvedValue({ ok: true, user: 'admin', token: 't' });
|
|
143
|
+
const setMock = vitest_1.vi.spyOn(api_1.dashboardAPI, 'setToolsetEnabled').mockResolvedValue({ ok: true });
|
|
144
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
145
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
146
|
+
// Switch is disabled without auth — clicking queues the toggle.
|
|
147
|
+
const webSwitch = react_1.screen.getByRole('switch', { name: /Disable Web research/ });
|
|
148
|
+
react_1.fireEvent.click(webSwitch);
|
|
149
|
+
// Login form appears (queued toggle named in the prompt).
|
|
150
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Log in to disable 'web'/)).toBeTruthy());
|
|
151
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('admin'), { target: { value: 'admin' } });
|
|
152
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('••••••••'), { target: { value: 'secret-pass' } });
|
|
153
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Log in & apply/ }));
|
|
154
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(loginMock).toHaveBeenCalled());
|
|
155
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(setMock).toHaveBeenCalledWith('web', false));
|
|
156
|
+
});
|
|
157
|
+
(0, vitest_1.it)('I11: authed admin can send a test message from the Channels tab', async () => {
|
|
158
|
+
mockReads();
|
|
159
|
+
const sendMock = vitest_1.vi.spyOn(api_1.dashboardAPI, 'sendChannelMessage').mockResolvedValue({ ok: true, platform: 'slack', channelId: 'C1' });
|
|
160
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
161
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
162
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
163
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Test a channel/)).toBeTruthy());
|
|
164
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('ops or slack:C0123 or email:team@example.com'), { target: { value: 'ops' } });
|
|
165
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('nightly build done 🎉'), { target: { value: 'hello hub' } });
|
|
166
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Send test message/ }));
|
|
167
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(sendMock).toHaveBeenCalledWith('ops', 'hello hub'));
|
|
168
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Sent to ops/)).toBeTruthy());
|
|
169
|
+
});
|
|
170
|
+
(0, vitest_1.it)('I11: a viewer role cannot send — inline error, no API call', async () => {
|
|
171
|
+
mockReads(HUB, { configured: true, authenticated: true, role: 'viewer' });
|
|
172
|
+
const sendMock = vitest_1.vi.spyOn(api_1.dashboardAPI, 'sendChannelMessage');
|
|
173
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
174
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
175
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
176
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Test a channel/)).toBeTruthy());
|
|
177
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('ops or slack:C0123 or email:team@example.com'), { target: { value: 'ops' } });
|
|
178
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('nightly build done 🎉'), { target: { value: 'hello hub' } });
|
|
179
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Send test message/ }));
|
|
180
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/cannot send channel messages/)).toBeTruthy());
|
|
181
|
+
(0, vitest_1.expect)(sendMock).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.it)('I11: an unauthenticated user is told to log in before sending', async () => {
|
|
184
|
+
mockReads(HUB, { configured: true, authenticated: false });
|
|
185
|
+
const sendMock = vitest_1.vi.spyOn(api_1.dashboardAPI, 'sendChannelMessage');
|
|
186
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
187
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
188
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
189
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Test a channel/)).toBeTruthy());
|
|
190
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('ops or slack:C0123 or email:team@example.com'), { target: { value: 'ops' } });
|
|
191
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText('nightly build done 🎉'), { target: { value: 'hello hub' } });
|
|
192
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /Send test message/ }));
|
|
193
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Log in .* to send test messages/)).toBeTruthy());
|
|
194
|
+
(0, vitest_1.expect)(sendMock).not.toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
/** The whatsapp card on the Permissions section (scoped — the label also renders in the transports list). */
|
|
197
|
+
function whatsappCard() {
|
|
198
|
+
const card = react_1.screen
|
|
199
|
+
.getAllByText('WhatsApp (Baileys bridge)')
|
|
200
|
+
.map((el) => el.closest('.hub-card'))
|
|
201
|
+
.find((c) => !!c);
|
|
202
|
+
(0, vitest_1.expect)(card).toBeTruthy();
|
|
203
|
+
return card;
|
|
204
|
+
}
|
|
205
|
+
(0, vitest_1.it)('Permissions: adding a verified user shows the MASKED id (no name, no full number)', async () => {
|
|
206
|
+
mockReads();
|
|
207
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
208
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
209
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
210
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Saved contacts \(validated list\)/)).toBeTruthy());
|
|
211
|
+
const card = whatsappCard();
|
|
212
|
+
react_1.fireEvent.change((0, react_1.within)(card).getByPlaceholderText('Name (optional)'), { target: { value: 'Sam' } });
|
|
213
|
+
react_1.fireEvent.change((0, react_1.within)(card).getByPlaceholderText('Contact no / sender id, or Allow-All'), { target: { value: '+919999999999' } });
|
|
214
|
+
react_1.fireEvent.click((0, react_1.within)(card).getByRole('button', { name: /\+ User/ }));
|
|
215
|
+
// The new chip shows the MASKED sender id with a pending marker — the
|
|
216
|
+
// personal name and the full number must NOT appear anywhere.
|
|
217
|
+
(0, vitest_1.expect)((0, react_1.within)(card).getByText(/\+91\*\*\*.*pending/)).toBeTruthy();
|
|
218
|
+
(0, vitest_1.expect)(react_1.screen.queryByText('Sam')).toBeNull();
|
|
219
|
+
(0, vitest_1.expect)(react_1.screen.queryByText('+919999999999')).toBeNull();
|
|
220
|
+
(0, vitest_1.expect)(react_1.screen.queryByText('919999999999')).toBeNull();
|
|
221
|
+
// The other saved users stay visible (masked too, no pending marker).
|
|
222
|
+
(0, vitest_1.expect)((0, react_1.within)(card).getAllByText(/91\*\*\*/).length).toBeGreaterThan(0);
|
|
223
|
+
});
|
|
224
|
+
(0, vitest_1.it)('Permissions: adding a user shows the UNSAVED banner until Save is pressed', async () => {
|
|
225
|
+
mockReads();
|
|
226
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
227
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
228
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
229
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Saved contacts \(validated list\)/)).toBeTruthy());
|
|
230
|
+
// No unsaved banner before any edit.
|
|
231
|
+
(0, vitest_1.expect)(react_1.screen.queryByText(/Unsaved changes/)).toBeNull();
|
|
232
|
+
const card = whatsappCard();
|
|
233
|
+
react_1.fireEvent.change((0, react_1.within)(card).getByPlaceholderText('Name (optional)'), { target: { value: 'Sam' } });
|
|
234
|
+
react_1.fireEvent.change((0, react_1.within)(card).getByPlaceholderText('Contact no / sender id, or Allow-All'), { target: { value: '+919999999999' } });
|
|
235
|
+
react_1.fireEvent.click((0, react_1.within)(card).getByRole('button', { name: /\+ User/ }));
|
|
236
|
+
// Draft edit → the banner appears, telling the user to press Save.
|
|
237
|
+
(0, vitest_1.expect)(react_1.screen.getByText(/Unsaved changes/)).toBeTruthy();
|
|
238
|
+
// Save persists and clears the banner (click the one INSIDE the banner).
|
|
239
|
+
const save = vitest_1.vi.spyOn(api_1.dashboardAPI, 'saveGatewayPolicies').mockResolvedValue({ ok: true });
|
|
240
|
+
const banner = react_1.screen.getByText(/Unsaved changes/).closest('.hub-unsaved-banner');
|
|
241
|
+
react_1.fireEvent.click((0, react_1.within)(banner).getByRole('button', { name: /Save permissions/ }));
|
|
242
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(save).toHaveBeenCalled());
|
|
243
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.queryByText(/Unsaved changes/)).toBeNull());
|
|
244
|
+
});
|
|
245
|
+
(0, vitest_1.it)('privacy: sender ids are masked by default; the admin toggle reveals full ids', async () => {
|
|
246
|
+
mockReads();
|
|
247
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
248
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
249
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
250
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Delivery ledger/)).toBeTruthy());
|
|
251
|
+
// Masked by default — the full fixture number never appears.
|
|
252
|
+
(0, vitest_1.expect)(react_1.screen.queryByText('+919876543210')).toBeNull();
|
|
253
|
+
(0, vitest_1.expect)(react_1.screen.getAllByText(/\+91\*\*\*/).length).toBeGreaterThan(0);
|
|
254
|
+
// Flip the admin-only toggle → full ids render (both fixture ids show).
|
|
255
|
+
react_1.fireEvent.click(react_1.screen.getByRole('checkbox', { name: /Show full sender ids/ }));
|
|
256
|
+
(0, vitest_1.expect)(react_1.screen.getAllByText('+919876543210').length).toBeGreaterThan(0);
|
|
257
|
+
(0, vitest_1.expect)(react_1.screen.getAllByText('919999999999').length).toBeGreaterThan(0);
|
|
258
|
+
});
|
|
259
|
+
(0, vitest_1.it)('privacy: a viewer cannot toggle full sender ids', async () => {
|
|
260
|
+
mockReads(HUB, { configured: true, authenticated: true, role: 'viewer' });
|
|
261
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
262
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
263
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
264
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Delivery ledger/)).toBeTruthy());
|
|
265
|
+
const toggle = react_1.screen.getByRole('checkbox', { name: /Show full sender ids/ });
|
|
266
|
+
(0, vitest_1.expect)(toggle.disabled).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
(0, vitest_1.it)('Permissions: removing ONE verified user keeps the rest of the saved list', async () => {
|
|
269
|
+
// Regression: the draft is seeded from the SAVED list, so removing one
|
|
270
|
+
// entry must not blank (and on save, silently delete) the others.
|
|
271
|
+
mockReads();
|
|
272
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
273
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
274
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
275
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Saved contacts \(validated list\)/)).toBeTruthy());
|
|
276
|
+
const card = whatsappCard();
|
|
277
|
+
// All ids render MASKED — remove the chip whose masked id is 91***.
|
|
278
|
+
const maskedRows = (0, react_1.within)(card).getAllByText(/91\*\*\*/);
|
|
279
|
+
const row = maskedRows[maskedRows.length - 1].closest('.hub-alias-row');
|
|
280
|
+
react_1.fireEvent.click((0, react_1.within)(row).getByRole('button', { name: '✕' }));
|
|
281
|
+
// The removed id is gone; at least one masked id remains.
|
|
282
|
+
(0, vitest_1.expect)((0, react_1.within)(card).queryByText(/\+91\*\*\*/)).toBeTruthy();
|
|
283
|
+
});
|
|
284
|
+
(0, vitest_1.it)('switches tabs — Channels shows the delivery ledger, Skills shows skills', async () => {
|
|
285
|
+
mockReads();
|
|
286
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
287
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Tools/ })).toBeTruthy());
|
|
288
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Channels/ }));
|
|
289
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Delivery ledger/)).toBeTruthy());
|
|
290
|
+
(0, vitest_1.expect)(react_1.screen.getByText('⏳ pending')).toBeTruthy();
|
|
291
|
+
// Platform transports section shows the I6 adapters (the label also
|
|
292
|
+
// appears as a chip on the Permissions cards below).
|
|
293
|
+
(0, vitest_1.expect)(react_1.screen.getAllByText('Email (SMTP)').length).toBeGreaterThan(0);
|
|
294
|
+
(0, vitest_1.expect)(react_1.screen.getAllByText('Signal (signal-cli-rest-api)').length).toBeGreaterThan(0);
|
|
295
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Artifacts/ }));
|
|
296
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/deploy report/)).toBeTruthy());
|
|
297
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Skills/ }));
|
|
298
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText('Fix lint')).toBeTruthy());
|
|
299
|
+
// 'demo-fix' renders twice (skill name + id) — assert both are present.
|
|
300
|
+
(0, vitest_1.expect)(react_1.screen.getAllByText('demo-fix')).toHaveLength(2);
|
|
301
|
+
});
|
|
302
|
+
(0, vitest_1.it)('P6e — provenance badges: bundled skills get 🧠, user-added get community', async () => {
|
|
303
|
+
mockReads();
|
|
304
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
305
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Skills/ })).toBeTruthy());
|
|
306
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Skills/ }));
|
|
307
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText('🧠 bundled')).toBeTruthy());
|
|
308
|
+
// Fix lint (bundled) → 🧠 badge; Custom X (user-added) → community badge.
|
|
309
|
+
const bundledCards = react_1.screen.getAllByText('🧠 bundled');
|
|
310
|
+
(0, vitest_1.expect)(bundledCards.length).toBeGreaterThanOrEqual(1);
|
|
311
|
+
(0, vitest_1.expect)(react_1.screen.getByText('community')).toBeTruthy();
|
|
312
|
+
});
|
|
313
|
+
(0, vitest_1.it)('P6d — marketplace: searches, shows results, and installs a skill', async () => {
|
|
314
|
+
mockReads();
|
|
315
|
+
const searchSpy = vitest_1.vi.spyOn(api_1.dashboardAPI, 'marketplaceSearch').mockResolvedValue([
|
|
316
|
+
{ name: 'code-assist', version: '1.2.0', description: 'Assist with code edits', author: 'nvidia', tags: ['code', 'assist'], source: 'git-repo:https://github.com/x/skills', sourceKind: 'git-repo' },
|
|
317
|
+
]);
|
|
318
|
+
const installSpy = vitest_1.vi.spyOn(api_1.dashboardAPI, 'marketplaceInstall').mockResolvedValue({ ok: true });
|
|
319
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
320
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Skills/ })).toBeTruthy());
|
|
321
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Skills/ }));
|
|
322
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByPlaceholderText(/Search community skills/)).toBeTruthy());
|
|
323
|
+
react_1.fireEvent.change(react_1.screen.getByPlaceholderText(/Search community skills/), { target: { value: 'assist' } });
|
|
324
|
+
react_1.fireEvent.submit(react_1.screen.getByPlaceholderText(/Search community skills/).closest('form'));
|
|
325
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(searchSpy).toHaveBeenCalledWith('assist'));
|
|
326
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText('code-assist')).toBeTruthy());
|
|
327
|
+
(0, vitest_1.expect)(react_1.screen.getByText(/v1\.2\.0 · git-repo/)).toBeTruthy();
|
|
328
|
+
react_1.fireEvent.click(react_1.screen.getByText('⬇ Install'));
|
|
329
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(installSpy).toHaveBeenCalledWith('code-assist'));
|
|
330
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Installed code-assist/)).toBeTruthy());
|
|
331
|
+
});
|
|
332
|
+
(0, vitest_1.it)('P6d — uninstalls a hub skill from the Skills list', async () => {
|
|
333
|
+
mockReads();
|
|
334
|
+
const uninstallSpy = vitest_1.vi.spyOn(api_1.dashboardAPI, 'marketplaceUninstall').mockResolvedValue({ ok: true });
|
|
335
|
+
(0, react_1.render)(<AgentHub_1.default />);
|
|
336
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByRole('tab', { name: /Skills/ })).toBeTruthy());
|
|
337
|
+
react_1.fireEvent.click(react_1.screen.getByRole('tab', { name: /Skills/ }));
|
|
338
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getAllByText('demo-fix').length).toBeGreaterThanOrEqual(2));
|
|
339
|
+
react_1.fireEvent.click(react_1.screen.getByText('🗑 Uninstall'));
|
|
340
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(uninstallSpy).toHaveBeenCalledWith('demo-fix'));
|
|
341
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(react_1.screen.getByText(/Uninstalled demo-fix/)).toBeTruthy());
|
|
342
|
+
});
|
|
343
|
+
});
|