@pellux/goodvibes-agent 0.1.42 → 0.1.44
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 +8 -0
- package/README.md +4 -2
- package/docs/operator-capability-benchmark.md +4 -2
- package/package.json +1 -1
- package/src/cli/capabilities-command.ts +32 -1
- package/src/cli/help.ts +4 -3
- package/src/input/agent-workspace.ts +1 -0
- package/src/input/commands/capabilities-runtime.ts +25 -1
- package/src/operator/daemon-capability-audit.ts +229 -2
- package/src/version.ts +1 -1
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.44 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- fdee09e Add daemon capability gap report
|
|
8
|
+
|
|
9
|
+
## 0.1.43 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 3afba9c Add daemon route risk coverage
|
|
12
|
+
|
|
5
13
|
## 0.1.42 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- 3c84649 Surface daemon capabilities in Agent workspace
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ 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
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
If Bun reports untrusted lifecycle dependencies, trust only the package and dependencies required by this package:
|
|
@@ -45,7 +46,7 @@ bun run publish:check
|
|
|
45
46
|
|
|
46
47
|
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
48
|
|
|
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 and isolated Agent Knowledge route coverage.
|
|
49
|
+
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.
|
|
49
50
|
|
|
50
51
|
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
52
|
|
|
@@ -87,9 +88,10 @@ To verify what the running daemon can expose for the Agent/OpenClaw/Hermes capab
|
|
|
87
88
|
```sh
|
|
88
89
|
goodvibes-agent capabilities daemon
|
|
89
90
|
goodvibes-agent capabilities daemon --json
|
|
91
|
+
goodvibes-agent capabilities daemon gaps
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
This audit checks `/api/control-plane/methods` and `/api/goodvibes-agent/knowledge/status`.
|
|
94
|
+
This audit checks `/api/control-plane/methods` and `/api/goodvibes-agent/knowledge/status`. The gap plan is derived from the same read-only calls. Neither command queries default Knowledge/Wiki or HomeGraph.
|
|
93
95
|
|
|
94
96
|
Agent intentionally blocks daemon lifecycle commands:
|
|
95
97
|
|
|
@@ -17,6 +17,7 @@ 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
|
|
20
21
|
goodvibes-agent capabilities daemon knowledge
|
|
21
22
|
```
|
|
22
23
|
|
|
@@ -27,6 +28,7 @@ Inside the TUI:
|
|
|
27
28
|
/capabilities openclaw
|
|
28
29
|
/capabilities knowledge
|
|
29
30
|
/capabilities daemon
|
|
31
|
+
/capabilities daemon gaps
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
## Research Baseline
|
|
@@ -56,7 +58,7 @@ The benchmark measures two different GoodVibes layers:
|
|
|
56
58
|
|
|
57
59
|
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
60
|
|
|
59
|
-
Use `goodvibes-agent capabilities daemon` for the live read-only daemon audit. It checks the public control-plane method catalog and the isolated Agent Knowledge status route.
|
|
61
|
+
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. Both commands intentionally avoid default `/api/knowledge/*`, HomeGraph, and Home Assistant routes.
|
|
60
62
|
|
|
61
63
|
## Capability Targets
|
|
62
64
|
|
|
@@ -78,7 +80,7 @@ Use `goodvibes-agent capabilities daemon` for the live read-only daemon audit. I
|
|
|
78
80
|
|
|
79
81
|
GoodVibes Agent should exceed OpenClaw/Hermes by making these properties true from day one:
|
|
80
82
|
|
|
81
|
-
- Capability surfaces are discoverable through `goodvibes-agent capabilities`, `goodvibes-agent capabilities daemon`, `/capabilities`, `/capabilities daemon`, onboarding, and the operator workspace.
|
|
83
|
+
- Capability surfaces are discoverable through `goodvibes-agent capabilities`, `goodvibes-agent capabilities daemon`, `goodvibes-agent capabilities daemon gaps`, `/capabilities`, `/capabilities daemon`, onboarding, and the operator workspace.
|
|
82
84
|
- Agent Knowledge isolation is a release gate, not a convention.
|
|
83
85
|
- Routine-to-schedule promotion preserves Agent Knowledge isolation, uses only public external daemon schedule routes, supports explicit delivery targets, and stores redacted receipts.
|
|
84
86
|
- Model-visible tools are policy-gated for serial, non-secret, non-destructive use.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
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,32 @@ import {
|
|
|
6
6
|
renderOperatorCapabilityBenchmark,
|
|
7
7
|
} from '../operator/capability-benchmark.ts';
|
|
8
8
|
import {
|
|
9
|
+
buildDaemonCapabilityGapReport,
|
|
9
10
|
fetchLiveDaemonCapabilityAudit,
|
|
10
11
|
filterDaemonCapabilityAuditAreas,
|
|
12
|
+
filterDaemonCapabilityGaps,
|
|
11
13
|
renderDaemonCapabilityAudit,
|
|
12
14
|
renderDaemonCapabilityFailure,
|
|
15
|
+
renderDaemonCapabilityGaps,
|
|
13
16
|
} from '../operator/daemon-capability-audit.ts';
|
|
14
17
|
import { resolveAgentDaemonConnection } from '../agent/routine-schedule-promotion.ts';
|
|
15
18
|
|
|
16
19
|
interface CapabilityCommandArgs {
|
|
17
|
-
readonly mode: 'benchmark' | 'daemon';
|
|
20
|
+
readonly mode: 'benchmark' | 'daemon' | 'daemon-gaps';
|
|
18
21
|
readonly query: string | undefined;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function readCapabilityArgs(args: readonly string[]): CapabilityCommandArgs {
|
|
22
25
|
const values = args.filter((arg) => !arg.startsWith('--'));
|
|
26
|
+
if (values[0] === 'gaps') {
|
|
27
|
+
const query = values.slice(1).join(' ').trim();
|
|
28
|
+
return { mode: 'daemon-gaps', query: query.length > 0 ? query : undefined };
|
|
29
|
+
}
|
|
23
30
|
if (values[0] === 'daemon') {
|
|
31
|
+
if (values[1] === 'gaps') {
|
|
32
|
+
const query = values.slice(2).join(' ').trim();
|
|
33
|
+
return { mode: 'daemon-gaps', query: query.length > 0 ? query : undefined };
|
|
34
|
+
}
|
|
24
35
|
const query = values.slice(1).join(' ').trim();
|
|
25
36
|
return { mode: 'daemon', query: query.length > 0 ? query : undefined };
|
|
26
37
|
}
|
|
@@ -48,6 +59,26 @@ export async function handleCapabilitiesCommand(runtime: CliCommandRuntime): Pro
|
|
|
48
59
|
exitCode: 0,
|
|
49
60
|
};
|
|
50
61
|
}
|
|
62
|
+
if (args.mode === 'daemon-gaps') {
|
|
63
|
+
const connection = resolveAgentDaemonConnection(runtime.configManager, runtime.homeDirectory);
|
|
64
|
+
const audit = await fetchLiveDaemonCapabilityAudit(connection);
|
|
65
|
+
if (!audit.ok) {
|
|
66
|
+
return {
|
|
67
|
+
output: runtime.cli.flags.outputFormat === 'json'
|
|
68
|
+
? JSON.stringify(audit, null, 2)
|
|
69
|
+
: renderDaemonCapabilityFailure(audit),
|
|
70
|
+
exitCode: audit.kind === 'auth_required' || audit.kind === 'daemon_unavailable' ? 1 : 2,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const report = buildDaemonCapabilityGapReport(audit);
|
|
74
|
+
const gaps = filterDaemonCapabilityGaps(report.gaps, args.query);
|
|
75
|
+
return {
|
|
76
|
+
output: runtime.cli.flags.outputFormat === 'json'
|
|
77
|
+
? JSON.stringify({ ...report, matchedGapCount: gaps.length, gaps }, null, 2)
|
|
78
|
+
: renderDaemonCapabilityGaps(report, gaps),
|
|
79
|
+
exitCode: 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
51
82
|
const report = buildOperatorCapabilityBenchmarkReport();
|
|
52
83
|
const capabilities = filterOperatorCapabilities(report.capabilities, args.query);
|
|
53
84
|
if (runtime.cli.flags.outputFormat === 'json') {
|
package/src/cli/help.ts
CHANGED
|
@@ -103,6 +103,7 @@ 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`,
|
|
106
107
|
` ${binary} knowledge status`,
|
|
107
108
|
` ${binary} knowledge ask "What is GoodVibes Agent?"`,
|
|
108
109
|
` ${binary} ask "What is GoodVibes Agent?"`,
|
|
@@ -197,9 +198,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
197
198
|
examples: ['compat', 'compat --json'],
|
|
198
199
|
},
|
|
199
200
|
capabilities: {
|
|
200
|
-
usage: ['capabilities [openclaw|hermes|query]', 'capabilities daemon [query]', 'capabilities --json'],
|
|
201
|
-
summary: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness,
|
|
202
|
-
examples: ['capabilities', 'capabilities hermes', 'capabilities daemon', 'capabilities daemon knowledge --json'],
|
|
201
|
+
usage: ['capabilities [openclaw|hermes|query]', 'capabilities daemon [query]', 'capabilities daemon gaps [query]', 'capabilities --json'],
|
|
202
|
+
summary: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness, live GoodVibes daemon method coverage, and daemon-measured product gaps.',
|
|
203
|
+
examples: ['capabilities', 'capabilities hermes', 'capabilities daemon', 'capabilities daemon gaps', 'capabilities daemon knowledge --json'],
|
|
203
204
|
},
|
|
204
205
|
knowledge: {
|
|
205
206
|
usage: [
|
|
@@ -489,6 +489,7 @@ 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' },
|
|
492
493
|
{ 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
494
|
{ 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
495
|
{ 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' },
|
|
@@ -5,10 +5,13 @@ import {
|
|
|
5
5
|
OPERATOR_CAPABILITY_BENCHMARKS,
|
|
6
6
|
} from '../../operator/capability-benchmark.ts';
|
|
7
7
|
import {
|
|
8
|
+
buildDaemonCapabilityGapReport,
|
|
8
9
|
fetchLiveDaemonCapabilityAudit,
|
|
9
10
|
filterDaemonCapabilityAuditAreas,
|
|
11
|
+
filterDaemonCapabilityGaps,
|
|
10
12
|
renderDaemonCapabilityAudit,
|
|
11
13
|
renderDaemonCapabilityFailure,
|
|
14
|
+
renderDaemonCapabilityGaps,
|
|
12
15
|
} from '../../operator/daemon-capability-audit.ts';
|
|
13
16
|
import { resolveAgentDaemonConnection } from '../../agent/routine-schedule-promotion.ts';
|
|
14
17
|
|
|
@@ -17,7 +20,7 @@ export function registerCapabilitiesRuntimeCommands(registry: CommandRegistry):
|
|
|
17
20
|
name: 'capabilities',
|
|
18
21
|
aliases: ['caps', 'benchmark'],
|
|
19
22
|
description: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness, and live daemon coverage',
|
|
20
|
-
usage: '[daemon|openclaw|hermes|query]',
|
|
23
|
+
usage: '[daemon|gaps|openclaw|hermes|query]',
|
|
21
24
|
async handler(args, ctx) {
|
|
22
25
|
if (args[0] === 'daemon') {
|
|
23
26
|
const homeDirectory = ctx.platform.configManager.getHomeDirectory() ?? process.cwd();
|
|
@@ -27,11 +30,32 @@ export function registerCapabilitiesRuntimeCommands(registry: CommandRegistry):
|
|
|
27
30
|
ctx.print(renderDaemonCapabilityFailure(audit));
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
33
|
+
if (args[1] === 'gaps') {
|
|
34
|
+
const report = buildDaemonCapabilityGapReport(audit);
|
|
35
|
+
const query = args.slice(2).join(' ').trim() || undefined;
|
|
36
|
+
const gaps = filterDaemonCapabilityGaps(report.gaps, query);
|
|
37
|
+
ctx.print(renderDaemonCapabilityGaps(report, gaps));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
30
40
|
const query = args.slice(1).join(' ').trim() || undefined;
|
|
31
41
|
const areas = filterDaemonCapabilityAuditAreas(audit.areas, query);
|
|
32
42
|
ctx.print(renderDaemonCapabilityAudit(audit, areas));
|
|
33
43
|
return;
|
|
34
44
|
}
|
|
45
|
+
if (args[0] === 'gaps') {
|
|
46
|
+
const homeDirectory = ctx.platform.configManager.getHomeDirectory() ?? process.cwd();
|
|
47
|
+
const connection = resolveAgentDaemonConnection(ctx.platform.configManager, homeDirectory);
|
|
48
|
+
const audit = await fetchLiveDaemonCapabilityAudit(connection);
|
|
49
|
+
if (!audit.ok) {
|
|
50
|
+
ctx.print(renderDaemonCapabilityFailure(audit));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const report = buildDaemonCapabilityGapReport(audit);
|
|
54
|
+
const query = args.slice(1).join(' ').trim() || undefined;
|
|
55
|
+
const gaps = filterDaemonCapabilityGaps(report.gaps, query);
|
|
56
|
+
ctx.print(renderDaemonCapabilityGaps(report, gaps));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
35
59
|
const query = args.join(' ').trim() || undefined;
|
|
36
60
|
const capabilities = filterOperatorCapabilities(OPERATOR_CAPABILITY_BENCHMARKS, query);
|
|
37
61
|
ctx.print(renderOperatorCapabilityBenchmark(capabilities));
|
|
@@ -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;
|
|
@@ -41,6 +48,12 @@ export interface DaemonCapabilityAuditArea {
|
|
|
41
48
|
readonly route: string;
|
|
42
49
|
readonly coverage: DaemonCapabilityRouteCoverage;
|
|
43
50
|
}[];
|
|
51
|
+
readonly routeRisk: {
|
|
52
|
+
readonly readOnlyMethodCount: number;
|
|
53
|
+
readonly mutatingMethodCount: number;
|
|
54
|
+
readonly authenticatedMethodCount: number;
|
|
55
|
+
readonly dangerousMethodIds: readonly string[];
|
|
56
|
+
};
|
|
44
57
|
readonly next: readonly string[];
|
|
45
58
|
}
|
|
46
59
|
|
|
@@ -61,6 +74,32 @@ export interface DaemonCapabilityAuditSuccess {
|
|
|
61
74
|
readonly areas: readonly DaemonCapabilityAuditArea[];
|
|
62
75
|
}
|
|
63
76
|
|
|
77
|
+
export interface DaemonCapabilityGap {
|
|
78
|
+
readonly id: string;
|
|
79
|
+
readonly kind: DaemonCapabilityGapKind;
|
|
80
|
+
readonly severity: DaemonCapabilityGapSeverity;
|
|
81
|
+
readonly areaId?: string;
|
|
82
|
+
readonly title: string;
|
|
83
|
+
readonly detail: string;
|
|
84
|
+
readonly action: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DaemonCapabilityGapReport {
|
|
88
|
+
readonly ok: true;
|
|
89
|
+
readonly kind: 'daemon.capabilities.gaps';
|
|
90
|
+
readonly baseUrl: string;
|
|
91
|
+
readonly daemonVersion: string;
|
|
92
|
+
readonly expectedSdkVersion: string;
|
|
93
|
+
readonly daemonCompatible: boolean;
|
|
94
|
+
readonly methodCatalogRoute: typeof DAEMON_METHOD_CATALOG_ROUTE;
|
|
95
|
+
readonly agentKnowledgeRoute: typeof AGENT_KNOWLEDGE_STATUS_ROUTE;
|
|
96
|
+
readonly agentKnowledgeRouteReady: boolean;
|
|
97
|
+
readonly defaultKnowledgeFallback: false;
|
|
98
|
+
readonly homeGraphFallback: false;
|
|
99
|
+
readonly gapCount: number;
|
|
100
|
+
readonly gaps: readonly DaemonCapabilityGap[];
|
|
101
|
+
}
|
|
102
|
+
|
|
64
103
|
export interface DaemonCapabilityAuditFailure {
|
|
65
104
|
readonly ok: false;
|
|
66
105
|
readonly kind: DaemonCapabilityAuditFailureKind;
|
|
@@ -75,12 +114,13 @@ export type DaemonCapabilityAuditResult =
|
|
|
75
114
|
| DaemonCapabilityAuditSuccess
|
|
76
115
|
| DaemonCapabilityAuditFailure;
|
|
77
116
|
|
|
78
|
-
interface DaemonMethodSummary {
|
|
117
|
+
export interface DaemonMethodSummary {
|
|
79
118
|
readonly id: string;
|
|
80
119
|
readonly title?: string;
|
|
81
120
|
readonly category?: string;
|
|
82
121
|
readonly invokable?: boolean;
|
|
83
122
|
readonly access?: string;
|
|
123
|
+
readonly dangerous?: boolean;
|
|
84
124
|
readonly http?: {
|
|
85
125
|
readonly method?: string;
|
|
86
126
|
readonly path?: string;
|
|
@@ -294,6 +334,7 @@ function readMethodSummaries(body: unknown): readonly DaemonMethodSummary[] {
|
|
|
294
334
|
category: readString(value, 'category') ?? undefined,
|
|
295
335
|
access: readString(value, 'access') ?? undefined,
|
|
296
336
|
invokable: typeof value.invokable === 'boolean' ? value.invokable : undefined,
|
|
337
|
+
dangerous: typeof value.dangerous === 'boolean' ? value.dangerous : undefined,
|
|
297
338
|
http: httpRecord
|
|
298
339
|
? {
|
|
299
340
|
method: readString(httpRecord, 'method') ?? undefined,
|
|
@@ -389,7 +430,9 @@ function failureFromThrown(error: unknown, connection: AgentDaemonConnection, ro
|
|
|
389
430
|
export function buildDaemonCapabilityAuditAreas(
|
|
390
431
|
methodIds: ReadonlySet<string>,
|
|
391
432
|
agentKnowledgeRouteReady: boolean | null,
|
|
433
|
+
methodSummaries: readonly DaemonMethodSummary[] = [],
|
|
392
434
|
): readonly DaemonCapabilityAuditArea[] {
|
|
435
|
+
const methodsById = new Map(methodSummaries.map((method) => [method.id, method]));
|
|
393
436
|
return DAEMON_CAPABILITY_REQUIREMENTS.map((requirement) => {
|
|
394
437
|
const presentRequiredMethodIds = requirement.requiredMethodIds.filter((methodId) => methodIds.has(methodId));
|
|
395
438
|
const missingRequiredMethodIds = requirement.requiredMethodIds.filter((methodId) => !methodIds.has(methodId));
|
|
@@ -404,6 +447,23 @@ export function buildDaemonCapabilityAuditAreas(
|
|
|
404
447
|
: 'missing',
|
|
405
448
|
} satisfies DaemonCapabilityAuditArea['agentRoutes'][number]));
|
|
406
449
|
const missingAgentRoutes = agentRoutes.filter((route) => route.coverage === 'missing');
|
|
450
|
+
const areaMethodIds = [...requirement.requiredMethodIds, ...requirement.optionalMethodIds];
|
|
451
|
+
const areaMethods = areaMethodIds.flatMap((methodId): DaemonMethodSummary[] => {
|
|
452
|
+
const method = methodsById.get(methodId);
|
|
453
|
+
return method ? [method] : [];
|
|
454
|
+
});
|
|
455
|
+
const readOnlyMethodCount = areaMethods.filter((method) => {
|
|
456
|
+
const verb = method.http?.method?.toUpperCase();
|
|
457
|
+
return verb === 'GET' || verb === 'HEAD';
|
|
458
|
+
}).length;
|
|
459
|
+
const mutatingMethodCount = areaMethods.filter((method) => {
|
|
460
|
+
const verb = method.http?.method?.toUpperCase();
|
|
461
|
+
return Boolean(verb) && verb !== 'GET' && verb !== 'HEAD';
|
|
462
|
+
}).length;
|
|
463
|
+
const authenticatedMethodCount = areaMethods.filter((method) => method.access === 'authenticated').length;
|
|
464
|
+
const dangerousMethodIds = areaMethods
|
|
465
|
+
.filter((method) => method.dangerous === true)
|
|
466
|
+
.map((method) => method.id);
|
|
407
467
|
const requiredCount = requirement.requiredMethodIds.length + requirement.requiredAgentRoutes.length;
|
|
408
468
|
const presentRequiredCount = presentRequiredMethodIds.length
|
|
409
469
|
+ agentRoutes.filter((route) => route.coverage === 'ready').length;
|
|
@@ -423,6 +483,12 @@ export function buildDaemonCapabilityAuditAreas(
|
|
|
423
483
|
presentOptionalMethodIds,
|
|
424
484
|
missingOptionalMethodIds,
|
|
425
485
|
agentRoutes,
|
|
486
|
+
routeRisk: {
|
|
487
|
+
readOnlyMethodCount,
|
|
488
|
+
mutatingMethodCount,
|
|
489
|
+
authenticatedMethodCount,
|
|
490
|
+
dangerousMethodIds,
|
|
491
|
+
},
|
|
426
492
|
next: requirement.next,
|
|
427
493
|
};
|
|
428
494
|
});
|
|
@@ -472,7 +538,7 @@ export async function fetchLiveDaemonCapabilityAudit(
|
|
|
472
538
|
defaultKnowledgeFallback: false,
|
|
473
539
|
homeGraphFallback: false,
|
|
474
540
|
warnings,
|
|
475
|
-
areas: buildDaemonCapabilityAuditAreas(methodIds, agentKnowledge.ok),
|
|
541
|
+
areas: buildDaemonCapabilityAuditAreas(methodIds, agentKnowledge.ok, methodSummaries),
|
|
476
542
|
};
|
|
477
543
|
} catch (error) {
|
|
478
544
|
return failureFromThrown(error, connection, daemonVersion === 'unknown' ? DAEMON_STATUS_ROUTE : DAEMON_METHOD_CATALOG_ROUTE);
|
|
@@ -498,6 +564,163 @@ export function filterDaemonCapabilityAuditAreas(
|
|
|
498
564
|
});
|
|
499
565
|
}
|
|
500
566
|
|
|
567
|
+
function gapToken(value: string): string {
|
|
568
|
+
return value
|
|
569
|
+
.toLowerCase()
|
|
570
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
571
|
+
.replace(/^-+|-+$/g, '')
|
|
572
|
+
|| 'gap';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function gapSeverityRank(severity: DaemonCapabilityGapSeverity): number {
|
|
576
|
+
if (severity === 'blocker') return 0;
|
|
577
|
+
if (severity === 'high') return 1;
|
|
578
|
+
if (severity === 'medium') return 2;
|
|
579
|
+
return 3;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function sortCapabilityGaps(gaps: readonly DaemonCapabilityGap[]): readonly DaemonCapabilityGap[] {
|
|
583
|
+
return [...gaps].sort((left, right) => {
|
|
584
|
+
const severityDelta = gapSeverityRank(left.severity) - gapSeverityRank(right.severity);
|
|
585
|
+
if (severityDelta !== 0) return severityDelta;
|
|
586
|
+
return left.id.localeCompare(right.id);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function buildDaemonCapabilityGapReport(
|
|
591
|
+
audit: DaemonCapabilityAuditSuccess,
|
|
592
|
+
areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
|
|
593
|
+
): DaemonCapabilityGapReport {
|
|
594
|
+
const gaps: DaemonCapabilityGap[] = [];
|
|
595
|
+
|
|
596
|
+
if (!audit.daemonCompatible) {
|
|
597
|
+
gaps.push({
|
|
598
|
+
id: 'daemon-version-mismatch',
|
|
599
|
+
kind: 'version_mismatch',
|
|
600
|
+
severity: audit.agentKnowledgeRouteReady ? 'high' : 'blocker',
|
|
601
|
+
title: 'Daemon SDK version does not match Agent SDK pin',
|
|
602
|
+
detail: `Agent expects ${audit.expectedSdkVersion}; daemon reports ${audit.daemonVersion}.`,
|
|
603
|
+
action: 'Update/restart the externally owned GoodVibes daemon before release validation; Agent will not start it.',
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
for (const area of areas) {
|
|
608
|
+
if (area.missingRequiredMethodIds.length > 0) {
|
|
609
|
+
gaps.push({
|
|
610
|
+
id: `${area.id}-missing-required-methods`,
|
|
611
|
+
kind: 'required_method_missing',
|
|
612
|
+
severity: 'high',
|
|
613
|
+
areaId: area.id,
|
|
614
|
+
title: `${area.title} missing required daemon methods`,
|
|
615
|
+
detail: area.missingRequiredMethodIds.join(', '),
|
|
616
|
+
action: 'Keep the Agent surface read-only or blocked for this area until the public daemon route contract is present.',
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
for (const route of area.agentRoutes) {
|
|
621
|
+
if (route.coverage !== 'missing') continue;
|
|
622
|
+
gaps.push({
|
|
623
|
+
id: `${area.id}-missing-${gapToken(route.route)}`,
|
|
624
|
+
kind: 'agent_route_missing',
|
|
625
|
+
severity: route.route === AGENT_KNOWLEDGE_STATUS_ROUTE ? 'blocker' : 'high',
|
|
626
|
+
areaId: area.id,
|
|
627
|
+
title: `${area.title} missing Agent route`,
|
|
628
|
+
detail: route.route,
|
|
629
|
+
action: 'Fail closed for this product segment. Do not query default Knowledge/Wiki, HomeGraph, or Home Assistant routes.',
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (area.routeRisk.dangerousMethodIds.length > 0) {
|
|
634
|
+
gaps.push({
|
|
635
|
+
id: `${area.id}-dangerous-route-review`,
|
|
636
|
+
kind: 'route_risk_review',
|
|
637
|
+
severity: 'medium',
|
|
638
|
+
areaId: area.id,
|
|
639
|
+
title: `${area.title} has dangerous daemon routes`,
|
|
640
|
+
detail: area.routeRisk.dangerousMethodIds.join(', '),
|
|
641
|
+
action: 'Keep these routes behind exact commands, confirmation, and concise approval UX; never trigger them from ordinary chat.',
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const next of area.next) {
|
|
646
|
+
gaps.push({
|
|
647
|
+
id: `${area.id}-agent-ux-${gapToken(next)}`,
|
|
648
|
+
kind: 'agent_ux_gap',
|
|
649
|
+
severity: area.coverage === 'ready' ? 'medium' : 'low',
|
|
650
|
+
areaId: area.id,
|
|
651
|
+
title: `${area.title} Agent UX gap`,
|
|
652
|
+
detail: next,
|
|
653
|
+
action: 'Build a first-class Agent workspace, command, or setup flow on top of the existing daemon capability.',
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const sortedGaps = sortCapabilityGaps(gaps);
|
|
659
|
+
return {
|
|
660
|
+
ok: true,
|
|
661
|
+
kind: 'daemon.capabilities.gaps',
|
|
662
|
+
baseUrl: audit.baseUrl,
|
|
663
|
+
daemonVersion: audit.daemonVersion,
|
|
664
|
+
expectedSdkVersion: audit.expectedSdkVersion,
|
|
665
|
+
daemonCompatible: audit.daemonCompatible,
|
|
666
|
+
methodCatalogRoute: audit.methodCatalogRoute,
|
|
667
|
+
agentKnowledgeRoute: audit.agentKnowledgeRoute,
|
|
668
|
+
agentKnowledgeRouteReady: audit.agentKnowledgeRouteReady,
|
|
669
|
+
defaultKnowledgeFallback: false,
|
|
670
|
+
homeGraphFallback: false,
|
|
671
|
+
gapCount: sortedGaps.length,
|
|
672
|
+
gaps: sortedGaps,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function filterDaemonCapabilityGaps(
|
|
677
|
+
gaps: readonly DaemonCapabilityGap[],
|
|
678
|
+
query: string | undefined,
|
|
679
|
+
): readonly DaemonCapabilityGap[] {
|
|
680
|
+
const normalized = query?.trim().toLowerCase();
|
|
681
|
+
if (!normalized) return gaps;
|
|
682
|
+
return gaps.filter((gap) => {
|
|
683
|
+
return gap.id.includes(normalized)
|
|
684
|
+
|| gap.kind.includes(normalized)
|
|
685
|
+
|| gap.severity.includes(normalized)
|
|
686
|
+
|| gap.title.toLowerCase().includes(normalized)
|
|
687
|
+
|| gap.detail.toLowerCase().includes(normalized)
|
|
688
|
+
|| gap.action.toLowerCase().includes(normalized)
|
|
689
|
+
|| Boolean(gap.areaId?.includes(normalized));
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export function renderDaemonCapabilityGaps(
|
|
694
|
+
report: DaemonCapabilityGapReport,
|
|
695
|
+
gaps: readonly DaemonCapabilityGap[] = report.gaps,
|
|
696
|
+
): string {
|
|
697
|
+
const lines: string[] = [
|
|
698
|
+
'GoodVibes daemon capability gaps',
|
|
699
|
+
` daemon: ${report.baseUrl}`,
|
|
700
|
+
` SDK: Agent expects ${report.expectedSdkVersion}; daemon reports ${report.daemonVersion}`,
|
|
701
|
+
` compatibility: ${report.daemonCompatible ? 'matched' : 'mismatch'}`,
|
|
702
|
+
` Agent Knowledge: ${report.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${report.agentKnowledgeRoute}`,
|
|
703
|
+
' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
|
|
704
|
+
` gaps: ${gaps.length}/${report.gapCount}`,
|
|
705
|
+
'',
|
|
706
|
+
];
|
|
707
|
+
|
|
708
|
+
if (gaps.length === 0) {
|
|
709
|
+
lines.push('No daemon capability gaps matched this query.');
|
|
710
|
+
return lines.join('\n');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
for (const gap of gaps) {
|
|
714
|
+
lines.push(`${gap.title} [${gap.severity}; ${gap.kind}]`);
|
|
715
|
+
if (gap.areaId) lines.push(` area: ${gap.areaId}`);
|
|
716
|
+
lines.push(` detail: ${gap.detail}`);
|
|
717
|
+
lines.push(` action: ${gap.action}`);
|
|
718
|
+
lines.push('');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return lines.join('\n').trimEnd();
|
|
722
|
+
}
|
|
723
|
+
|
|
501
724
|
export function renderDaemonCapabilityAudit(
|
|
502
725
|
audit: DaemonCapabilityAuditSuccess,
|
|
503
726
|
areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
|
|
@@ -528,6 +751,10 @@ export function renderDaemonCapabilityAudit(
|
|
|
528
751
|
lines.push(` optional methods: ${area.presentOptionalMethodIds.length}/${optionalTotal}`);
|
|
529
752
|
if (area.missingOptionalMethodIds.length > 0) lines.push(` missing optional: ${area.missingOptionalMethodIds.join(', ')}`);
|
|
530
753
|
}
|
|
754
|
+
lines.push(` route risk: ${area.routeRisk.readOnlyMethodCount} read-only; ${area.routeRisk.mutatingMethodCount} mutating; ${area.routeRisk.dangerousMethodIds.length} dangerous; ${area.routeRisk.authenticatedMethodCount} authenticated`);
|
|
755
|
+
if (area.routeRisk.dangerousMethodIds.length > 0) {
|
|
756
|
+
lines.push(` dangerous methods: ${area.routeRisk.dangerousMethodIds.join(', ')}`);
|
|
757
|
+
}
|
|
531
758
|
for (const route of area.agentRoutes) lines.push(` route: ${route.route} [${route.coverage}]`);
|
|
532
759
|
lines.push(` next: ${area.next.join(' | ')}`);
|
|
533
760
|
lines.push('');
|
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.
|
|
9
|
+
let _version = '0.1.44';
|
|
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 {
|