@pellux/goodvibes-agent 0.1.70 → 0.1.72

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 (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -3
  3. package/docs/README.md +2 -2
  4. package/docs/getting-started.md +1 -1
  5. package/docs/runtime-connection.md +37 -0
  6. package/package.json +43 -2
  7. package/src/agent/skill-discovery.ts +119 -0
  8. package/src/cli/config-overrides.ts +1 -5
  9. package/src/cli/entrypoint.ts +0 -6
  10. package/src/cli/help.ts +0 -43
  11. package/src/cli/index.ts +0 -2
  12. package/src/cli/management-commands.ts +1 -109
  13. package/src/cli/management.ts +1 -32
  14. package/src/cli/package-verification.ts +12 -4
  15. package/src/cli/parser.ts +0 -16
  16. package/src/cli/status.ts +1 -1
  17. package/src/cli/types.ts +0 -8
  18. package/src/input/commands/delegation-runtime.ts +0 -8
  19. package/src/input/commands/experience-runtime.ts +0 -177
  20. package/src/input/commands/guidance-runtime.ts +0 -69
  21. package/src/input/commands/local-runtime.ts +1 -57
  22. package/src/input/commands/local-setup-review.ts +1 -1
  23. package/src/input/commands/operator-runtime.ts +1 -145
  24. package/src/input/commands/platform-access-runtime.ts +2 -195
  25. package/src/input/commands/product-runtime.ts +0 -116
  26. package/src/input/commands/security-runtime.ts +88 -0
  27. package/src/input/commands/session-content.ts +0 -97
  28. package/src/input/commands/shell-core.ts +0 -13
  29. package/src/input/commands.ts +2 -95
  30. package/src/panels/builtin/operations.ts +3 -184
  31. package/src/panels/confirm-state.ts +1 -1
  32. package/src/panels/index.ts +0 -11
  33. package/src/version.ts +1 -1
  34. package/docs/deployment-and-services.md +0 -52
  35. package/src/cli/service-command.ts +0 -26
  36. package/src/cli/surface-command.ts +0 -247
  37. package/src/input/commands/branch-runtime.ts +0 -72
  38. package/src/input/commands/control-room-runtime.ts +0 -234
  39. package/src/input/commands/discovery-runtime.ts +0 -61
  40. package/src/input/commands/hooks-runtime.ts +0 -207
  41. package/src/input/commands/incident-runtime.ts +0 -106
  42. package/src/input/commands/integration-runtime.ts +0 -437
  43. package/src/input/commands/local-setup.ts +0 -288
  44. package/src/input/commands/managed-runtime.ts +0 -240
  45. package/src/input/commands/marketplace-runtime.ts +0 -305
  46. package/src/input/commands/memory-product-runtime.ts +0 -148
  47. package/src/input/commands/operator-panel-runtime.ts +0 -146
  48. package/src/input/commands/platform-services-runtime.ts +0 -271
  49. package/src/input/commands/profile-sync-runtime.ts +0 -110
  50. package/src/input/commands/provider.ts +0 -363
  51. package/src/input/commands/remote-runtime-pool.ts +0 -89
  52. package/src/input/commands/remote-runtime-setup.ts +0 -226
  53. package/src/input/commands/remote-runtime.ts +0 -432
  54. package/src/input/commands/replay-runtime.ts +0 -25
  55. package/src/input/commands/services-runtime.ts +0 -220
  56. package/src/input/commands/settings-sync-runtime.ts +0 -197
  57. package/src/input/commands/share-runtime.ts +0 -127
  58. package/src/input/commands/skills-runtime.ts +0 -226
  59. package/src/input/commands/teleport-runtime.ts +0 -68
  60. package/src/panels/cockpit-panel.ts +0 -183
  61. package/src/panels/communication-panel.ts +0 -153
  62. package/src/panels/control-plane-panel.ts +0 -211
  63. package/src/panels/forensics-panel.ts +0 -364
  64. package/src/panels/hooks-panel.ts +0 -239
  65. package/src/panels/incident-review-panel.ts +0 -197
  66. package/src/panels/marketplace-panel.ts +0 -212
  67. package/src/panels/ops-control-panel.ts +0 -150
  68. package/src/panels/ops-strategy-panel.ts +0 -235
  69. package/src/panels/orchestration-panel.ts +0 -272
  70. package/src/panels/plugins-panel.ts +0 -178
  71. package/src/panels/remote-panel.ts +0 -449
  72. package/src/panels/routes-panel.ts +0 -178
  73. package/src/panels/services-panel.ts +0 -231
  74. package/src/panels/settings-sync-panel.ts +0 -120
  75. package/src/panels/skills-panel.ts +0 -431
  76. package/src/panels/watchers-panel.ts +0 -193
  77. package/src/verification/live-verifier.ts +0 -588
  78. package/src/verification/verification-ledger.ts +0 -239
@@ -1,588 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { join, resolve } from 'node:path';
3
- import { spawn } from 'node:child_process';
4
- import { buildVerificationLedger } from './verification-ledger.ts';
5
- import { SDK_VERSION } from '../version.ts';
6
-
7
- const AGENT_KNOWLEDGE_FORBIDDEN_RESPONSE_MARKERS = [
8
- ['home', ' assistant'].join(''),
9
- ['home', 'graph'].join(''),
10
- ['home ', 'graph'].join(''),
11
- ] as const;
12
-
13
- export type LiveVerificationStatus = 'pass' | 'warn' | 'fail' | 'skip';
14
-
15
- export interface LiveVerificationCheck {
16
- id: string;
17
- title: string;
18
- status: LiveVerificationStatus;
19
- summary: string;
20
- detail?: string;
21
- }
22
-
23
- export interface LiveVerificationOptions {
24
- homeDir: string;
25
- binaryPath: string;
26
- projectRoot: string;
27
- daemonBaseUrl?: string;
28
- token?: string;
29
- strict?: boolean;
30
- }
31
-
32
- export interface LiveVerificationReport {
33
- generatedAt: string;
34
- homeDir: string;
35
- binaryPath: string;
36
- daemonBaseUrl: string;
37
- strict: boolean;
38
- checks: LiveVerificationCheck[];
39
- counts: Record<LiveVerificationStatus, number>;
40
- ok: boolean;
41
- }
42
-
43
- interface CommandResult {
44
- exitCode: number | null;
45
- stdout: string;
46
- stderr: string;
47
- timedOut: boolean;
48
- }
49
-
50
- function readJsonFile(path: string): unknown {
51
- return JSON.parse(readFileSync(path, 'utf8'));
52
- }
53
-
54
- function redact(text: string): string {
55
- return text
56
- .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/g, 'Bearer [redacted]')
57
- .replace(/"token"\s*:\s*"[^"]+"/g, '"token":"[redacted]"');
58
- }
59
-
60
- function compact(text: string, maxLength = 900): string {
61
- const trimmed = redact(text.trim());
62
- if (trimmed.length <= maxLength) return trimmed;
63
- return `${trimmed.slice(0, maxLength - 16)}... [truncated]`;
64
- }
65
-
66
- function readDaemonToken(homeDir: string): string | undefined {
67
- if (process.env.GOODVIBES_DAEMON_TOKEN) return process.env.GOODVIBES_DAEMON_TOKEN;
68
- const tokenPath = join(homeDir, 'daemon', 'operator-tokens.json');
69
- if (!existsSync(tokenPath)) return undefined;
70
- try {
71
- const data = readJsonFile(tokenPath);
72
- if (data && typeof data === 'object' && typeof (data as { token?: unknown }).token === 'string') {
73
- return (data as { token: string }).token;
74
- }
75
- } catch {
76
- return undefined;
77
- }
78
- return undefined;
79
- }
80
-
81
- function resolveDaemonBaseUrl(homeDir: string, explicit?: string): string {
82
- if (explicit) return explicit.replace(/\/+$/, '');
83
- if (process.env.GOODVIBES_DAEMON_URL) return process.env.GOODVIBES_DAEMON_URL.replace(/\/+$/, '');
84
- const settingsPath = join(homeDir, 'tui', 'settings.json');
85
- let port = 3421;
86
- if (existsSync(settingsPath)) {
87
- try {
88
- const settings = readJsonFile(settingsPath);
89
- const configuredPort = (settings as { controlPlane?: { port?: unknown } })?.controlPlane?.port;
90
- if (typeof configuredPort === 'number' && Number.isFinite(configuredPort)) port = configuredPort;
91
- } catch {
92
- // Keep the default; this verifier should report daemon state, not fail before checks run.
93
- }
94
- }
95
- return `http://127.0.0.1:${port}`;
96
- }
97
-
98
- function runCommand(command: string, args: string[], cwd: string, timeoutMs = 15_000): Promise<CommandResult> {
99
- return new Promise((resolveCommand) => {
100
- const child = spawn(command, args, {
101
- cwd,
102
- env: { ...process.env, NO_COLOR: '1' },
103
- stdio: ['ignore', 'pipe', 'pipe'],
104
- });
105
- const stdout: Buffer[] = [];
106
- const stderr: Buffer[] = [];
107
- let timedOut = false;
108
- const timeout = setTimeout(() => {
109
- timedOut = true;
110
- child.kill('SIGTERM');
111
- setTimeout(() => child.kill('SIGKILL'), 1000).unref();
112
- }, timeoutMs);
113
- child.stdout?.on('data', (chunk) => stdout.push(Buffer.from(chunk)));
114
- child.stderr?.on('data', (chunk) => stderr.push(Buffer.from(chunk)));
115
- child.on('error', (error) => {
116
- clearTimeout(timeout);
117
- resolveCommand({
118
- exitCode: -1,
119
- stdout: '',
120
- stderr: error instanceof Error ? error.message : String(error),
121
- timedOut,
122
- });
123
- });
124
- child.on('exit', (exitCode) => {
125
- clearTimeout(timeout);
126
- resolveCommand({
127
- exitCode,
128
- stdout: Buffer.concat(stdout).toString('utf8'),
129
- stderr: Buffer.concat(stderr).toString('utf8'),
130
- timedOut,
131
- });
132
- });
133
- });
134
- }
135
-
136
- function commandCheck(
137
- id: string,
138
- title: string,
139
- result: CommandResult,
140
- passSummary: string,
141
- options?: { warnOnNonZero?: boolean; parseJson?: boolean },
142
- ): LiveVerificationCheck {
143
- if (result.timedOut) {
144
- return {
145
- id,
146
- title,
147
- status: options?.warnOnNonZero ? 'warn' : 'fail',
148
- summary: 'Command timed out.',
149
- detail: compact(`${result.stdout}\n${result.stderr}`),
150
- };
151
- }
152
- if (result.exitCode !== 0) {
153
- return {
154
- id,
155
- title,
156
- status: options?.warnOnNonZero ? 'warn' : 'fail',
157
- summary: `Command exited ${result.exitCode}.`,
158
- detail: compact(`${result.stdout}\n${result.stderr}`),
159
- };
160
- }
161
- if (options?.parseJson) {
162
- try {
163
- JSON.parse(result.stdout);
164
- } catch (error) {
165
- return {
166
- id,
167
- title,
168
- status: 'fail',
169
- summary: 'Command succeeded but did not return valid JSON.',
170
- detail: error instanceof Error ? error.message : String(error),
171
- };
172
- }
173
- }
174
- return {
175
- id,
176
- title,
177
- status: 'pass',
178
- summary: passSummary,
179
- detail: compact(result.stdout || result.stderr),
180
- };
181
- }
182
-
183
- async function fetchCheck(
184
- id: string,
185
- title: string,
186
- url: string,
187
- token: string | undefined,
188
- validate: (status: number, body: string) => { status: LiveVerificationStatus; summary: string; detail?: string },
189
- ): Promise<LiveVerificationCheck> {
190
- if (!token) {
191
- return {
192
- id,
193
- title,
194
- status: 'skip',
195
- summary: 'No daemon bearer token was available.',
196
- };
197
- }
198
- try {
199
- const response = await fetch(url, {
200
- headers: { Authorization: `Bearer ${token}` },
201
- signal: AbortSignal.timeout(5000),
202
- });
203
- const body = await response.text();
204
- const validated = validate(response.status, body);
205
- return {
206
- id,
207
- title,
208
- ...validated,
209
- detail: validated.detail ?? compact(body),
210
- };
211
- } catch (error) {
212
- return {
213
- id,
214
- title,
215
- status: 'fail',
216
- summary: 'Request failed.',
217
- detail: error instanceof Error ? error.message : String(error),
218
- };
219
- }
220
- }
221
-
222
- async function fetchJsonCheck(
223
- id: string,
224
- title: string,
225
- url: string,
226
- token: string | undefined,
227
- options: {
228
- readonly method?: 'GET' | 'POST';
229
- readonly body?: unknown;
230
- readonly validate: (status: number, body: string) => { status: LiveVerificationStatus; summary: string; detail?: string };
231
- },
232
- ): Promise<LiveVerificationCheck> {
233
- if (!token) {
234
- return {
235
- id,
236
- title,
237
- status: 'skip',
238
- summary: 'No daemon bearer token was available.',
239
- };
240
- }
241
- try {
242
- const response = await fetch(url, {
243
- method: options.method ?? 'GET',
244
- headers: {
245
- Authorization: `Bearer ${token}`,
246
- 'Content-Type': 'application/json',
247
- },
248
- body: options.body === undefined ? undefined : JSON.stringify(options.body),
249
- signal: AbortSignal.timeout(5000),
250
- });
251
- const body = await response.text();
252
- const validated = options.validate(response.status, body);
253
- return {
254
- id,
255
- title,
256
- ...validated,
257
- detail: validated.detail ?? compact(body),
258
- };
259
- } catch (error) {
260
- return {
261
- id,
262
- title,
263
- status: 'fail',
264
- summary: 'Request failed.',
265
- detail: error instanceof Error ? error.message : String(error),
266
- };
267
- }
268
- }
269
-
270
- function countStatuses(checks: readonly LiveVerificationCheck[]): Record<LiveVerificationStatus, number> {
271
- return checks.reduce<Record<LiveVerificationStatus, number>>(
272
- (counts, check) => {
273
- counts[check.status] += 1;
274
- return counts;
275
- },
276
- { pass: 0, warn: 0, fail: 0, skip: 0 },
277
- );
278
- }
279
-
280
- export function buildAgentKnowledgeLiveSkipCheck(
281
- id: string,
282
- title: string,
283
- daemonVersion: string,
284
- expectedSdkVersion = SDK_VERSION,
285
- ): LiveVerificationCheck {
286
- return {
287
- id,
288
- title,
289
- status: 'skip',
290
- summary: `Skipped because external daemon SDK ${daemonVersion} does not match Agent SDK pin ${expectedSdkVersion}.`,
291
- detail: [
292
- 'Agent Knowledge is intentionally isolated under /api/goodvibes-agent/knowledge/*.',
293
- 'An older daemon cannot validate those routes, and Agent must not fall back to default Knowledge/Wiki or non-Agent knowledge segments.',
294
- 'Update/restart the external daemon, then rerun live verification.',
295
- ].join('\n'),
296
- };
297
- }
298
-
299
- export async function buildLiveVerificationReport(options: LiveVerificationOptions): Promise<LiveVerificationReport> {
300
- const homeDir = resolve(options.homeDir);
301
- const projectRoot = resolve(options.projectRoot);
302
- const binaryPath = resolve(options.binaryPath);
303
- const daemonBaseUrl = resolveDaemonBaseUrl(homeDir, options.daemonBaseUrl);
304
- const token = options.token ?? readDaemonToken(homeDir);
305
- const checks: LiveVerificationCheck[] = [];
306
- let daemonSdkVersion: string | null = null;
307
-
308
- const ledger = buildVerificationLedger(projectRoot);
309
- checks.push({
310
- id: 'verification-ledger',
311
- title: 'Verification inventory ledger',
312
- status: ledger.totals.localSignalPercent >= 90 ? 'pass' : 'fail',
313
- summary: `${ledger.totals.localSignalPercent}% local verification signal across ${ledger.totals.total} inventory items.`,
314
- detail: `${ledger.totals.localBehaviorPercent}% local behavior verified; ${ledger.totals.externalOutcomeRequired} item(s) require external outcomes.`,
315
- });
316
-
317
- checks.push({
318
- id: 'compiled-cli-present',
319
- title: 'Compiled GoodVibes Agent CLI binary',
320
- status: existsSync(binaryPath) ? 'pass' : 'fail',
321
- summary: existsSync(binaryPath) ? `Found ${binaryPath}.` : `Missing ${binaryPath}.`,
322
- });
323
-
324
- if (existsSync(binaryPath)) {
325
- checks.push(commandCheck(
326
- 'cli-version',
327
- 'Agent CLI version command',
328
- await runCommand(binaryPath, ['--version'], projectRoot),
329
- 'Agent CLI version returned successfully.',
330
- ));
331
- checks.push(commandCheck(
332
- 'cli-status-json',
333
- 'Agent CLI status JSON command',
334
- await runCommand(binaryPath, ['status', '--json'], projectRoot),
335
- 'Agent CLI status returned parseable JSON.',
336
- { parseJson: true },
337
- ));
338
- checks.push(commandCheck(
339
- 'cli-compat-json',
340
- 'Agent CLI compatibility JSON command',
341
- await runCommand(binaryPath, ['compat', '--json'], projectRoot),
342
- 'Agent CLI compatibility returned parseable JSON.',
343
- { parseJson: true, warnOnNonZero: true },
344
- ));
345
- checks.push(commandCheck(
346
- 'cli-agent-knowledge-status',
347
- 'Agent Knowledge CLI status command',
348
- await runCommand(binaryPath, ['knowledge', 'status', '--json'], projectRoot),
349
- 'Agent Knowledge status returned parseable JSON.',
350
- { parseJson: true, warnOnNonZero: true },
351
- ));
352
- checks.push(commandCheck(
353
- 'cli-providers',
354
- 'Agent CLI providers command',
355
- await runCommand(binaryPath, ['providers'], projectRoot),
356
- 'Provider inventory rendered successfully.',
357
- ));
358
- checks.push(commandCheck(
359
- 'cli-control-plane-status',
360
- 'Read-only control-plane status command',
361
- await runCommand(binaryPath, ['control-plane', 'status'], projectRoot),
362
- 'Control-plane status rendered successfully.',
363
- { warnOnNonZero: true },
364
- ));
365
- checks.push(commandCheck(
366
- 'cli-listener-test',
367
- 'Read-only listener readiness command',
368
- await runCommand(binaryPath, ['listener', 'test'], projectRoot),
369
- 'HTTP listener readiness rendered successfully.',
370
- { warnOnNonZero: true },
371
- ));
372
- checks.push(commandCheck(
373
- 'cli-surfaces-check',
374
- 'Read-only surfaces readiness command',
375
- await runCommand(binaryPath, ['surfaces', 'check'], projectRoot),
376
- 'Surface readiness rendered successfully.',
377
- { warnOnNonZero: true },
378
- ));
379
- checks.push(commandCheck(
380
- 'cli-service-check',
381
- 'Read-only service posture command',
382
- await runCommand(binaryPath, ['service', 'check'], projectRoot),
383
- 'Service posture rendered successfully.',
384
- { warnOnNonZero: true },
385
- ));
386
- checks.push(commandCheck(
387
- 'cli-doctor',
388
- 'CLI doctor command',
389
- await runCommand(binaryPath, ['doctor', '--output', 'text'], projectRoot),
390
- 'Doctor completed without findings.',
391
- { warnOnNonZero: true },
392
- ));
393
- }
394
-
395
- checks.push(await fetchCheck(
396
- 'daemon-status',
397
- 'Authenticated daemon /status',
398
- `${daemonBaseUrl}/status`,
399
- token,
400
- (status, body) => {
401
- if (status !== 200) return { status: 'fail', summary: `/status returned ${status}.` };
402
- try {
403
- const parsed = JSON.parse(body) as { version?: unknown; sdkVersion?: unknown };
404
- const version = typeof parsed.sdkVersion === 'string'
405
- ? parsed.sdkVersion
406
- : typeof parsed.version === 'string' ? parsed.version : 'unknown';
407
- daemonSdkVersion = version;
408
- return { status: 'pass', summary: `/status returned 200, version ${version}.` };
409
- } catch {
410
- return { status: 'warn', summary: '/status returned 200 but was not parseable JSON.' };
411
- }
412
- },
413
- ));
414
-
415
- checks.push(await fetchCheck(
416
- 'daemon-health',
417
- 'Authenticated daemon /api/health',
418
- `${daemonBaseUrl}/api/health`,
419
- token,
420
- (status, body) => {
421
- if (status !== 200) return { status: 'fail', summary: `/api/health returned ${status}.` };
422
- try {
423
- const parsed = JSON.parse(body) as { overall?: unknown };
424
- return {
425
- status: parsed.overall === 'healthy' ? 'pass' : 'warn',
426
- summary: `Health overall=${String(parsed.overall ?? 'unknown')}.`,
427
- };
428
- } catch {
429
- return { status: 'warn', summary: '/api/health returned 200 but was not parseable JSON.' };
430
- }
431
- },
432
- ));
433
-
434
- checks.push(await fetchCheck(
435
- 'openai-compatible-models',
436
- 'OpenAI-compatible /v1/models route',
437
- `${daemonBaseUrl}/v1/models`,
438
- token,
439
- (status, body) => {
440
- if (status !== 200) return { status: 'fail', summary: `/v1/models returned ${status}.` };
441
- try {
442
- const parsed = JSON.parse(body) as { data?: unknown };
443
- const models = Array.isArray(parsed.data) ? parsed.data.length : 0;
444
- return {
445
- status: models > 0 ? 'pass' : 'warn',
446
- summary: `/v1/models returned ${models} model(s).`,
447
- };
448
- } catch {
449
- return { status: 'warn', summary: '/v1/models returned 200 but was not parseable JSON.' };
450
- }
451
- },
452
- ));
453
-
454
- const daemonVersionMismatch = daemonSdkVersion !== null && daemonSdkVersion !== 'unknown' && daemonSdkVersion !== SDK_VERSION;
455
- if (daemonVersionMismatch) {
456
- const mismatchedDaemonVersion = daemonSdkVersion ?? 'unknown';
457
- checks.push(
458
- buildAgentKnowledgeLiveSkipCheck('agent-knowledge-status', 'Agent Knowledge isolated /status', mismatchedDaemonVersion),
459
- buildAgentKnowledgeLiveSkipCheck('agent-knowledge-ask-isolated', 'Agent Knowledge isolated ask', mismatchedDaemonVersion),
460
- buildAgentKnowledgeLiveSkipCheck('agent-knowledge-search-isolated', 'Agent Knowledge isolated search', mismatchedDaemonVersion),
461
- );
462
- } else {
463
- checks.push(await fetchJsonCheck(
464
- 'agent-knowledge-status',
465
- 'Agent Knowledge isolated /status',
466
- `${daemonBaseUrl}/api/goodvibes-agent/knowledge/status`,
467
- token,
468
- {
469
- validate: (status, body) => {
470
- if (status !== 200) return { status: 'fail', summary: `/api/goodvibes-agent/knowledge/status returned ${status}.` };
471
- try {
472
- JSON.parse(body);
473
- return { status: 'pass', summary: 'Agent Knowledge status route returned parseable JSON.' };
474
- } catch {
475
- return { status: 'fail', summary: 'Agent Knowledge status was not parseable JSON.' };
476
- }
477
- },
478
- },
479
- ));
480
-
481
- checks.push(await fetchJsonCheck(
482
- 'agent-knowledge-ask-isolated',
483
- 'Agent Knowledge isolated ask',
484
- `${daemonBaseUrl}/api/goodvibes-agent/knowledge/ask`,
485
- token,
486
- {
487
- method: 'POST',
488
- body: {
489
- query: 'What is GoodVibes Agent?',
490
- limit: 5,
491
- mode: 'concise',
492
- includeSources: true,
493
- includeConfidence: true,
494
- includeLinkedObjects: true,
495
- },
496
- validate: (status, body) => {
497
- if (status !== 200) return { status: 'fail', summary: `/api/goodvibes-agent/knowledge/ask returned ${status}.` };
498
- try {
499
- JSON.parse(body);
500
- } catch {
501
- return { status: 'fail', summary: 'Agent Knowledge ask was not parseable JSON.' };
502
- }
503
- const lower = body.toLowerCase();
504
- if (AGENT_KNOWLEDGE_FORBIDDEN_RESPONSE_MARKERS.some((marker) => lower.includes(marker))) {
505
- return { status: 'fail', summary: 'Agent Knowledge ask returned non-Agent knowledge contamination.' };
506
- }
507
- return { status: 'pass', summary: 'Agent Knowledge ask stayed on the isolated Agent route.' };
508
- },
509
- },
510
- ));
511
-
512
- checks.push(await fetchJsonCheck(
513
- 'agent-knowledge-search-isolated',
514
- 'Agent Knowledge isolated search',
515
- `${daemonBaseUrl}/api/goodvibes-agent/knowledge/search`,
516
- token,
517
- {
518
- method: 'POST',
519
- body: { query: 'What is GoodVibes Agent?', limit: 5 },
520
- validate: (status, body) => {
521
- if (status !== 200) return { status: 'fail', summary: `/api/goodvibes-agent/knowledge/search returned ${status}.` };
522
- try {
523
- JSON.parse(body);
524
- } catch {
525
- return { status: 'fail', summary: 'Agent Knowledge search was not parseable JSON.' };
526
- }
527
- const lower = body.toLowerCase();
528
- if (AGENT_KNOWLEDGE_FORBIDDEN_RESPONSE_MARKERS.some((marker) => lower.includes(marker))) {
529
- return { status: 'fail', summary: 'Agent Knowledge search returned non-Agent knowledge contamination.' };
530
- }
531
- return { status: 'pass', summary: 'Agent Knowledge search stayed on the isolated Agent route.' };
532
- },
533
- },
534
- ));
535
- }
536
-
537
- const counts = countStatuses(checks);
538
- const ok = counts.fail === 0 && (!options.strict || counts.warn === 0);
539
- return {
540
- generatedAt: new Date().toISOString(),
541
- homeDir,
542
- binaryPath,
543
- daemonBaseUrl,
544
- strict: options.strict ?? false,
545
- checks,
546
- counts,
547
- ok,
548
- };
549
- }
550
-
551
- export function renderLiveVerificationReportMarkdown(report: LiveVerificationReport): string {
552
- const lines: string[] = [
553
- '# GoodVibes Agent Live Verification',
554
- '',
555
- `Generated: ${report.generatedAt}`,
556
- `Home: \`${report.homeDir}\``,
557
- `Binary: \`${report.binaryPath}\``,
558
- `Daemon: \`${report.daemonBaseUrl}\``,
559
- '',
560
- '| Status | Count |',
561
- '|---|---:|',
562
- `| pass | ${report.counts.pass} |`,
563
- `| warn | ${report.counts.warn} |`,
564
- `| fail | ${report.counts.fail} |`,
565
- `| skip | ${report.counts.skip} |`,
566
- '',
567
- '| Check | Status | Summary |',
568
- '|---|---|---|',
569
- ];
570
- for (const check of report.checks) {
571
- lines.push(`| ${check.title} | ${check.status} | ${check.summary.replace(/\|/g, '\\|')} |`);
572
- }
573
- const detailed = report.checks.filter((check) => check.detail?.trim());
574
- if (detailed.length > 0) {
575
- lines.push('', '## Details', '');
576
- for (const check of detailed) {
577
- lines.push(`### ${check.title}`, '', '```text', check.detail?.trim() ?? '', '```', '');
578
- }
579
- }
580
- lines.push(report.ok ? 'Result: PASS' : 'Result: FAIL', '');
581
- return lines.join('\n');
582
- }
583
-
584
- export function writeLiveVerificationReportFiles(report: LiveVerificationReport, outputDir: string): void {
585
- mkdirSync(outputDir, { recursive: true });
586
- writeFileSync(join(outputDir, 'live-verification.json'), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
587
- writeFileSync(join(outputDir, 'live-verification.md'), renderLiveVerificationReportMarkdown(report), 'utf8');
588
- }