@pellux/goodvibes-agent 0.1.43 → 0.1.45

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.45 - 2026-05-31
6
+
7
+ - 0aa4b3e Add daemon route risk approval review
8
+
9
+ ## 0.1.44 - 2026-05-31
10
+
11
+ - fdee09e Add daemon capability gap report
12
+
5
13
  ## 0.1.43 - 2026-05-31
6
14
 
7
15
  - 3afba9c Add daemon route risk coverage
package/README.md CHANGED
@@ -17,6 +17,8 @@ goodvibes-agent --help
17
17
  goodvibes-agent status
18
18
  goodvibes-agent capabilities
19
19
  goodvibes-agent capabilities daemon
20
+ goodvibes-agent capabilities daemon gaps
21
+ goodvibes-agent capabilities daemon risk
20
22
  ```
21
23
 
22
24
  If Bun reports untrusted lifecycle dependencies, trust only the package and dependencies required by this package:
@@ -45,7 +47,7 @@ bun run publish:check
45
47
 
46
48
  Inside the Agent TUI, use `/agent`, `/home`, or `/operator` to open the operator workspace. It is the Agent-first fullscreen surface for setup, status, live daemon capability coverage, knowledge, local memory/skills, work-plan/approval review, automation observability, and explicit build delegation to GoodVibes TUI.
47
49
 
48
- Use `goodvibes-agent capabilities` or `/capabilities` to inspect the OpenClaw/Hermes benchmark, current Agent posture, configuration commands, usage paths, and remaining gaps. Use `goodvibes-agent capabilities daemon` or `/capabilities daemon` for a live read-only audit of the GoodVibes daemon method catalog, route risk posture, and isolated Agent Knowledge route coverage.
50
+ Use `goodvibes-agent capabilities` or `/capabilities` to inspect the OpenClaw/Hermes benchmark, current Agent posture, configuration commands, usage paths, and remaining gaps. Use `goodvibes-agent capabilities daemon` or `/capabilities daemon` for a live read-only audit of the GoodVibes daemon method catalog, route risk posture, and isolated Agent Knowledge route coverage. Use `goodvibes-agent capabilities daemon gaps` or `/capabilities daemon gaps` to turn that live daemon audit into a prioritized gap plan that separates missing daemon routes from Agent UX work. Use `goodvibes-agent capabilities daemon risk` or `/approval risk` for route-risk-aware approval posture without mutating approval state.
49
51
 
50
52
  Inside the workspace, use `/agent-profile guide` to author custom profile starters without leaving the Agent TUI. The guided flow lists starters, exports starter JSON, imports edited local starters, and creates isolated runtime profiles from them.
51
53
 
@@ -87,9 +89,11 @@ To verify what the running daemon can expose for the Agent/OpenClaw/Hermes capab
87
89
  ```sh
88
90
  goodvibes-agent capabilities daemon
89
91
  goodvibes-agent capabilities daemon --json
92
+ goodvibes-agent capabilities daemon gaps
93
+ goodvibes-agent capabilities daemon risk
90
94
  ```
91
95
 
92
- This audit checks `/api/control-plane/methods` and `/api/goodvibes-agent/knowledge/status`. It does not query default Knowledge/Wiki or HomeGraph.
96
+ This audit checks `/api/control-plane/methods` and `/api/goodvibes-agent/knowledge/status`. The gap plan and route-risk review are derived from the same read-only calls. None of these commands query default Knowledge/Wiki or HomeGraph.
93
97
 
94
98
  Agent intentionally blocks daemon lifecycle commands:
95
99
 
@@ -17,6 +17,8 @@ goodvibes-agent capabilities --json
17
17
  goodvibes-agent capabilities hermes
18
18
  goodvibes-agent capabilities daemon
19
19
  goodvibes-agent capabilities daemon --json
20
+ goodvibes-agent capabilities daemon gaps
21
+ goodvibes-agent capabilities daemon risk
20
22
  goodvibes-agent capabilities daemon knowledge
21
23
  ```
22
24
 
@@ -27,6 +29,8 @@ Inside the TUI:
27
29
  /capabilities openclaw
28
30
  /capabilities knowledge
29
31
  /capabilities daemon
