@pellux/goodvibes-agent 0.1.44 → 0.1.46
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 +6 -2
- package/docs/operator-capability-benchmark.md +7 -3
- package/package.json +1 -1
- package/src/cli/capabilities-command.ts +54 -1
- package/src/cli/help.ts +5 -3
- package/src/input/agent-workspace.ts +3 -0
- package/src/input/commands/capabilities-runtime.ts +24 -0
- package/src/input/commands/experience-runtime.ts +27 -3
- package/src/operator/daemon-capability-audit.ts +407 -8
- 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.46 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- e0addfe Add full daemon capability inventory
|
|
8
|
+
|
|
9
|
+
## 0.1.45 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 0aa4b3e Add daemon route risk approval review
|
|
12
|
+
|
|
5
13
|
## 0.1.44 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- fdee09e Add daemon capability gap report
|
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ goodvibes-agent status
|
|
|
18
18
|
goodvibes-agent capabilities
|
|
19
19
|
goodvibes-agent capabilities daemon
|
|
20
20
|
goodvibes-agent capabilities daemon gaps
|
|
21
|
+
goodvibes-agent capabilities daemon risk
|
|
22
|
+
goodvibes-agent capabilities daemon inventory
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
If Bun reports untrusted lifecycle dependencies, trust only the package and dependencies required by this package:
|
|
@@ -46,7 +48,7 @@ bun run publish:check
|
|
|
46
48
|
|
|
47
49
|
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.
|
|
48
50
|
|
|
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.
|
|
51
|
+
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 inventory` for the full public daemon method inventory grouped by category, access, HTTP posture, and dangerous flags. 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.
|
|
50
52
|
|
|
51
53
|
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.
|
|
52
54
|
|
|
@@ -89,9 +91,11 @@ To verify what the running daemon can expose for the Agent/OpenClaw/Hermes capab
|
|
|
89
91
|
goodvibes-agent capabilities daemon
|
|
90
92
|
goodvibes-agent capabilities daemon --json
|
|
91
93
|
goodvibes-agent capabilities daemon gaps
|
|
94
|
+
goodvibes-agent capabilities daemon risk
|
|
95
|
+
goodvibes-agent capabilities daemon inventory
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-
This audit checks `/api/control-plane/methods` and `/api/goodvibes-agent/knowledge/status`. The gap plan
|
|
98
|
+
This audit checks `/api/control-plane/methods` and `/api/goodvibes-agent/knowledge/status`. The gap plan, route-risk review, and full method inventory are derived from read-only daemon calls. None of these commands query default Knowledge/Wiki or HomeGraph.
|
|
95
99
|
|
|
96
100
|
Agent intentionally blocks daemon lifecycle commands:
|
|
97
101
|
|
|
@@ -18,6 +18,8 @@ goodvibes-agent capabilities hermes
|
|
|
18
18
|
goodvibes-agent capabilities daemon
|
|
19
19
|
goodvibes-agent capabilities daemon --json
|
|
20
20
|
goodvibes-agent capabilities daemon gaps
|
|
21
|
+
goodvibes-agent capabilities daemon risk
|
|
22
|
+
goodvibes-agent capabilities daemon inventory
|
|
21
23
|
goodvibes-agent capabilities daemon knowledge
|
|
22
24
|
```
|
|
23
25
|
|
|
@@ -29,6 +31,8 @@ Inside the TUI:
|
|
|
29
31
|
/capabilities knowledge
|
|
30
32
|
/capabilities daemon
|
|
31
33
|
/capabilities daemon gaps
|
|
34
|
+
/capabilities daemon inventory
|
|
35
|
+
/approval risk
|
|
32
36
|
```
|
|
33
37
|
|
|
34
38
|
## Research Baseline
|
|
@@ -58,7 +62,7 @@ The benchmark measures two different GoodVibes layers:
|
|
|
58
62
|
|
|
59
63
|
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.
|
|
60
64
|
|
|
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.
|
|
65
|
+
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 inventory` for the full public daemon method inventory grouped by category, access, HTTP method, and dangerous flag. 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.
|
|
62
66
|
|
|
63
67
|
## Capability Targets
|
|
64
68
|
|
|
@@ -80,7 +84,7 @@ Use `goodvibes-agent capabilities daemon` for the live read-only daemon audit. I
|
|
|
80
84
|
|
|
81
85
|
GoodVibes Agent should exceed OpenClaw/Hermes by making these properties true from day one:
|
|
82
86
|
|
|
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.
|
|
87
|
+
- Capability surfaces are discoverable through `goodvibes-agent capabilities`, `goodvibes-agent capabilities daemon`, `goodvibes-agent capabilities daemon inventory`, `goodvibes-agent capabilities daemon gaps`, `goodvibes-agent capabilities daemon risk`, `/capabilities`, `/capabilities daemon`, `/approval risk`, onboarding, and the operator workspace.
|
|
84
88
|
- Agent Knowledge isolation is a release gate, not a convention.
|
|
85
89
|
- Routine-to-schedule promotion preserves Agent Knowledge isolation, uses only public external daemon schedule routes, supports explicit delivery targets, and stores redacted receipts.
|
|
86
90
|
- Model-visible tools are policy-gated for serial, non-secret, non-destructive use.
|
|
@@ -96,5 +100,5 @@ GoodVibes Agent should exceed OpenClaw/Hermes by making these properties true fr
|
|
|
96
100
|
- Artifact and multimodal Agent Knowledge ingestion when the isolated Agent route accepts artifact-backed media.
|
|
97
101
|
- Deeper live run/delivery history and delivery error surfacing for promoted routines.
|
|
98
102
|
- Delegation receipts and artifact review inside the operator workspace.
|
|
99
|
-
-
|
|
103
|
+
- Saved policy presets for the route-risk-aware approval center.
|
|
100
104
|
- 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.
|
|
3
|
+
"version": "0.1.46",
|
|
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",
|
|
@@ -7,17 +7,23 @@ import {
|
|
|
7
7
|
} from '../operator/capability-benchmark.ts';
|
|
8
8
|
import {
|
|
9
9
|
buildDaemonCapabilityGapReport,
|
|
10
|
+
buildDaemonCapabilityRouteRiskReport,
|
|
10
11
|
fetchLiveDaemonCapabilityAudit,
|
|
12
|
+
fetchLiveDaemonCapabilityInventory,
|
|
11
13
|
filterDaemonCapabilityAuditAreas,
|
|
12
14
|
filterDaemonCapabilityGaps,
|
|
15
|
+
filterDaemonCapabilityInventoryGroups,
|
|
16
|
+
filterDaemonCapabilityRouteRiskAreas,
|
|
13
17
|
renderDaemonCapabilityAudit,
|
|
14
18
|
renderDaemonCapabilityFailure,
|
|
15
19
|
renderDaemonCapabilityGaps,
|
|
20
|
+
renderDaemonCapabilityInventory,
|
|
21
|
+
renderDaemonCapabilityRouteRisk,
|
|
16
22
|
} from '../operator/daemon-capability-audit.ts';
|
|
17
23
|
import { resolveAgentDaemonConnection } from '../agent/routine-schedule-promotion.ts';
|
|
18
24
|
|
|
19
25
|
interface CapabilityCommandArgs {
|
|
20
|
-
readonly mode: 'benchmark' | 'daemon' | 'daemon-gaps';
|
|
26
|
+
readonly mode: 'benchmark' | 'daemon' | 'daemon-gaps' | 'daemon-risk' | 'daemon-inventory';
|
|
21
27
|
readonly query: string | undefined;
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -32,6 +38,14 @@ function readCapabilityArgs(args: readonly string[]): CapabilityCommandArgs {
|
|
|
32
38
|
const query = values.slice(2).join(' ').trim();
|
|
33
39
|
return { mode: 'daemon-gaps', query: query.length > 0 ? query : undefined };
|
|
34
40
|
}
|
|
41
|
+
if (values[1] === 'risk' || values[1] === 'route-risk') {
|
|
42
|
+
const query = values.slice(2).join(' ').trim();
|
|
43
|
+
return { mode: 'daemon-risk', query: query.length > 0 ? query : undefined };
|
|
44
|
+
}
|
|
45
|
+
if (values[1] === 'inventory' || values[1] === 'methods' || values[1] === 'routes') {
|
|
46
|
+
const query = values.slice(2).join(' ').trim();
|
|
47
|
+
return { mode: 'daemon-inventory', query: query.length > 0 ? query : undefined };
|
|
48
|
+
}
|
|
35
49
|
const query = values.slice(1).join(' ').trim();
|
|
36
50
|
return { mode: 'daemon', query: query.length > 0 ? query : undefined };
|
|
37
51
|
}
|
|
@@ -79,6 +93,45 @@ export async function handleCapabilitiesCommand(runtime: CliCommandRuntime): Pro
|
|
|
79
93
|
exitCode: 0,
|
|
80
94
|
};
|
|
81
95
|
}
|
|
96
|
+
if (args.mode === 'daemon-risk') {
|
|
97
|
+
const connection = resolveAgentDaemonConnection(runtime.configManager, runtime.homeDirectory);
|
|
98
|
+
const audit = await fetchLiveDaemonCapabilityAudit(connection);
|
|
99
|
+
if (!audit.ok) {
|
|
100
|
+
return {
|
|
101
|
+
output: runtime.cli.flags.outputFormat === 'json'
|
|
102
|
+
? JSON.stringify(audit, null, 2)
|
|
103
|
+
: renderDaemonCapabilityFailure(audit),
|
|
104
|
+
exitCode: audit.kind === 'auth_required' || audit.kind === 'daemon_unavailable' ? 1 : 2,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const report = buildDaemonCapabilityRouteRiskReport(audit);
|
|
108
|
+
const areas = filterDaemonCapabilityRouteRiskAreas(report.areas, args.query);
|
|
109
|
+
return {
|
|
110
|
+
output: runtime.cli.flags.outputFormat === 'json'
|
|
111
|
+
? JSON.stringify({ ...report, matchedAreaCount: areas.length, areas }, null, 2)
|
|
112
|
+
: renderDaemonCapabilityRouteRisk(report, areas),
|
|
113
|
+
exitCode: 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (args.mode === 'daemon-inventory') {
|
|
117
|
+
const connection = resolveAgentDaemonConnection(runtime.configManager, runtime.homeDirectory);
|
|
118
|
+
const inventory = await fetchLiveDaemonCapabilityInventory(connection);
|
|
119
|
+
if (!inventory.ok) {
|
|
120
|
+
return {
|
|
121
|
+
output: runtime.cli.flags.outputFormat === 'json'
|
|
122
|
+
? JSON.stringify(inventory, null, 2)
|
|
123
|
+
: renderDaemonCapabilityFailure(inventory),
|
|
124
|
+
exitCode: inventory.kind === 'auth_required' || inventory.kind === 'daemon_unavailable' ? 1 : 2,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const groups = filterDaemonCapabilityInventoryGroups(inventory.groups, args.query);
|
|
128
|
+
return {
|
|
129
|
+
output: runtime.cli.flags.outputFormat === 'json'
|
|
130
|
+
? JSON.stringify({ ...inventory, matchedGroupCount: groups.length, groups }, null, 2)
|
|
131
|
+
: renderDaemonCapabilityInventory(inventory, groups),
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
82
135
|
const report = buildOperatorCapabilityBenchmarkReport();
|
|
83
136
|
const capabilities = filterOperatorCapabilities(report.capabilities, args.query);
|
|
84
137
|
if (runtime.cli.flags.outputFormat === 'json') {
|
package/src/cli/help.ts
CHANGED
|
@@ -104,6 +104,8 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
104
104
|
` ${binary} capabilities`,
|
|
105
105
|
` ${binary} capabilities daemon`,
|
|
106
106
|
` ${binary} capabilities daemon gaps`,
|
|
107
|
+
` ${binary} capabilities daemon risk`,
|
|
108
|
+
` ${binary} capabilities daemon inventory`,
|
|
107
109
|
` ${binary} knowledge status`,
|
|
108
110
|
` ${binary} knowledge ask "What is GoodVibes Agent?"`,
|
|
109
111
|
` ${binary} ask "What is GoodVibes Agent?"`,
|
|
@@ -198,9 +200,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
198
200
|
examples: ['compat', 'compat --json'],
|
|
199
201
|
},
|
|
200
202
|
capabilities: {
|
|
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
|
|
203
|
+
usage: ['capabilities [openclaw|hermes|query]', 'capabilities daemon [query]', 'capabilities daemon gaps [query]', 'capabilities daemon risk [query]', 'capabilities daemon inventory [query]', 'capabilities --json'],
|
|
204
|
+
summary: 'Show the OpenClaw/Hermes capability benchmark, Agent readiness, live GoodVibes daemon method coverage, route risk, and daemon-measured product gaps.',
|
|
205
|
+
examples: ['capabilities', 'capabilities hermes', 'capabilities daemon', 'capabilities daemon gaps', 'capabilities daemon risk --json', 'capabilities daemon inventory channels --json'],
|
|
204
206
|
},
|
|
205
207
|
knowledge: {
|
|
206
208
|
usage: [
|
|
@@ -490,6 +490,8 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
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
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' },
|
|
494
|
+
{ id: 'capabilities-daemon-inventory', label: 'Full method inventory', detail: 'List every public daemon method by category, HTTP posture, access, and dangerous flag without querying default Knowledge/Wiki or HomeGraph.', command: '/capabilities daemon inventory', kind: 'command', safety: 'read-only' },
|
|
493
495
|
{ 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' },
|
|
494
496
|
{ 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' },
|
|
495
497
|
{ 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' },
|
|
@@ -581,6 +583,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
581
583
|
{ id: 'workplan', label: 'Open work plan', detail: 'Open the workspace-scoped work plan panel.', command: '/workplan panel', kind: 'command', safety: 'read-only' },
|
|
582
584
|
{ id: 'workplan-list', label: 'List work plan', detail: 'Print a concise work plan summary.', command: '/workplan list', kind: 'command', safety: 'read-only' },
|
|
583
585
|
{ 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' },
|
|
586
|
+
{ 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' },
|
|
584
587
|
],
|
|
585
588
|
},
|
|
586
589
|
{
|
|
@@ -6,12 +6,18 @@ import {
|
|
|
6
6
|
} from '../../operator/capability-benchmark.ts';
|
|
7
7
|
import {
|
|
8
8
|
buildDaemonCapabilityGapReport,
|
|
9
|
+
buildDaemonCapabilityRouteRiskReport,
|
|
9
10
|
fetchLiveDaemonCapabilityAudit,
|
|
11
|
+
fetchLiveDaemonCapabilityInventory,
|
|
10
12
|
filterDaemonCapabilityAuditAreas,
|
|
11
13
|
filterDaemonCapabilityGaps,
|
|
14
|
+
filterDaemonCapabilityInventoryGroups,
|
|
15
|
+
filterDaemonCapabilityRouteRiskAreas,
|
|
12
16
|
renderDaemonCapabilityAudit,
|
|
13
17
|
renderDaemonCapabilityFailure,
|
|
14
18
|
renderDaemonCapabilityGaps,
|
|
19
|
+
renderDaemonCapabilityInventory,
|
|
20
|
+
renderDaemonCapabilityRouteRisk,
|
|
15
21
|
} from '../../operator/daemon-capability-audit.ts';
|
|
16
22
|
import { resolveAgentDaemonConnection } from '../../agent/routine-schedule-promotion.ts';
|
|
17
23
|
|
|
@@ -37,6 +43,24 @@ export function registerCapabilitiesRuntimeCommands(registry: CommandRegistry):
|
|
|
37
43
|
ctx.print(renderDaemonCapabilityGaps(report, gaps));
|
|
38
44
|
return;
|
|
39
45
|
}
|
|
46
|
+
if (args[1] === 'risk' || args[1] === 'route-risk') {
|
|
47
|
+
const report = buildDaemonCapabilityRouteRiskReport(audit);
|
|
48
|
+
const query = args.slice(2).join(' ').trim() || undefined;
|
|
49
|
+
const areas = filterDaemonCapabilityRouteRiskAreas(report.areas, query);
|
|
50
|
+
ctx.print(renderDaemonCapabilityRouteRisk(report, areas));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (args[1] === 'inventory' || args[1] === 'methods' || args[1] === 'routes') {
|
|
54
|
+
const inventory = await fetchLiveDaemonCapabilityInventory(connection);
|
|
55
|
+
if (!inventory.ok) {
|
|
56
|
+
ctx.print(renderDaemonCapabilityFailure(inventory));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const query = args.slice(2).join(' ').trim() || undefined;
|
|
60
|
+
const groups = filterDaemonCapabilityInventoryGroups(inventory.groups, query);
|
|
61
|
+
ctx.print(renderDaemonCapabilityInventory(inventory, groups));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
40
64
|
const query = args.slice(1).join(' ').trim() || undefined;
|
|
41
65
|
const areas = filterDaemonCapabilityAuditAreas(audit.areas, query);
|
|
42
66
|
ctx.print(renderDaemonCapabilityAudit(audit, areas));
|
|
@@ -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
|
|
|
@@ -49,6 +49,9 @@ export interface DaemonCapabilityAuditArea {
|
|
|
49
49
|
readonly coverage: DaemonCapabilityRouteCoverage;
|
|
50
50
|
}[];
|
|
51
51
|
readonly routeRisk: {
|
|
52
|
+
readonly readOnlyMethodIds: readonly string[];
|
|
53
|
+
readonly mutatingMethodIds: readonly string[];
|
|
54
|
+
readonly authenticatedMethodIds: readonly string[];
|
|
52
55
|
readonly readOnlyMethodCount: number;
|
|
53
56
|
readonly mutatingMethodCount: number;
|
|
54
57
|
readonly authenticatedMethodCount: number;
|
|
@@ -100,6 +103,85 @@ export interface DaemonCapabilityGapReport {
|
|
|
100
103
|
readonly gaps: readonly DaemonCapabilityGap[];
|
|
101
104
|
}
|
|
102
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
|
+
|
|
138
|
+
export interface DaemonCapabilityInventoryMethod {
|
|
139
|
+
readonly id: string;
|
|
140
|
+
readonly title?: string;
|
|
141
|
+
readonly category: string;
|
|
142
|
+
readonly access: string;
|
|
143
|
+
readonly invokable: boolean | null;
|
|
144
|
+
readonly dangerous: boolean;
|
|
145
|
+
readonly httpMethod: string;
|
|
146
|
+
readonly path?: string;
|
|
147
|
+
readonly readOnly: boolean;
|
|
148
|
+
readonly mutating: boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface DaemonCapabilityInventoryGroup {
|
|
152
|
+
readonly category: string;
|
|
153
|
+
readonly methodCount: number;
|
|
154
|
+
readonly readOnlyMethodCount: number;
|
|
155
|
+
readonly mutatingMethodCount: number;
|
|
156
|
+
readonly authenticatedMethodCount: number;
|
|
157
|
+
readonly dangerousMethodCount: number;
|
|
158
|
+
readonly methods: readonly DaemonCapabilityInventoryMethod[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DaemonCapabilityInventoryReport {
|
|
162
|
+
readonly ok: true;
|
|
163
|
+
readonly kind: 'daemon.capabilities.inventory';
|
|
164
|
+
readonly baseUrl: string;
|
|
165
|
+
readonly daemonVersion: string;
|
|
166
|
+
readonly expectedSdkVersion: string;
|
|
167
|
+
readonly daemonCompatible: boolean;
|
|
168
|
+
readonly methodCatalogRoute: typeof DAEMON_METHOD_CATALOG_ROUTE;
|
|
169
|
+
readonly methodCount: number;
|
|
170
|
+
readonly agentKnowledgeRoute: typeof AGENT_KNOWLEDGE_STATUS_ROUTE;
|
|
171
|
+
readonly agentKnowledgeRouteReady: boolean;
|
|
172
|
+
readonly defaultKnowledgeFallback: false;
|
|
173
|
+
readonly homeGraphFallback: false;
|
|
174
|
+
readonly readOnlyMethodCount: number;
|
|
175
|
+
readonly mutatingMethodCount: number;
|
|
176
|
+
readonly authenticatedMethodCount: number;
|
|
177
|
+
readonly dangerousMethodCount: number;
|
|
178
|
+
readonly accessCounts: readonly {
|
|
179
|
+
readonly access: string;
|
|
180
|
+
readonly count: number;
|
|
181
|
+
}[];
|
|
182
|
+
readonly groups: readonly DaemonCapabilityInventoryGroup[];
|
|
183
|
+
}
|
|
184
|
+
|
|
103
185
|
export interface DaemonCapabilityAuditFailure {
|
|
104
186
|
readonly ok: false;
|
|
105
187
|
readonly kind: DaemonCapabilityAuditFailureKind;
|
|
@@ -345,6 +427,36 @@ function readMethodSummaries(body: unknown): readonly DaemonMethodSummary[] {
|
|
|
345
427
|
});
|
|
346
428
|
}
|
|
347
429
|
|
|
430
|
+
function normalizeMethodCategory(method: DaemonMethodSummary): string {
|
|
431
|
+
const category = method.category?.trim();
|
|
432
|
+
if (category) return category;
|
|
433
|
+
const [prefix] = method.id.split('.');
|
|
434
|
+
return prefix || 'uncategorized';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function normalizeAccess(method: DaemonMethodSummary): string {
|
|
438
|
+
const access = method.access?.trim();
|
|
439
|
+
return access || 'unknown';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function normalizeHttpMethod(method: DaemonMethodSummary): string {
|
|
443
|
+
return method.http?.method?.trim().toUpperCase() || 'UNKNOWN';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function isReadOnlyHttpMethod(httpMethod: string): boolean {
|
|
447
|
+
return httpMethod === 'GET' || httpMethod === 'HEAD';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function compareInventoryMethods(left: DaemonCapabilityInventoryMethod, right: DaemonCapabilityInventoryMethod): number {
|
|
451
|
+
return left.id.localeCompare(right.id);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function compareInventoryGroups(left: DaemonCapabilityInventoryGroup, right: DaemonCapabilityInventoryGroup): number {
|
|
455
|
+
const countDelta = right.methodCount - left.methodCount;
|
|
456
|
+
if (countDelta !== 0) return countDelta;
|
|
457
|
+
return left.category.localeCompare(right.category);
|
|
458
|
+
}
|
|
459
|
+
|
|
348
460
|
function daemonVersionFromStatus(body: unknown): string {
|
|
349
461
|
if (!isRecord(body)) return 'unknown';
|
|
350
462
|
return readString(body, 'version')
|
|
@@ -452,15 +564,17 @@ export function buildDaemonCapabilityAuditAreas(
|
|
|
452
564
|
const method = methodsById.get(methodId);
|
|
453
565
|
return method ? [method] : [];
|
|
454
566
|
});
|
|
455
|
-
const
|
|
567
|
+
const readOnlyMethodIds = areaMethods.filter((method) => {
|
|
456
568
|
const verb = method.http?.method?.toUpperCase();
|
|
457
569
|
return verb === 'GET' || verb === 'HEAD';
|
|
458
|
-
}).
|
|
459
|
-
const
|
|
570
|
+
}).map((method) => method.id);
|
|
571
|
+
const mutatingMethodIds = areaMethods.filter((method) => {
|
|
460
572
|
const verb = method.http?.method?.toUpperCase();
|
|
461
573
|
return Boolean(verb) && verb !== 'GET' && verb !== 'HEAD';
|
|
462
|
-
}).
|
|
463
|
-
const
|
|
574
|
+
}).map((method) => method.id);
|
|
575
|
+
const authenticatedMethodIds = areaMethods
|
|
576
|
+
.filter((method) => method.access === 'authenticated')
|
|
577
|
+
.map((method) => method.id);
|
|
464
578
|
const dangerousMethodIds = areaMethods
|
|
465
579
|
.filter((method) => method.dangerous === true)
|
|
466
580
|
.map((method) => method.id);
|
|
@@ -484,9 +598,12 @@ export function buildDaemonCapabilityAuditAreas(
|
|
|
484
598
|
missingOptionalMethodIds,
|
|
485
599
|
agentRoutes,
|
|
486
600
|
routeRisk: {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
601
|
+
readOnlyMethodIds,
|
|
602
|
+
mutatingMethodIds,
|
|
603
|
+
authenticatedMethodIds,
|
|
604
|
+
readOnlyMethodCount: readOnlyMethodIds.length,
|
|
605
|
+
mutatingMethodCount: mutatingMethodIds.length,
|
|
606
|
+
authenticatedMethodCount: authenticatedMethodIds.length,
|
|
490
607
|
dangerousMethodIds,
|
|
491
608
|
},
|
|
492
609
|
next: requirement.next,
|
|
@@ -545,6 +662,116 @@ export async function fetchLiveDaemonCapabilityAudit(
|
|
|
545
662
|
}
|
|
546
663
|
}
|
|
547
664
|
|
|
665
|
+
export function buildDaemonCapabilityInventoryReport(
|
|
666
|
+
connection: AgentDaemonConnection,
|
|
667
|
+
daemonVersion: string,
|
|
668
|
+
agentKnowledgeRouteReady: boolean,
|
|
669
|
+
methodSummaries: readonly DaemonMethodSummary[],
|
|
670
|
+
): DaemonCapabilityInventoryReport {
|
|
671
|
+
const methods = methodSummaries.map((method): DaemonCapabilityInventoryMethod => {
|
|
672
|
+
const httpMethod = normalizeHttpMethod(method);
|
|
673
|
+
const readOnly = isReadOnlyHttpMethod(httpMethod);
|
|
674
|
+
return {
|
|
675
|
+
id: method.id,
|
|
676
|
+
title: method.title,
|
|
677
|
+
category: normalizeMethodCategory(method),
|
|
678
|
+
access: normalizeAccess(method),
|
|
679
|
+
invokable: typeof method.invokable === 'boolean' ? method.invokable : null,
|
|
680
|
+
dangerous: method.dangerous === true,
|
|
681
|
+
httpMethod,
|
|
682
|
+
path: method.http?.path,
|
|
683
|
+
readOnly,
|
|
684
|
+
mutating: httpMethod !== 'UNKNOWN' && !readOnly,
|
|
685
|
+
};
|
|
686
|
+
}).sort(compareInventoryMethods);
|
|
687
|
+
|
|
688
|
+
const groupsByCategory = new Map<string, DaemonCapabilityInventoryMethod[]>();
|
|
689
|
+
for (const method of methods) {
|
|
690
|
+
const existing = groupsByCategory.get(method.category) ?? [];
|
|
691
|
+
existing.push(method);
|
|
692
|
+
groupsByCategory.set(method.category, existing);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const groups = [...groupsByCategory.entries()].map(([category, categoryMethods]): DaemonCapabilityInventoryGroup => ({
|
|
696
|
+
category,
|
|
697
|
+
methodCount: categoryMethods.length,
|
|
698
|
+
readOnlyMethodCount: categoryMethods.filter((method) => method.readOnly).length,
|
|
699
|
+
mutatingMethodCount: categoryMethods.filter((method) => method.mutating).length,
|
|
700
|
+
authenticatedMethodCount: categoryMethods.filter((method) => method.access === 'authenticated').length,
|
|
701
|
+
dangerousMethodCount: categoryMethods.filter((method) => method.dangerous).length,
|
|
702
|
+
methods: categoryMethods,
|
|
703
|
+
})).sort(compareInventoryGroups);
|
|
704
|
+
|
|
705
|
+
const accessEntries = new Map<string, number>();
|
|
706
|
+
for (const method of methods) {
|
|
707
|
+
accessEntries.set(method.access, (accessEntries.get(method.access) ?? 0) + 1);
|
|
708
|
+
}
|
|
709
|
+
const accessCounts = [...accessEntries.entries()]
|
|
710
|
+
.map(([access, count]) => ({ access, count }))
|
|
711
|
+
.sort((left, right) => {
|
|
712
|
+
const countDelta = right.count - left.count;
|
|
713
|
+
if (countDelta !== 0) return countDelta;
|
|
714
|
+
return left.access.localeCompare(right.access);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
ok: true,
|
|
719
|
+
kind: 'daemon.capabilities.inventory',
|
|
720
|
+
baseUrl: connection.baseUrl,
|
|
721
|
+
daemonVersion,
|
|
722
|
+
expectedSdkVersion: SDK_VERSION,
|
|
723
|
+
daemonCompatible: daemonVersion === SDK_VERSION,
|
|
724
|
+
methodCatalogRoute: DAEMON_METHOD_CATALOG_ROUTE,
|
|
725
|
+
methodCount: methods.length,
|
|
726
|
+
agentKnowledgeRoute: AGENT_KNOWLEDGE_STATUS_ROUTE,
|
|
727
|
+
agentKnowledgeRouteReady,
|
|
728
|
+
defaultKnowledgeFallback: false,
|
|
729
|
+
homeGraphFallback: false,
|
|
730
|
+
readOnlyMethodCount: methods.filter((method) => method.readOnly).length,
|
|
731
|
+
mutatingMethodCount: methods.filter((method) => method.mutating).length,
|
|
732
|
+
authenticatedMethodCount: methods.filter((method) => method.access === 'authenticated').length,
|
|
733
|
+
dangerousMethodCount: methods.filter((method) => method.dangerous).length,
|
|
734
|
+
accessCounts,
|
|
735
|
+
groups,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export type DaemonCapabilityInventoryResult =
|
|
740
|
+
| DaemonCapabilityInventoryReport
|
|
741
|
+
| DaemonCapabilityAuditFailure;
|
|
742
|
+
|
|
743
|
+
export async function fetchLiveDaemonCapabilityInventory(
|
|
744
|
+
connection: AgentDaemonConnection,
|
|
745
|
+
): Promise<DaemonCapabilityInventoryResult> {
|
|
746
|
+
let daemonVersion = 'unknown';
|
|
747
|
+
try {
|
|
748
|
+
const status = await fetchJson(connection, DAEMON_STATUS_ROUTE);
|
|
749
|
+
if (status.ok) {
|
|
750
|
+
daemonVersion = daemonVersionFromStatus(status.body);
|
|
751
|
+
} else if (status.status === 401 || status.status === 403) {
|
|
752
|
+
return failureFromResponse(status, connection, DAEMON_STATUS_ROUTE, daemonVersion);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const methods = await fetchJson(connection, DAEMON_METHOD_CATALOG_ROUTE);
|
|
756
|
+
if (!methods.ok) return failureFromResponse(methods, connection, DAEMON_METHOD_CATALOG_ROUTE, daemonVersion);
|
|
757
|
+
|
|
758
|
+
const agentKnowledge = await fetchJson(connection, AGENT_KNOWLEDGE_STATUS_ROUTE);
|
|
759
|
+
if (!agentKnowledge.ok) {
|
|
760
|
+
const failure = failureFromResponse(agentKnowledge, connection, AGENT_KNOWLEDGE_STATUS_ROUTE, daemonVersion);
|
|
761
|
+
if (failure.kind === 'auth_required') return failure;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return buildDaemonCapabilityInventoryReport(
|
|
765
|
+
connection,
|
|
766
|
+
daemonVersion,
|
|
767
|
+
agentKnowledge.ok,
|
|
768
|
+
readMethodSummaries(methods.body),
|
|
769
|
+
);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
return failureFromThrown(error, connection, daemonVersion === 'unknown' ? DAEMON_STATUS_ROUTE : DAEMON_METHOD_CATALOG_ROUTE);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
548
775
|
export function filterDaemonCapabilityAuditAreas(
|
|
549
776
|
areas: readonly DaemonCapabilityAuditArea[],
|
|
550
777
|
query: string | undefined,
|
|
@@ -721,6 +948,178 @@ export function renderDaemonCapabilityGaps(
|
|
|
721
948
|
return lines.join('\n').trimEnd();
|
|
722
949
|
}
|
|
723
950
|
|
|
951
|
+
export function buildDaemonCapabilityRouteRiskReport(
|
|
952
|
+
audit: DaemonCapabilityAuditSuccess,
|
|
953
|
+
areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
|
|
954
|
+
): DaemonCapabilityRouteRiskReport {
|
|
955
|
+
const riskAreas = areas.map((area): DaemonCapabilityRouteRiskArea => ({
|
|
956
|
+
areaId: area.id,
|
|
957
|
+
title: area.title,
|
|
958
|
+
coverage: area.coverage,
|
|
959
|
+
readOnlyMethodIds: area.routeRisk.readOnlyMethodIds,
|
|
960
|
+
mutatingMethodIds: area.routeRisk.mutatingMethodIds,
|
|
961
|
+
authenticatedMethodIds: area.routeRisk.authenticatedMethodIds,
|
|
962
|
+
readOnlyMethodCount: area.routeRisk.readOnlyMethodCount,
|
|
963
|
+
mutatingMethodCount: area.routeRisk.mutatingMethodCount,
|
|
964
|
+
authenticatedMethodCount: area.routeRisk.authenticatedMethodCount,
|
|
965
|
+
dangerousMethodIds: area.routeRisk.dangerousMethodIds,
|
|
966
|
+
}));
|
|
967
|
+
const readOnlyMethodIds = new Set(riskAreas.flatMap((area) => area.readOnlyMethodIds));
|
|
968
|
+
const mutatingMethodIds = new Set(riskAreas.flatMap((area) => area.mutatingMethodIds));
|
|
969
|
+
const authenticatedMethodIds = new Set(riskAreas.flatMap((area) => area.authenticatedMethodIds));
|
|
970
|
+
const dangerousMethodIds = new Set(riskAreas.flatMap((area) => area.dangerousMethodIds));
|
|
971
|
+
|
|
972
|
+
return {
|
|
973
|
+
ok: true,
|
|
974
|
+
kind: 'daemon.capabilities.route_risk',
|
|
975
|
+
baseUrl: audit.baseUrl,
|
|
976
|
+
daemonVersion: audit.daemonVersion,
|
|
977
|
+
expectedSdkVersion: audit.expectedSdkVersion,
|
|
978
|
+
daemonCompatible: audit.daemonCompatible,
|
|
979
|
+
methodCatalogRoute: audit.methodCatalogRoute,
|
|
980
|
+
agentKnowledgeRoute: audit.agentKnowledgeRoute,
|
|
981
|
+
agentKnowledgeRouteReady: audit.agentKnowledgeRouteReady,
|
|
982
|
+
defaultKnowledgeFallback: false,
|
|
983
|
+
homeGraphFallback: false,
|
|
984
|
+
totalReadOnlyMethodCount: readOnlyMethodIds.size,
|
|
985
|
+
totalMutatingMethodCount: mutatingMethodIds.size,
|
|
986
|
+
totalAuthenticatedMethodCount: authenticatedMethodIds.size,
|
|
987
|
+
totalDangerousMethodCount: dangerousMethodIds.size,
|
|
988
|
+
areas: riskAreas,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
export function filterDaemonCapabilityRouteRiskAreas(
|
|
993
|
+
areas: readonly DaemonCapabilityRouteRiskArea[],
|
|
994
|
+
query: string | undefined,
|
|
995
|
+
): readonly DaemonCapabilityRouteRiskArea[] {
|
|
996
|
+
const normalized = query?.trim().toLowerCase();
|
|
997
|
+
if (!normalized) return areas;
|
|
998
|
+
return areas.filter((area) => {
|
|
999
|
+
return area.areaId.includes(normalized)
|
|
1000
|
+
|| area.title.toLowerCase().includes(normalized)
|
|
1001
|
+
|| area.coverage.includes(normalized)
|
|
1002
|
+
|| area.dangerousMethodIds.some((methodId) => methodId.includes(normalized));
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export function renderDaemonCapabilityRouteRisk(
|
|
1007
|
+
report: DaemonCapabilityRouteRiskReport,
|
|
1008
|
+
areas: readonly DaemonCapabilityRouteRiskArea[] = report.areas,
|
|
1009
|
+
): string {
|
|
1010
|
+
const lines: string[] = [
|
|
1011
|
+
'GoodVibes daemon route risk review',
|
|
1012
|
+
` daemon: ${report.baseUrl}`,
|
|
1013
|
+
` SDK: Agent expects ${report.expectedSdkVersion}; daemon reports ${report.daemonVersion}`,
|
|
1014
|
+
` compatibility: ${report.daemonCompatible ? 'matched' : 'mismatch'}`,
|
|
1015
|
+
` method catalog: ${report.methodCatalogRoute}`,
|
|
1016
|
+
` Agent Knowledge: ${report.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${report.agentKnowledgeRoute}`,
|
|
1017
|
+
' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
|
|
1018
|
+
` totals: ${report.totalReadOnlyMethodCount} read-only; ${report.totalMutatingMethodCount} mutating; ${report.totalDangerousMethodCount} dangerous; ${report.totalAuthenticatedMethodCount} authenticated`,
|
|
1019
|
+
' policy: exact command plus confirmation for side effects; ordinary chat never triggers mutating routes',
|
|
1020
|
+
'',
|
|
1021
|
+
];
|
|
1022
|
+
|
|
1023
|
+
const visibleAreas = areas.filter((area) => {
|
|
1024
|
+
return area.readOnlyMethodCount > 0
|
|
1025
|
+
|| area.mutatingMethodCount > 0
|
|
1026
|
+
|| area.authenticatedMethodCount > 0
|
|
1027
|
+
|| area.dangerousMethodIds.length > 0;
|
|
1028
|
+
});
|
|
1029
|
+
if (visibleAreas.length === 0) {
|
|
1030
|
+
lines.push('No route risk metadata matched this query.');
|
|
1031
|
+
return lines.join('\n');
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
for (const area of visibleAreas) {
|
|
1035
|
+
lines.push(`${area.title} [${area.coverage}]`);
|
|
1036
|
+
lines.push(` methods: ${area.readOnlyMethodCount} read-only; ${area.mutatingMethodCount} mutating; ${area.dangerousMethodIds.length} dangerous; ${area.authenticatedMethodCount} authenticated`);
|
|
1037
|
+
if (area.dangerousMethodIds.length > 0) {
|
|
1038
|
+
lines.push(` dangerous methods: ${area.dangerousMethodIds.join(', ')}`);
|
|
1039
|
+
}
|
|
1040
|
+
lines.push(' approval posture: read-only by default; exact command and confirmation required for side effects.');
|
|
1041
|
+
lines.push('');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
return lines.join('\n').trimEnd();
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
export function filterDaemonCapabilityInventoryGroups(
|
|
1048
|
+
groups: readonly DaemonCapabilityInventoryGroup[],
|
|
1049
|
+
query: string | undefined,
|
|
1050
|
+
): readonly DaemonCapabilityInventoryGroup[] {
|
|
1051
|
+
const normalized = query?.trim().toLowerCase();
|
|
1052
|
+
if (!normalized) return groups;
|
|
1053
|
+
return groups.flatMap((group): DaemonCapabilityInventoryGroup[] => {
|
|
1054
|
+
const categoryMatches = group.category.toLowerCase().includes(normalized);
|
|
1055
|
+
const methods = categoryMatches
|
|
1056
|
+
? group.methods
|
|
1057
|
+
: group.methods.filter((method) => {
|
|
1058
|
+
return method.id.toLowerCase().includes(normalized)
|
|
1059
|
+
|| method.title?.toLowerCase().includes(normalized) === true
|
|
1060
|
+
|| method.access.toLowerCase().includes(normalized)
|
|
1061
|
+
|| method.httpMethod.toLowerCase().includes(normalized)
|
|
1062
|
+
|| method.path?.toLowerCase().includes(normalized) === true;
|
|
1063
|
+
});
|
|
1064
|
+
if (methods.length === 0) return [];
|
|
1065
|
+
return [{
|
|
1066
|
+
category: group.category,
|
|
1067
|
+
methodCount: methods.length,
|
|
1068
|
+
readOnlyMethodCount: methods.filter((method) => method.readOnly).length,
|
|
1069
|
+
mutatingMethodCount: methods.filter((method) => method.mutating).length,
|
|
1070
|
+
authenticatedMethodCount: methods.filter((method) => method.access === 'authenticated').length,
|
|
1071
|
+
dangerousMethodCount: methods.filter((method) => method.dangerous).length,
|
|
1072
|
+
methods,
|
|
1073
|
+
}];
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function renderInventoryMethod(method: DaemonCapabilityInventoryMethod): string {
|
|
1078
|
+
const risk = method.dangerous
|
|
1079
|
+
? ' dangerous'
|
|
1080
|
+
: method.mutating
|
|
1081
|
+
? ' mutating'
|
|
1082
|
+
: ' read-only';
|
|
1083
|
+
const route = method.path ? ` ${method.httpMethod} ${method.path}` : ` ${method.httpMethod}`;
|
|
1084
|
+
return ` ${method.id} [${method.access};${risk}]${route}`;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
export function renderDaemonCapabilityInventory(
|
|
1088
|
+
report: DaemonCapabilityInventoryReport,
|
|
1089
|
+
groups: readonly DaemonCapabilityInventoryGroup[] = report.groups,
|
|
1090
|
+
): string {
|
|
1091
|
+
const lines: string[] = [
|
|
1092
|
+
'GoodVibes daemon method inventory',
|
|
1093
|
+
` daemon: ${report.baseUrl}`,
|
|
1094
|
+
` SDK: Agent expects ${report.expectedSdkVersion}; daemon reports ${report.daemonVersion}`,
|
|
1095
|
+
` compatibility: ${report.daemonCompatible ? 'matched' : 'mismatch'}`,
|
|
1096
|
+
` method catalog: ${report.methodCount} methods from ${report.methodCatalogRoute}`,
|
|
1097
|
+
` Agent Knowledge: ${report.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${report.agentKnowledgeRoute}`,
|
|
1098
|
+
' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
|
|
1099
|
+
` totals: ${report.readOnlyMethodCount} read-only; ${report.mutatingMethodCount} mutating; ${report.dangerousMethodCount} dangerous; ${report.authenticatedMethodCount} authenticated`,
|
|
1100
|
+
` access: ${report.accessCounts.map((entry) => `${entry.access} ${entry.count}`).join('; ') || 'none'}`,
|
|
1101
|
+
'',
|
|
1102
|
+
];
|
|
1103
|
+
|
|
1104
|
+
if (groups.length === 0) {
|
|
1105
|
+
lines.push('No daemon methods matched this query.');
|
|
1106
|
+
return lines.join('\n');
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
for (const group of groups) {
|
|
1110
|
+
lines.push(`${group.category} (${group.methodCount})`);
|
|
1111
|
+
lines.push(` ${group.readOnlyMethodCount} read-only; ${group.mutatingMethodCount} mutating; ${group.dangerousMethodCount} dangerous; ${group.authenticatedMethodCount} authenticated`);
|
|
1112
|
+
const visibleMethods = group.methods.slice(0, 12);
|
|
1113
|
+
for (const method of visibleMethods) lines.push(renderInventoryMethod(method));
|
|
1114
|
+
if (group.methods.length > visibleMethods.length) {
|
|
1115
|
+
lines.push(` ... ${group.methods.length - visibleMethods.length} more; use --json or a narrower query for the full list.`);
|
|
1116
|
+
}
|
|
1117
|
+
lines.push('');
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
return lines.join('\n').trimEnd();
|
|
1121
|
+
}
|
|
1122
|
+
|
|
724
1123
|
export function renderDaemonCapabilityAudit(
|
|
725
1124
|
audit: DaemonCapabilityAuditSuccess,
|
|
726
1125
|
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.
|
|
9
|
+
let _version = '0.1.46';
|
|
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 {
|