32
+ /capabilities daemon gaps
33
+ /approval risk
30
34
  ```
31
35
 
32
36
  ## Research Baseline
@@ -56,7 +60,7 @@ The benchmark measures two different GoodVibes layers:
56
60
 
57
61
  If the daemon already has a route but Agent lacks a good setup/workspace/CLI surface, the gap is treated as an Agent product gap rather than a missing platform capability.
58
62
 
59
- Use `goodvibes-agent capabilities daemon` for the live read-only daemon audit. It checks the public control-plane method catalog, route risk posture, and the isolated Agent Knowledge status route. It intentionally does not call default `/api/knowledge/*`, HomeGraph, or Home Assistant routes.
63
+ Use `goodvibes-agent capabilities daemon` for the live read-only daemon audit. It checks the public control-plane method catalog, route risk posture, and the isolated Agent Knowledge status route. Use `goodvibes-agent capabilities daemon gaps` to convert that daemon-measured audit into a prioritized gap plan with `version_mismatch`, `agent_route_missing`, `required_method_missing`, `route_risk_review`, and `agent_ux_gap` rows. Use `goodvibes-agent capabilities daemon risk` or `/approval risk` for a route-risk-aware approval-center view over read-only, mutating, dangerous, and authenticated route metadata. These commands intentionally avoid default `/api/knowledge/*`, HomeGraph, and Home Assistant routes.
60
64
 
61
65
  ## Capability Targets
62
66
 
@@ -78,7 +82,7 @@ Use `goodvibes-agent capabilities daemon` for the live read-only daemon audit. I
78
82
 
79
83
  GoodVibes Agent should exceed OpenClaw/Hermes by making these properties true from day one:
80
84
 
81
- - Capability surfaces are discoverable through `goodvibes-agent capabilities`, `goodvibes-agent capabilities daemon`, `/capabilities`, `/capabilities daemon`, onboarding, and the operator workspace.
85
+ - Capability surfaces are discoverable through `goodvibes-agent capabilities`, `goodvibes-agent capabilities daemon`, `goodvibes-agent capabilities daemon gaps`, `goodvibes-agent capabilities daemon risk`, `/capabilities`, `/capabilities daemon`, `/approval risk`, onboarding, and the operator workspace.
82
86
  - Agent Knowledge isolation is a release gate, not a convention.
83
87
  - Routine-to-schedule promotion preserves Agent Knowledge isolation, uses only public external daemon schedule routes, supports explicit delivery targets, and stores redacted receipts.
84
88
  - Model-visible tools are policy-gated for serial, non-secret, non-destructive use.
@@ -94,5 +98,5 @@ GoodVibes Agent should exceed OpenClaw/Hermes by making these properties true fr
94
98
  - Artifact and multimodal Agent Knowledge ingestion when the isolated Agent route accepts artifact-backed media.
95
99
  - Deeper live run/delivery history and delivery error surfacing for promoted routines.
96
100
  - Delegation receipts and artifact review inside the operator workspace.
97
- - Approval center with route risk labels and saved policy presets.
101
+ - Saved policy presets for the route-risk-aware approval center.
98
102
  - Intent-gated tool exposure so the model sees fewer irrelevant tools per turn while retaining broad capability coverage.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "private": false,
5
5
  "description": "Near-fork GoodVibes operator assistant with the GoodVibes TUI shell, renderer, input, fullscreen workspace, and daemon-connected Agent product brain.",
6
6
  "type": "module",
@@ -6,21 +6,39 @@ import {
6
6
  renderOperatorCapabilityBenchmark,
7
7
  } from '../operator/capability-benchmark.ts';
8
8
  import {
9
+ buildDaemonCapabilityGapReport,
10
+ buildDaemonCapabilityRouteRiskReport,
9
11
  fetchLiveDaemonCapabilityAudit,
10
12
  filterDaemonCapabilityAuditAreas,
13
+ filterDaemonCapabilityGaps,
14
+ filterDaemonCapabilityRouteRiskAreas,
11
15
  renderDaemonCapabilityAudit,
12
16
  renderDaemonCapabilityFailure,
17
+ renderDaemonCapabilityGaps,
18
+ renderDaemonCapabilityRouteRisk,
13
19
  } from '../operator/daemon-capability-audit.ts';
14
20
  import { resolveAgentDaemonConnection } from '../agent/routine-schedule-promotion.ts';
15
21
 
16
22
  interface CapabilityCommandArgs {
17
- readonly mode: 'benchmark' | 'daemon';
23
+ readonly mode: 'benchmark' | 'daemon' | 'daemon-gaps' | 'daemon-risk';
18
24
  readonly query: string | undefined;
19
25
  }
20
26
 
21
27
  function readCapabilityArgs(args: readonly string[]): CapabilityCommandArgs {
22
28
  const values = args.filter((arg) => !arg.startsWith('--'));
29
+ if (values[0] === 'gaps') {
30
+ const query = values.slice(1).join(' ').trim();
31
+ return { mode: 'daemon-gaps', query: query.length > 0 ? query : undefined };
32
+ }
23
33
  if (values[0] === 'daemon') {
34
+ if (values[1] === 'gaps') {
35
+ const query = values.slice(2).join(' ').trim();
36
+ return { mode: 'daemon-gaps', query: query.length > 0 ? query : undefined };
37
+ }
38
+ if (values[1] === 'risk' || values[1] === 'route-risk') {
39
+ const query = values.slice(2).join(' ').trim();
40
+ return { mode: 'daemon-risk', query: query.length > 0 ? query : undefined };
41
+ }
24
42
  const query = values.slice(1).join(' ').trim();
25
43
  return { mode: 'daemon', query: query.length > 0 ? query : undefined };
26
44
  }
@@ -48,6 +66,46 @@ export async function handleCapabilitiesCommand(runtime: CliCommandRuntime): Pro
48
66
  exitCode: 0,
49
67
  };
50
68
  }
69
+ if (args.mode === 'daemon-gaps') {
70
+ const connection = resolveAgentDaemonConnection(runtime.configManager, runtime.homeDirectory);
71
+ const audit = await fetchLiveDaemonCapabilityAudit(connection);
72
+ if (!audit.ok) {
73
+ return {
74
+ output: runtime.cli.flags.outputFormat === 'json'
75
+ ? JSON.stringify(audit, null, 2)
76
+ : renderDaemonCapabilityFailure(audit),
77
+ exitCode: audit.kind === 'auth_required' || audit.kind === 'daemon_unavailable' ? 1 : 2,
78
+ };
79
+ }
80
+ const report = buildDaemonCapabilityGapReport(audit);
81
+ const gaps = filterDaemonCapabilityGaps(report.gaps, args.query);
82
+ return {
83
+ output: runtime.cli.flags.outputFormat === 'json'
84
+ ? JSON.stringify({ ...report, matchedGapCount: gaps.length, gaps }, null, 2)
85
+ : renderDaemonCapabilityGaps(report, gaps),
86
+ exitCode: 0,
87
+ };
88
+ }
89
+ if (args.mode === 'daemon-risk') {
90
+ const connection = resolveAgentDaemonConnection(runtime.configManager, runtime.homeDirectory);
91
+ const audit = await fetchLiveDaemonCapabilityAudit(connection);
92
+ if (!audit.ok) {
93
+ return {
94
+ output: runtime.cli.flags.outputFormat === 'json'
95
+ ? JSON.stringify(audit, null, 2)
96
+ : renderDaemonCapabilityFailure(audit),
97
+ exitCode: audit.kind === 'auth_required' || audit.kind === 'daemon_unavailable' ? 1 : 2,
98
+ };
99
+ }
100
+ const report = buildDaemonCapabilityRouteRiskReport(audit);
101
+ const areas = filterDaemonCapabilityRouteRiskAreas(report.areas, args.query);
102
+ return {
103
+ output: runtime.cli.flags.outputFormat === 'json'
104
+ ? JSON.stringify({ ...report, matchedAreaCount: areas.length, areas }, null, 2)
105
+ : renderDaemonCapabilityRouteRisk(report, areas),
106
+ exitCode: 0,
107
+ };
108
+ }
51
109
  const report = buildOperatorCapabilityBenchmarkReport();
52
110
  const capabilities = filterOperatorCapabilities(report.capabilities, args.query);
53
111
  if (runtime.cli.flags.outputFormat === 'json') {
package/src/cli/help.ts CHANGED
@@ -103,6 +103,8 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
103
103
  ` ${binary} compat`,
104
104
  ` ${binary} capabilities`,
105
105
  ` ${binary} capabilities daemon`,
106
+ ` ${binary} capabilities daemon gaps`,
107
+ ` ${binary} capabilities daemon risk`,
106
108
  ` ${binary} knowledge status`,
107
109
  ` ${binary} knowledge ask "What is GoodVibes Agent?"`,
108
110
  ` ${binary} ask "What is GoodVibes Agent?"`,
@@ -197,9 +199,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
197
199
  examples: ['compat', 'compat --json'],
198
200
  },
199
201
  capabilities: {
200
- usage: ['capabilities [openclaw|hermes|query]', 'capabilities daemon [query]', 'capabilities --json'],
201
- summary: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness, and live GoodVibes daemon method coverage.',
202
- examples: ['capabilities', 'capabilities hermes', 'capabilities daemon', 'capabilities daemon knowledge --json'],
202
+ usage: ['capabilities [openclaw|hermes|query]', 'capabilities daemon [query]', 'capabilities daemon gaps [query]', 'capabilities daemon risk [query]', 'capabilities --json'],
203
+ summary: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness, live GoodVibes daemon method coverage, and daemon-measured product gaps.',
204
+ examples: ['capabilities', 'capabilities hermes', 'capabilities daemon', 'capabilities daemon gaps', 'capabilities daemon risk --json', 'capabilities daemon knowledge --json'],
203
205
  },
204
206
  knowledge: {
205
207
  usage: [
@@ -489,6 +489,8 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
489
489
  actions: [
490
490
  { id: 'capabilities-benchmark', label: 'Benchmark report', detail: 'Show the OpenClaw/Hermes parity benchmark with Agent posture, setup commands, usage paths, and remaining product gaps.', command: '/capabilities', kind: 'command', safety: 'read-only' },
491
491
  { id: 'capabilities-daemon', label: 'Live daemon audit', detail: 'Inspect the public daemon method catalog plus isolated Agent Knowledge route coverage. Does not query default Knowledge/Wiki or HomeGraph.', command: '/capabilities daemon', kind: 'command', safety: 'read-only' },
492
+ { id: 'capabilities-daemon-gaps', label: 'Daemon gap plan', detail: 'Classify live daemon coverage into platform gaps, Agent route gaps, route-risk reviews, and Agent UX work without querying default Knowledge/Wiki or HomeGraph.', command: '/capabilities daemon gaps', kind: 'command', safety: 'read-only' },
493
+ { id: 'capabilities-daemon-risk', label: 'Route risk review', detail: 'Inspect daemon read-only, mutating, dangerous, and authenticated route metadata for approval-center planning.', command: '/capabilities daemon risk', kind: 'command', safety: 'read-only' },
492
494
  { id: 'capabilities-daemon-knowledge', label: 'Knowledge coverage', detail: 'Filter the live daemon audit to the isolated Agent Knowledge segment.', command: '/capabilities daemon knowledge', kind: 'command', safety: 'read-only' },
493
495
  { id: 'capabilities-daemon-channels', label: 'Channel coverage', detail: 'Filter the live daemon audit to channel and delivery gateway route coverage.', command: '/capabilities daemon channels', kind: 'command', safety: 'read-only' },
494
496
  { id: 'capabilities-daemon-automation', label: 'Automation coverage', detail: 'Filter the live daemon audit to automation, schedule, run, and capacity route coverage.', command: '/capabilities daemon automation', kind: 'command', safety: 'read-only' },
@@ -580,6 +582,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
580
582
  { id: 'workplan', label: 'Open work plan', detail: 'Open the workspace-scoped work plan panel.', command: '/workplan panel', kind: 'command', safety: 'read-only' },
581
583
  { id: 'workplan-list', label: 'List work plan', detail: 'Print a concise work plan summary.', command: '/workplan list', kind: 'command', safety: 'read-only' },
582
584
  { id: 'approvals', label: 'Review approvals', detail: 'Open/read approval posture. This workspace does not approve or deny requests.', command: '/approval open', kind: 'command', safety: 'read-only' },
585
+ { id: 'approval-risk', label: 'Route risk review', detail: 'Inspect daemon route risk and dangerous method metadata without approving, denying, or mutating requests.', command: '/approval risk', kind: 'command', safety: 'read-only' },
583
586
  ],
584
587
  },
585
588
  {
@@ -5,10 +5,16 @@ import {
5
5
  OPERATOR_CAPABILITY_BENCHMARKS,
6
6
  } from '../../operator/capability-benchmark.ts';
7
7
  import {
8
+ buildDaemonCapabilityGapReport,
9
+ buildDaemonCapabilityRouteRiskReport,
8
10
  fetchLiveDaemonCapabilityAudit,
9
11
  filterDaemonCapabilityAuditAreas,
12
+ filterDaemonCapabilityGaps,
13
+ filterDaemonCapabilityRouteRiskAreas,
10
14
  renderDaemonCapabilityAudit,
11
15
  renderDaemonCapabilityFailure,
16
+ renderDaemonCapabilityGaps,
17
+ renderDaemonCapabilityRouteRisk,
12
18
  } from '../../operator/daemon-capability-audit.ts';
13
19
  import { resolveAgentDaemonConnection } from '../../agent/routine-schedule-promotion.ts';
14
20
 
@@ -17,7 +23,7 @@ export function registerCapabilitiesRuntimeCommands(registry: CommandRegistry):
17
23
  name: 'capabilities',
18
24
  aliases: ['caps', 'benchmark'],
19
25
  description: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness, and live daemon coverage',
20
- usage: '[daemon|openclaw|hermes|query]',
26
+ usage: '[daemon|gaps|openclaw|hermes|query]',
21
27
  async handler(args, ctx) {
22
28
  if (args[0] === 'daemon') {
23
29
  const homeDirectory = ctx.platform.configManager.getHomeDirectory() ?? process.cwd();
@@ -27,11 +33,39 @@ export function registerCapabilitiesRuntimeCommands(registry: CommandRegistry):
27
33
  ctx.print(renderDaemonCapabilityFailure(audit));
28
34
  return;
29
35
  }
36
+ if (args[1] === 'gaps') {
37
+ const report = buildDaemonCapabilityGapReport(audit);
38
+ const query = args.slice(2).join(' ').trim() || undefined;
39
+ const gaps = filterDaemonCapabilityGaps(report.gaps, query);
40
+ ctx.print(renderDaemonCapabilityGaps(report, gaps));
41
+ return;
42
+ }
43
+ if (args[1] === 'risk' || args[1] === 'route-risk') {
44
+ const report = buildDaemonCapabilityRouteRiskReport(audit);
45
+ const query = args.slice(2).join(' ').trim() || undefined;
46
+ const areas = filterDaemonCapabilityRouteRiskAreas(report.areas, query);
47
+ ctx.print(renderDaemonCapabilityRouteRisk(report, areas));
48
+ return;
49
+ }
30
50
  const query = args.slice(1).join(' ').trim() || undefined;
31
51
  const areas = filterDaemonCapabilityAuditAreas(audit.areas, query);
32
52
  ctx.print(renderDaemonCapabilityAudit(audit, areas));
33
53
  return;
34
54
  }
55
+ if (args[0] === 'gaps') {
56
+ const homeDirectory = ctx.platform.configManager.getHomeDirectory() ?? process.cwd();
57
+ const connection = resolveAgentDaemonConnection(ctx.platform.configManager, homeDirectory);
58
+ const audit = await fetchLiveDaemonCapabilityAudit(connection);
59
+ if (!audit.ok) {
60
+ ctx.print(renderDaemonCapabilityFailure(audit));
61
+ return;
62
+ }
63
+ const report = buildDaemonCapabilityGapReport(audit);
64
+ const query = args.slice(1).join(' ').trim() || undefined;
65
+ const gaps = filterDaemonCapabilityGaps(report.gaps, query);
66
+ ctx.print(renderDaemonCapabilityGaps(report, gaps));
67
+ return;
68
+ }
35
69
  const query = args.join(' ').trim() || undefined;
36
70
  const capabilities = filterOperatorCapabilities(OPERATOR_CAPABILITY_BENCHMARKS, query);
37
71
  ctx.print(renderOperatorCapabilityBenchmark(capabilities));
@@ -3,6 +3,14 @@ import { dirname, resolve } from 'node:path';
3
3
  import type { CommandRegistry } from '../command-registry.ts';
4
4
  import { requirePanelManager, requireShellPaths } from './runtime-services.ts';
5
5
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
6
+ import { resolveAgentDaemonConnection } from '../../agent/routine-schedule-promotion.ts';
7
+ import {
8
+ buildDaemonCapabilityRouteRiskReport,
9
+ fetchLiveDaemonCapabilityAudit,
10
+ filterDaemonCapabilityRouteRiskAreas,
11
+ renderDaemonCapabilityFailure,
12
+ renderDaemonCapabilityRouteRisk,
13
+ } from '../../operator/daemon-capability-audit.ts';
6
14
 
7
15
  interface VoiceBundle {
8
16
  readonly version: 1;
@@ -178,8 +186,8 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
178
186
  name: 'approval',
179
187
  aliases: ['approvals'],
180
188
  description: 'Review action-specific approval classes and the specialized security UX matrix',
181
- usage: '[matrix|review <kind>]',
182
- handler(args, ctx) {
189
+ usage: '[matrix|risk|review <kind>]',
190
+ async handler(args, ctx) {
183
191
  const sub = (args[0] ?? 'matrix').toLowerCase();
184
192
  if (sub === 'open' || sub === 'panel') {
185
193
  if (ctx.showPanel) ctx.showPanel('approval');
@@ -205,9 +213,25 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
205
213
  ctx.print([
206
214
  'Approval Matrix',
207
215
  ...matrix.map(([kind, summary]) => ` ${kind.padEnd(10)} ${summary}`),
216
+ '',
217
+ 'Live daemon route risk: /approval risk',
208
218
  ].join('\n'));
209
219
  return;
210
220
  }
221
+ if (sub === 'risk' || sub === 'route-risk') {
222
+ const shellPaths = requireShellPaths(ctx);
223
+ const connection = resolveAgentDaemonConnection(ctx.platform.configManager, shellPaths.homeDirectory);
224
+ const audit = await fetchLiveDaemonCapabilityAudit(connection);
225
+ if (!audit.ok) {
226
+ ctx.print(renderDaemonCapabilityFailure(audit));
227
+ return;
228
+ }
229
+ const report = buildDaemonCapabilityRouteRiskReport(audit);
230
+ const query = args.slice(1).join(' ').trim() || undefined;
231
+ const areas = filterDaemonCapabilityRouteRiskAreas(report.areas, query);
232
+ ctx.print(renderDaemonCapabilityRouteRisk(report, areas));
233
+ return;
234
+ }
211
235
  if (sub === 'review') {
212
236
  const kind = (args[1] ?? '').toLowerCase();
213
237
  const entry = matrix.find(([id]) => id === kind);
@@ -222,7 +246,7 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
222
246
  ].join('\n'));
223
247
  return;
224
248
  }
225
- ctx.print('Usage: /approval [open|matrix|review <kind>]');
249
+ ctx.print('Usage: /approval [open|matrix|risk|review <kind>]');
226
250
  },
227
251
  });
228
252
 
@@ -15,6 +15,13 @@ export type DaemonCapabilityAuditFailureKind =
15
15
 
16
16
  export type DaemonCapabilityCoverage = 'ready' | 'partial' | 'missing';
17
17
  export type DaemonCapabilityRouteCoverage = 'ready' | 'missing' | 'not_checked';
18
+ export type DaemonCapabilityGapKind =
19
+ | 'version_mismatch'
20
+ | 'agent_route_missing'
21
+ | 'required_method_missing'
22
+ | 'route_risk_review'
23
+ | 'agent_ux_gap';
24
+ export type DaemonCapabilityGapSeverity = 'blocker' | 'high' | 'medium' | 'low';
18
25
 
19
26
  export interface DaemonCapabilityRequirement {
20
27
  readonly id: string;
@@ -42,6 +49,9 @@ export interface DaemonCapabilityAuditArea {
42
49
  readonly coverage: DaemonCapabilityRouteCoverage;
43
50
  }[];
44
51
  readonly routeRisk: {
52
+ readonly readOnlyMethodIds: readonly string[];
53
+ readonly mutatingMethodIds: readonly string[];
54
+ readonly authenticatedMethodIds: readonly string[];
45
55
  readonly readOnlyMethodCount: number;
46
56
  readonly mutatingMethodCount: number;
47
57
  readonly authenticatedMethodCount: number;
@@ -67,6 +77,64 @@ export interface DaemonCapabilityAuditSuccess {
67
77
  readonly areas: readonly DaemonCapabilityAuditArea[];
68
78
  }
69
79
 
80
+ export interface DaemonCapabilityGap {
81
+ readonly id: string;
82
+ readonly kind: DaemonCapabilityGapKind;
83
+ readonly severity: DaemonCapabilityGapSeverity;
84
+ readonly areaId?: string;
85
+ readonly title: string;
86
+ readonly detail: string;
87
+ readonly action: string;
88
+ }
89
+
90
+ export interface DaemonCapabilityGapReport {
91
+ readonly ok: true;
92
+ readonly kind: 'daemon.capabilities.gaps';
93
+ readonly baseUrl: string;
94
+ readonly daemonVersion: string;
95
+ readonly expectedSdkVersion: string;
96
+ readonly daemonCompatible: boolean;
97
+ readonly methodCatalogRoute: typeof DAEMON_METHOD_CATALOG_ROUTE;
98
+ readonly agentKnowledgeRoute: typeof AGENT_KNOWLEDGE_STATUS_ROUTE;
99
+ readonly agentKnowledgeRouteReady: boolean;
100
+ readonly defaultKnowledgeFallback: false;
101
+ readonly homeGraphFallback: false;
102
+ readonly gapCount: number;
103
+ readonly gaps: readonly DaemonCapabilityGap[];
104
+ }
105
+
106
+ export interface DaemonCapabilityRouteRiskArea {
107
+ readonly areaId: string;
108
+ readonly title: string;
109
+ readonly coverage: DaemonCapabilityCoverage;
110
+ readonly readOnlyMethodIds: readonly string[];
111
+ readonly mutatingMethodIds: readonly string[];
112
+ readonly authenticatedMethodIds: readonly string[];
113
+ readonly readOnlyMethodCount: number;
114
+ readonly mutatingMethodCount: number;
115
+ readonly authenticatedMethodCount: number;
116
+ readonly dangerousMethodIds: readonly string[];
117
+ }
118
+
119
+ export interface DaemonCapabilityRouteRiskReport {
120
+ readonly ok: true;
121
+ readonly kind: 'daemon.capabilities.route_risk';
122
+ readonly baseUrl: string;
123
+ readonly daemonVersion: string;
124
+ readonly expectedSdkVersion: string;
125
+ readonly daemonCompatible: boolean;
126
+ readonly methodCatalogRoute: typeof DAEMON_METHOD_CATALOG_ROUTE;
127
+ readonly agentKnowledgeRoute: typeof AGENT_KNOWLEDGE_STATUS_ROUTE;
128
+ readonly agentKnowledgeRouteReady: boolean;
129
+ readonly defaultKnowledgeFallback: false;
130
+ readonly homeGraphFallback: false;
131
+ readonly totalReadOnlyMethodCount: number;
132
+ readonly totalMutatingMethodCount: number;
133
+ readonly totalAuthenticatedMethodCount: number;
134
+ readonly totalDangerousMethodCount: number;
135
+ readonly areas: readonly DaemonCapabilityRouteRiskArea[];
136
+ }
137
+
70
138
  export interface DaemonCapabilityAuditFailure {
71
139
  readonly ok: false;
72
140
  readonly kind: DaemonCapabilityAuditFailureKind;
@@ -419,15 +487,17 @@ export function buildDaemonCapabilityAuditAreas(
419
487
  const method = methodsById.get(methodId);
420
488
  return method ? [method] : [];
421
489
  });
422
- const readOnlyMethodCount = areaMethods.filter((method) => {
490
+ const readOnlyMethodIds = areaMethods.filter((method) => {
423
491
  const verb = method.http?.method?.toUpperCase();
424
492
  return verb === 'GET' || verb === 'HEAD';
425
- }).length;
426
- const mutatingMethodCount = areaMethods.filter((method) => {
493
+ }).map((method) => method.id);
494
+ const mutatingMethodIds = areaMethods.filter((method) => {
427
495
  const verb = method.http?.method?.toUpperCase();
428
496
  return Boolean(verb) && verb !== 'GET' && verb !== 'HEAD';
429
- }).length;
430
- const authenticatedMethodCount = areaMethods.filter((method) => method.access === 'authenticated').length;
497
+ }).map((method) => method.id);
498
+ const authenticatedMethodIds = areaMethods
499
+ .filter((method) => method.access === 'authenticated')
500
+ .map((method) => method.id);
431
501
  const dangerousMethodIds = areaMethods
432
502
  .filter((method) => method.dangerous === true)
433
503
  .map((method) => method.id);
@@ -451,9 +521,12 @@ export function buildDaemonCapabilityAuditAreas(
451
521
  missingOptionalMethodIds,
452
522
  agentRoutes,
453
523
  routeRisk: {
454
- readOnlyMethodCount,
455
- mutatingMethodCount,
456
- authenticatedMethodCount,
524
+ readOnlyMethodIds,
525
+ mutatingMethodIds,
526
+ authenticatedMethodIds,
527
+ readOnlyMethodCount: readOnlyMethodIds.length,
528
+ mutatingMethodCount: mutatingMethodIds.length,
529
+ authenticatedMethodCount: authenticatedMethodIds.length,
457
530
  dangerousMethodIds,
458
531
  },
459
532
  next: requirement.next,
@@ -531,6 +604,259 @@ export function filterDaemonCapabilityAuditAreas(
531
604
  });
532
605
  }
533
606
 
607
+ function gapToken(value: string): string {
608
+ return value
609
+ .toLowerCase()
610
+ .replace(/[^a-z0-9]+/g, '-')
611
+ .replace(/^-+|-+$/g, '')
612
+ || 'gap';
613
+ }
614
+
615
+ function gapSeverityRank(severity: DaemonCapabilityGapSeverity): number {
616
+ if (severity === 'blocker') return 0;
617
+ if (severity === 'high') return 1;
618
+ if (severity === 'medium') return 2;
619
+ return 3;
620
+ }
621
+
622
+ function sortCapabilityGaps(gaps: readonly DaemonCapabilityGap[]): readonly DaemonCapabilityGap[] {
623
+ return [...gaps].sort((left, right) => {
624
+ const severityDelta = gapSeverityRank(left.severity) - gapSeverityRank(right.severity);
625
+ if (severityDelta !== 0) return severityDelta;
626
+ return left.id.localeCompare(right.id);
627
+ });
628
+ }
629
+
630
+ export function buildDaemonCapabilityGapReport(
631
+ audit: DaemonCapabilityAuditSuccess,
632
+ areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
633
+ ): DaemonCapabilityGapReport {
634
+ const gaps: DaemonCapabilityGap[] = [];
635
+
636
+ if (!audit.daemonCompatible) {
637
+ gaps.push({
638
+ id: 'daemon-version-mismatch',
639
+ kind: 'version_mismatch',
640
+ severity: audit.agentKnowledgeRouteReady ? 'high' : 'blocker',
641
+ title: 'Daemon SDK version does not match Agent SDK pin',
642
+ detail: `Agent expects ${audit.expectedSdkVersion}; daemon reports ${audit.daemonVersion}.`,
643
+ action: 'Update/restart the externally owned GoodVibes daemon before release validation; Agent will not start it.',
644
+ });
645
+ }
646
+
647
+ for (const area of areas) {
648
+ if (area.missingRequiredMethodIds.length > 0) {
649
+ gaps.push({
650
+ id: `${area.id}-missing-required-methods`,
651
+ kind: 'required_method_missing',
652
+ severity: 'high',
653
+ areaId: area.id,
654
+ title: `${area.title} missing required daemon methods`,
655
+ detail: area.missingRequiredMethodIds.join(', '),
656
+ action: 'Keep the Agent surface read-only or blocked for this area until the public daemon route contract is present.',
657
+ });
658
+ }
659
+
660
+ for (const route of area.agentRoutes) {
661
+ if (route.coverage !== 'missing') continue;
662
+ gaps.push({
663
+ id: `${area.id}-missing-${gapToken(route.route)}`,
664
+ kind: 'agent_route_missing',
665
+ severity: route.route === AGENT_KNOWLEDGE_STATUS_ROUTE ? 'blocker' : 'high',
666
+ areaId: area.id,
667
+ title: `${area.title} missing Agent route`,
668
+ detail: route.route,
669
+ action: 'Fail closed for this product segment. Do not query default Knowledge/Wiki, HomeGraph, or Home Assistant routes.',
670
+ });
671
+ }
672
+
673
+ if (area.routeRisk.dangerousMethodIds.length > 0) {
674
+ gaps.push({
675
+ id: `${area.id}-dangerous-route-review`,
676
+ kind: 'route_risk_review',
677
+ severity: 'medium',
678
+ areaId: area.id,
679
+ title: `${area.title} has dangerous daemon routes`,
680
+ detail: area.routeRisk.dangerousMethodIds.join(', '),
681
+ action: 'Keep these routes behind exact commands, confirmation, and concise approval UX; never trigger them from ordinary chat.',
682
+ });
683
+ }
684
+
685
+ for (const next of area.next) {
686
+ gaps.push({
687
+ id: `${area.id}-agent-ux-${gapToken(next)}`,
688
+ kind: 'agent_ux_gap',
689
+ severity: area.coverage === 'ready' ? 'medium' : 'low',
690
+ areaId: area.id,
691
+ title: `${area.title} Agent UX gap`,
692
+ detail: next,
693
+ action: 'Build a first-class Agent workspace, command, or setup flow on top of the existing daemon capability.',
694
+ });
695
+ }
696
+ }
697
+
698
+ const sortedGaps = sortCapabilityGaps(gaps);
699
+ return {
700
+ ok: true,
701
+ kind: 'daemon.capabilities.gaps',
702
+ baseUrl: audit.baseUrl,
703
+ daemonVersion: audit.daemonVersion,
704
+ expectedSdkVersion: audit.expectedSdkVersion,
705
+ daemonCompatible: audit.daemonCompatible,
706
+ methodCatalogRoute: audit.methodCatalogRoute,
707
+ agentKnowledgeRoute: audit.agentKnowledgeRoute,
708
+ agentKnowledgeRouteReady: audit.agentKnowledgeRouteReady,
709
+ defaultKnowledgeFallback: false,
710
+ homeGraphFallback: false,
711
+ gapCount: sortedGaps.length,
712
+ gaps: sortedGaps,
713
+ };
714
+ }
715
+
716
+ export function filterDaemonCapabilityGaps(
717
+ gaps: readonly DaemonCapabilityGap[],
718
+ query: string | undefined,
719
+ ): readonly DaemonCapabilityGap[] {
720
+ const normalized = query?.trim().toLowerCase();
721
+ if (!normalized) return gaps;
722
+ return gaps.filter((gap) => {
723
+ return gap.id.includes(normalized)
724
+ || gap.kind.includes(normalized)
725
+ || gap.severity.includes(normalized)
726
+ || gap.title.toLowerCase().includes(normalized)
727
+ || gap.detail.toLowerCase().includes(normalized)
728
+ || gap.action.toLowerCase().includes(normalized)
729
+ || Boolean(gap.areaId?.includes(normalized));
730
+ });
731
+ }
732
+
733
+ export function renderDaemonCapabilityGaps(
734
+ report: DaemonCapabilityGapReport,
735
+ gaps: readonly DaemonCapabilityGap[] = report.gaps,
736
+ ): string {
737
+ const lines: string[] = [
738
+ 'GoodVibes daemon capability gaps',
739
+ ` daemon: ${report.baseUrl}`,
740
+ ` SDK: Agent expects ${report.expectedSdkVersion}; daemon reports ${report.daemonVersion}`,
741
+ ` compatibility: ${report.daemonCompatible ? 'matched' : 'mismatch'}`,
742
+ ` Agent Knowledge: ${report.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${report.agentKnowledgeRoute}`,
743
+ ' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
744
+ ` gaps: ${gaps.length}/${report.gapCount}`,
745
+ '',
746
+ ];
747
+
748
+ if (gaps.length === 0) {
749
+ lines.push('No daemon capability gaps matched this query.');
750
+ return lines.join('\n');
751
+ }
752
+
753
+ for (const gap of gaps) {
754
+ lines.push(`${gap.title} [${gap.severity}; ${gap.kind}]`);
755
+ if (gap.areaId) lines.push(` area: ${gap.areaId}`);
756
+ lines.push(` detail: ${gap.detail}`);
757
+ lines.push(` action: ${gap.action}`);
758
+ lines.push('');
759
+ }
760
+
761
+ return lines.join('\n').trimEnd();
762
+ }
763
+
764
+ export function buildDaemonCapabilityRouteRiskReport(
765
+ audit: DaemonCapabilityAuditSuccess,
766
+ areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
767
+ ): DaemonCapabilityRouteRiskReport {
768
+ const riskAreas = areas.map((area): DaemonCapabilityRouteRiskArea => ({
769
+ areaId: area.id,
770
+ title: area.title,
771
+ coverage: area.coverage,
772
+ readOnlyMethodIds: area.routeRisk.readOnlyMethodIds,
773
+ mutatingMethodIds: area.routeRisk.mutatingMethodIds,
774
+ authenticatedMethodIds: area.routeRisk.authenticatedMethodIds,
775
+ readOnlyMethodCount: area.routeRisk.readOnlyMethodCount,
776
+ mutatingMethodCount: area.routeRisk.mutatingMethodCount,
777
+ authenticatedMethodCount: area.routeRisk.authenticatedMethodCount,
778
+ dangerousMethodIds: area.routeRisk.dangerousMethodIds,
779
+ }));
780
+ const readOnlyMethodIds = new Set(riskAreas.flatMap((area) => area.readOnlyMethodIds));
781
+ const mutatingMethodIds = new Set(riskAreas.flatMap((area) => area.mutatingMethodIds));
782
+ const authenticatedMethodIds = new Set(riskAreas.flatMap((area) => area.authenticatedMethodIds));
783
+ const dangerousMethodIds = new Set(riskAreas.flatMap((area) => area.dangerousMethodIds));
784
+
785
+ return {
786
+ ok: true,
787
+ kind: 'daemon.capabilities.route_risk',
788
+ baseUrl: audit.baseUrl,
789
+ daemonVersion: audit.daemonVersion,
790
+ expectedSdkVersion: audit.expectedSdkVersion,
791
+ daemonCompatible: audit.daemonCompatible,
792
+ methodCatalogRoute: audit.methodCatalogRoute,
793
+ agentKnowledgeRoute: audit.agentKnowledgeRoute,
794
+ agentKnowledgeRouteReady: audit.agentKnowledgeRouteReady,
795
+ defaultKnowledgeFallback: false,
796
+ homeGraphFallback: false,
797
+ totalReadOnlyMethodCount: readOnlyMethodIds.size,
798
+ totalMutatingMethodCount: mutatingMethodIds.size,
799
+ totalAuthenticatedMethodCount: authenticatedMethodIds.size,
800
+ totalDangerousMethodCount: dangerousMethodIds.size,
801
+ areas: riskAreas,
802
+ };
803
+ }
804
+
805
+ export function filterDaemonCapabilityRouteRiskAreas(
806
+ areas: readonly DaemonCapabilityRouteRiskArea[],
807
+ query: string | undefined,
808
+ ): readonly DaemonCapabilityRouteRiskArea[] {
809
+ const normalized = query?.trim().toLowerCase();
810
+ if (!normalized) return areas;
811
+ return areas.filter((area) => {
812
+ return area.areaId.includes(normalized)
813
+ || area.title.toLowerCase().includes(normalized)
814
+ || area.coverage.includes(normalized)
815
+ || area.dangerousMethodIds.some((methodId) => methodId.includes(normalized));
816
+ });
817
+ }
818
+
819
+ export function renderDaemonCapabilityRouteRisk(
820
+ report: DaemonCapabilityRouteRiskReport,
821
+ areas: readonly DaemonCapabilityRouteRiskArea[] = report.areas,
822
+ ): string {
823
+ const lines: string[] = [
824
+ 'GoodVibes daemon route risk review',
825
+ ` daemon: ${report.baseUrl}`,
826
+ ` SDK: Agent expects ${report.expectedSdkVersion}; daemon reports ${report.daemonVersion}`,
827
+ ` compatibility: ${report.daemonCompatible ? 'matched' : 'mismatch'}`,
828
+ ` method catalog: ${report.methodCatalogRoute}`,
829
+ ` Agent Knowledge: ${report.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${report.agentKnowledgeRoute}`,
830
+ ' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
831
+ ` totals: ${report.totalReadOnlyMethodCount} read-only; ${report.totalMutatingMethodCount} mutating; ${report.totalDangerousMethodCount} dangerous; ${report.totalAuthenticatedMethodCount} authenticated`,
832
+ ' policy: exact command plus confirmation for side effects; ordinary chat never triggers mutating routes',
833
+ '',
834
+ ];
835
+
836
+ const visibleAreas = areas.filter((area) => {
837
+ return area.readOnlyMethodCount > 0
838
+ || area.mutatingMethodCount > 0
839
+ || area.authenticatedMethodCount > 0
840
+ || area.dangerousMethodIds.length > 0;
841
+ });
842
+ if (visibleAreas.length === 0) {
843
+ lines.push('No route risk metadata matched this query.');
844
+ return lines.join('\n');
845
+ }
846
+
847
+ for (const area of visibleAreas) {
848
+ lines.push(`${area.title} [${area.coverage}]`);
849
+ lines.push(` methods: ${area.readOnlyMethodCount} read-only; ${area.mutatingMethodCount} mutating; ${area.dangerousMethodIds.length} dangerous; ${area.authenticatedMethodCount} authenticated`);
850
+ if (area.dangerousMethodIds.length > 0) {
851
+ lines.push(` dangerous methods: ${area.dangerousMethodIds.join(', ')}`);
852
+ }
853
+ lines.push(' approval posture: read-only by default; exact command and confirmation required for side effects.');
854
+ lines.push('');
855
+ }
856
+
857
+ return lines.join('\n').trimEnd();
858
+ }
859
+
534
860
  export function renderDaemonCapabilityAudit(
535
861
  audit: DaemonCapabilityAuditSuccess,
536
862
  areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.1.43';
9
+ let _version = '0.1.45';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {